From 17e407f0c1aa056ebfe8ca6d7283bdbdf773e68a Mon Sep 17 00:00:00 2001 From: lars Date: Wed, 21 Nov 2018 23:44:46 +0000 Subject: [PATCH 001/156] Add No-Brain Jogging to examples --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 714e7850..aa31949b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,11 @@ Here are some screenshots from the examples! | --- | --- | | ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![Starfield](https://github.com/faiface/pixel-examples/blob/master/community/starfield/screenshot.png) | + +| [gonutz' No-Brain Jogging](https://github.com/gonutz/no-brain-jogging) | +| --- | +| ![NoBrainJogging](https://raw.githubusercontent.com/gonutz/no-brain-jogging/master/screenshots/screenshot.png) | + ## Features Here's the list of the main features in Pixel. Although Pixel is still under heavy development, From 4a0b2f5107909df9baf6a3c2f4612a78ad5369db Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 23 Nov 2018 11:23:38 +0100 Subject: [PATCH 002/156] Replace startfield example with No-Brain Jogging --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index aa31949b..01b56f57 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,9 @@ Here are some screenshots from the examples! | --- | --- | | ![Smoke](https://github.com/faiface/pixel-examples/blob/master/smoke/screenshot.png) | ![Typewriter](https://github.com/faiface/pixel-examples/blob/master/typewriter/screenshot.png) | -| [Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster) | [Starfield](https://github.com/faiface/pixel-examples/blob/master/community/starfield) | +| [Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster) | [gonutz' No-Brain Jogging](https://github.com/gonutz/no-brain-jogging) | | --- | --- | -| ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![Starfield](https://github.com/faiface/pixel-examples/blob/master/community/starfield/screenshot.png) | - - -| [gonutz' No-Brain Jogging](https://github.com/gonutz/no-brain-jogging) | -| --- | -| ![NoBrainJogging](https://raw.githubusercontent.com/gonutz/no-brain-jogging/master/screenshots/screenshot.png) | +| ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![NoBrainJogging](https://raw.githubusercontent.com/gonutz/no-brain-jogging/master/screenshots/screenshot.png) | ## Features From c18b8f1c29da10f72a104ad17286b9786d225bd5 Mon Sep 17 00:00:00 2001 From: Magnus Date: Tue, 11 Dec 2018 09:24:17 +0100 Subject: [PATCH 003/156] Added position as out variable from vertex shader. Adding the position out form vertex shader makes us skip computation of position in the fragment shader based on gl_FragCoord. --- pixelgl/glshader.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pixelgl/glshader.go b/pixelgl/glshader.go index 33e4f612..50235793 100644 --- a/pixelgl/glshader.go +++ b/pixelgl/glshader.go @@ -226,6 +226,7 @@ in float aIntensity; out vec4 vColor; out vec2 vTexCoords; out float vIntensity; +out vec2 vPosition; uniform mat3 uTransform; uniform vec4 uBounds; @@ -235,6 +236,7 @@ void main() { vec2 normPos = (transPos - uBounds.xy) / uBounds.zw * 2 - vec2(1, 1); gl_Position = vec4(normPos, 0.0, 1.0); vColor = aColor; + vPosition = aPosition; vTexCoords = aTexCoords; vIntensity = aIntensity; } From 676509d8560dec370aed306dd63657337c5492a0 Mon Sep 17 00:00:00 2001 From: Humphrey Shotton Date: Sun, 30 Dec 2018 18:42:02 +0000 Subject: [PATCH 004/156] Added joystick input --- pixelgl/input.go | 2 + pixelgl/joystick.go | 132 ++++++++++++++++++++++++++++++++++++++++++++ pixelgl/window.go | 2 + 3 files changed, 136 insertions(+) create mode 100644 pixelgl/joystick.go diff --git a/pixelgl/input.go b/pixelgl/input.go index 2eedb0dc..a2ef8087 100644 --- a/pixelgl/input.go +++ b/pixelgl/input.go @@ -385,4 +385,6 @@ func (w *Window) UpdateInput() { w.tempInp.repeat = [KeyLast + 1]bool{} w.tempInp.scroll = pixel.ZV w.tempInp.typed = "" + + w.updateJoystickInput() } diff --git a/pixelgl/joystick.go b/pixelgl/joystick.go new file mode 100644 index 00000000..a1eb8d9a --- /dev/null +++ b/pixelgl/joystick.go @@ -0,0 +1,132 @@ +package pixelgl + +import ( + "github.com/go-gl/glfw/v3.2/glfw" +) + +// Joystick is a joystick or controller. +type Joystick int + +// List all of the joysticks. +const ( + Joystick1 = Joystick(glfw.Joystick1) + Joystick2 = Joystick(glfw.Joystick2) + Joystick3 = Joystick(glfw.Joystick3) + Joystick4 = Joystick(glfw.Joystick4) + Joystick5 = Joystick(glfw.Joystick5) + Joystick6 = Joystick(glfw.Joystick6) + Joystick7 = Joystick(glfw.Joystick7) + Joystick8 = Joystick(glfw.Joystick8) + Joystick9 = Joystick(glfw.Joystick9) + Joystick10 = Joystick(glfw.Joystick10) + Joystick11 = Joystick(glfw.Joystick11) + Joystick12 = Joystick(glfw.Joystick12) + Joystick13 = Joystick(glfw.Joystick13) + Joystick14 = Joystick(glfw.Joystick14) + Joystick15 = Joystick(glfw.Joystick15) + Joystick16 = Joystick(glfw.Joystick16) + //JoystickLast = Joystick(glfw.JoystickLast) +) + +// JoystickPresent returns if the joystick is currently connected. +func (w *Window) JoystickPresent(js Joystick) bool { + return w.currJoy.connected[js] +} + +// JoystickName returns the name of the joystick. A disconnected joystick will return an +// empty string. +func (w *Window) JoystickName(js Joystick) string { + return w.currJoy.name[js] +} + +// JoystickButtonCount returns the number of buttons a connected joystick has. +func (w *Window) JoystickButtonCount(js Joystick) int { + return len(w.currJoy.buttons[js]) +} + +// JoystickAxisCount returns the number of axes a connected joystick has. +func (w *Window) JoystickAxisCount(js Joystick) int { + return len(w.currJoy.axis[js]) +} + +// JoystickPressed returns whether the joystick Button is currently pressed down. +// +// If the button index is out of range, this will return false. +func (w *Window) JoystickPressed(js Joystick, button int) bool { + return w.currJoy.getButton(js, button) +} + +// JoystickJustPressed returns whether the joystick Button has just been pressed down. +// +// If the button index is out of range, this will return false. +func (w *Window) JoystickJustPressed(js Joystick, button int) bool { + return w.currJoy.getButton(js, button) && !w.prevJoy.getButton(js, button) +} + +// JoystickJustReleased returns whether the joystick Button has just been released up. +// +// If the button index is out of range, this will return false. +func (w *Window) JoystickJustReleased(js Joystick, button int) bool { + return !w.currJoy.getButton(js, button) && w.prevJoy.getButton(js, button) +} + +// JoystickAxis returns the value of a joystick axis at the last call to Window.Update. +// +// If the axis index is out of range, this will return 0. +func (w *Window) JoystickAxis(js Joystick, axis int) float64 { + return w.currJoy.getAxis(js, axis) +} + +// Used internally during Window.UpdateInput to update the state of the joysticks. +func (w *Window) updateJoystickInput() { + for js := Joystick1; js < Joystick16; js++ { + // Determine and store if the joystick was connected + joystickPresent := glfw.JoystickPresent(glfw.Joystick(js)) + w.tempJoy.connected[js] = joystickPresent + + if joystickPresent { + w.tempJoy.buttons[js] = glfw.GetJoystickButtons(glfw.Joystick(js)) + w.tempJoy.axis[js] = glfw.GetJoystickAxes(glfw.Joystick(js)) + + if !w.currJoy.connected[js] { + // The joystick was recently connected, we get the name + w.tempJoy.name[js] = glfw.GetJoystickName(glfw.Joystick(js)) + } else { + // Use the name from the previous one + w.tempJoy.name[js] = w.currJoy.name[js] + } + } else { + w.tempJoy.buttons[js] = []byte{} + w.tempJoy.axis[js] = []float32{} + w.tempJoy.name[js] = "" + } + } + + w.prevJoy = w.currJoy + w.currJoy = w.tempJoy +} + +type joystickState struct { + connected [Joystick16]bool + name [Joystick16]string + buttons [Joystick16][]byte + axis [Joystick16][]float32 +} + +// Returns if a button on a joystick is down, returning false if the button or joystick is invalid. +func (js *joystickState) getButton(joystick Joystick, button int) bool { + // Check that the joystick and button is valid, return false by default + if js.buttons[joystick] == nil || button >= len(js.buttons[joystick]) || button < 0 { + return false + } + return js.buttons[joystick][byte(button)] == 1 +} + +// Returns the value of a joystick axis, returning 0 if the button or joystick is invalid. +func (js *joystickState) getAxis(joystick Joystick, axis int) float64 { + // Check that the joystick and axis is valid, return 0 by default. + if js.axis[joystick] == nil || axis >= len(js.axis[joystick]) || axis < 0 { + return 0 + } + return float64(js.axis[joystick][axis]) +} diff --git a/pixelgl/window.go b/pixelgl/window.go index 2d646c53..c124152b 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -72,6 +72,8 @@ type Window struct { scroll pixel.Vec typed string } + + prevJoy, currJoy, tempJoy joystickState } var currWin *Window From 52b438424069f2098fb492eee5cc392c4896b9cd Mon Sep 17 00:00:00 2001 From: Humphrey Shotton Date: Wed, 9 Jan 2019 21:07:46 +0000 Subject: [PATCH 005/156] Removed JoystickLast --- pixelgl/joystick.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pixelgl/joystick.go b/pixelgl/joystick.go index a1eb8d9a..2d95108c 100644 --- a/pixelgl/joystick.go +++ b/pixelgl/joystick.go @@ -25,7 +25,6 @@ const ( Joystick14 = Joystick(glfw.Joystick14) Joystick15 = Joystick(glfw.Joystick15) Joystick16 = Joystick(glfw.Joystick16) - //JoystickLast = Joystick(glfw.JoystickLast) ) // JoystickPresent returns if the joystick is currently connected. From 026878de071f6e2b83e468b220bf1a75a7e52f8f Mon Sep 17 00:00:00 2001 From: Humphrey Shotton Date: Thu, 10 Jan 2019 19:25:55 +0000 Subject: [PATCH 006/156] Mark API as experimental --- pixelgl/joystick.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pixelgl/joystick.go b/pixelgl/joystick.go index 2d95108c..3cdec27b 100644 --- a/pixelgl/joystick.go +++ b/pixelgl/joystick.go @@ -28,50 +28,62 @@ const ( ) // JoystickPresent returns if the joystick is currently connected. +// +// This API is experimental. func (w *Window) JoystickPresent(js Joystick) bool { return w.currJoy.connected[js] } // JoystickName returns the name of the joystick. A disconnected joystick will return an // empty string. +// +// This API is experimental. func (w *Window) JoystickName(js Joystick) string { return w.currJoy.name[js] } // JoystickButtonCount returns the number of buttons a connected joystick has. +// +// This API is experimental. func (w *Window) JoystickButtonCount(js Joystick) int { return len(w.currJoy.buttons[js]) } // JoystickAxisCount returns the number of axes a connected joystick has. +// +// This API is experimental. func (w *Window) JoystickAxisCount(js Joystick) int { return len(w.currJoy.axis[js]) } // JoystickPressed returns whether the joystick Button is currently pressed down. -// // If the button index is out of range, this will return false. +// +// This API is experimental. func (w *Window) JoystickPressed(js Joystick, button int) bool { return w.currJoy.getButton(js, button) } // JoystickJustPressed returns whether the joystick Button has just been pressed down. -// // If the button index is out of range, this will return false. +// +// This API is experimental. func (w *Window) JoystickJustPressed(js Joystick, button int) bool { return w.currJoy.getButton(js, button) && !w.prevJoy.getButton(js, button) } // JoystickJustReleased returns whether the joystick Button has just been released up. -// // If the button index is out of range, this will return false. +// +// This API is experimental. func (w *Window) JoystickJustReleased(js Joystick, button int) bool { return !w.currJoy.getButton(js, button) && w.prevJoy.getButton(js, button) } // JoystickAxis returns the value of a joystick axis at the last call to Window.Update. -// // If the axis index is out of range, this will return 0. +// +// This API is experimental. func (w *Window) JoystickAxis(js Joystick, axis int) float64 { return w.currJoy.getAxis(js, axis) } From 3bed5a84498945184ff9493dcbc75e6a4fd9aec3 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Fri, 18 Jan 2019 11:37:17 +0000 Subject: [PATCH 007/156] WIP adding tests --- batch_test.go | 154 +++++++++++ color_test.go | 361 ++++++++++++++++++++++++ compose_test.go | 101 +++++++ data_test.go | 325 ++++++++++++++++++++++ drawer_test.go | 118 ++++++++ geometry_test.go | 94 +++---- go.mod | 12 + go.sum | 16 ++ matrix_test.go | 168 +++++++++++ rect_test.go | 609 ++++++++++++++++++++++++++++++++++++++++ sprite_test.go | 122 ++++++++ vec_test.go | 707 +++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 2727 insertions(+), 60 deletions(-) create mode 100644 batch_test.go create mode 100644 compose_test.go create mode 100644 data_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 rect_test.go create mode 100644 sprite_test.go create mode 100644 vec_test.go diff --git a/batch_test.go b/batch_test.go new file mode 100644 index 00000000..a8df400d --- /dev/null +++ b/batch_test.go @@ -0,0 +1,154 @@ +package pixel_test + +import ( + "image/color" + "reflect" + "testing" + + "github.com/faiface/pixel" +) + +func TestNewBatch(t *testing.T) { + type args struct { + container pixel.Triangles + pic pixel.Picture + } + tests := []struct { + name string + args args + want *pixel.Batch + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.NewBatch(tt.args.container, tt.args.pic); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewBatch() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBatch_Dirty(t *testing.T) { + tests := []struct { + name string + b *pixel.Batch + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.b.Dirty() + }) + } +} + +func TestBatch_Clear(t *testing.T) { + tests := []struct { + name string + b *pixel.Batch + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.b.Clear() + }) + } +} + +func TestBatch_Draw(t *testing.T) { + type args struct { + t pixel.Target + } + tests := []struct { + name string + b *pixel.Batch + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.b.Draw(tt.args.t) + }) + } +} + +func TestBatch_SetMatrix(t *testing.T) { + type args struct { + m pixel.Matrix + } + tests := []struct { + name string + b *pixel.Batch + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.b.SetMatrix(tt.args.m) + }) + } +} + +func TestBatch_SetColorMask(t *testing.T) { + type args struct { + c color.Color + } + tests := []struct { + name string + b *pixel.Batch + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.b.SetColorMask(tt.args.c) + }) + } +} + +func TestBatch_MakeTriangles(t *testing.T) { + type args struct { + t pixel.Triangles + } + tests := []struct { + name string + b *pixel.Batch + args args + want pixel.TargetTriangles + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.b.MakeTriangles(tt.args.t); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Batch.MakeTriangles() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBatch_MakePicture(t *testing.T) { + type args struct { + p pixel.Picture + } + tests := []struct { + name string + b *pixel.Batch + args args + want pixel.TargetPicture + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.b.MakePicture(tt.args.p); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Batch.MakePicture() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/color_test.go b/color_test.go index 31514d7e..bdc689ae 100644 --- a/color_test.go +++ b/color_test.go @@ -3,6 +3,7 @@ package pixel_test import ( "fmt" "image/color" + "reflect" "testing" "github.com/faiface/pixel" @@ -22,3 +23,363 @@ func BenchmarkColorToRGBA(b *testing.B) { }) } } + +func TestRGB(t *testing.T) { + type args struct { + r float64 + g float64 + b float64 + } + tests := []struct { + name string + args args + want pixel.RGBA + }{ + { + name: "RBG: create black", + args: args{r: 0, g: 0, b: 0}, + want: pixel.RGBA{R: 0, G: 0, B: 0, A: 1}, + }, + { + name: "RBG: create white", + args: args{r: 1, g: 1, b: 1}, + want: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}, + }, + { + name: "RBG: create nonsense", + args: args{r: 500, g: 500, b: 500}, + want: pixel.RGBA{R: 500, G: 500, B: 500, A: 1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.RGB(tt.args.r, tt.args.g, tt.args.b); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RGB() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAlpha(t *testing.T) { + type args struct { + a float64 + } + tests := []struct { + name string + args args + want pixel.RGBA + }{ + { + name: "Alpha: transparent", + args: args{a: 0}, + want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, + }, + { + name: "Alpha: obaque", + args: args{a: 1}, + want: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}, + }, + { + name: "Alpha: nonsense", + args: args{a: 1024}, + want: pixel.RGBA{R: 1024, G: 1024, B: 1024, A: 1024}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.Alpha(tt.args.a); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Alpha() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRGBA_Add(t *testing.T) { + type fields struct { + R float64 + G float64 + B float64 + A float64 + } + type args struct { + d pixel.RGBA + } + tests := []struct { + name string + fields fields + args args + want pixel.RGBA + }{ + { + name: "RGBA.Add: add to black", + fields: fields{R: 0, G: 0, B: 0, A: 1}, + args: args{d: pixel.RGBA{R: 50, G: 50, B: 50, A: 0}}, + want: pixel.RGBA{R: 50, G: 50, B: 50, A: 1}, + }, + { + name: "RGBA.Add: add to white", + fields: fields{R: 1, G: 1, B: 1, A: 1}, + args: args{d: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}}, + want: pixel.RGBA{R: 2, G: 2, B: 2, A: 2}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.RGBA{ + R: tt.fields.R, + G: tt.fields.G, + B: tt.fields.B, + A: tt.fields.A, + } + if got := c.Add(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RGBA.Add() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRGBA_Sub(t *testing.T) { + type fields struct { + R float64 + G float64 + B float64 + A float64 + } + type args struct { + d pixel.RGBA + } + tests := []struct { + name string + fields fields + args args + want pixel.RGBA + }{ + { + name: "RGBA.Sub: sub from white", + fields: fields{R: 1, G: 1, B: 1, A: 1}, + args: args{d: pixel.RGBA{R: .5, G: .5, B: .5, A: 0}}, + want: pixel.RGBA{R: .5, G: .5, B: .5, A: 1}, + }, + { + name: "RGBA.Sub: sub from black", + fields: fields{R: 0, G: 0, B: 0, A: 0}, + args: args{d: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}}, + want: pixel.RGBA{R: -1, G: -1, B: -1, A: -1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.RGBA{ + R: tt.fields.R, + G: tt.fields.G, + B: tt.fields.B, + A: tt.fields.A, + } + if got := c.Sub(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RGBA.Sub() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRGBA_Mul(t *testing.T) { + type fields struct { + R float64 + G float64 + B float64 + A float64 + } + type args struct { + d pixel.RGBA + } + + greaterThanOne := args{d: pixel.RGBA{R: 2, G: 3, B: 4, A: 5}} + lessThanOne := args{d: pixel.RGBA{R: .2, G: .3, B: .4, A: .5}} + + tests := []struct { + name string + fields fields + args args + want pixel.RGBA + }{ + { + name: "RGBA.Mul: multiply black by >1", + fields: fields{R: 0, G: 0, B: 0, A: 0}, + args: greaterThanOne, + want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, + }, + { + name: "RGBA.Mul: multiply white by >1", + fields: fields{R: 1, G: 1, B: 1, A: 1}, + args: greaterThanOne, + want: pixel.RGBA{R: 2, G: 3, B: 4, A: 5}, + }, + { + name: "RGBA.Mul: multiply black by <1", + fields: fields{R: 0, G: 0, B: 0, A: 0}, + args: lessThanOne, + want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, + }, + { + name: "RGBA.Mul: multiply white by <1", + fields: fields{R: 1, G: 1, B: 1, A: 1}, + args: lessThanOne, + want: pixel.RGBA{R: .2, G: .3, B: .4, A: .5}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.RGBA{ + R: tt.fields.R, + G: tt.fields.G, + B: tt.fields.B, + A: tt.fields.A, + } + if got := c.Mul(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RGBA.Mul() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRGBA_Scaled(t *testing.T) { + type fields struct { + R float64 + G float64 + B float64 + A float64 + } + type args struct { + scale float64 + } + tests := []struct { + name string + fields fields + args args + want pixel.RGBA + }{ + { + name: "RBGA.Scaled: black <1", + fields: fields{R: 0, G: 0, B: 0, A: 0}, + args: args{scale: 0.5}, + want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, + }, + { + name: "RBGA.Scaled: black <1", + fields: fields{R: 1, G: 1, B: 1, A: 1}, + args: args{scale: 0.5}, + want: pixel.RGBA{R: .5, G: .5, B: .5, A: .5}, + }, + { + name: "RBGA.Scaled: black >1", + fields: fields{R: 0, G: 0, B: 0, A: 0}, + args: args{scale: 2}, + want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, + }, + { + name: "RBGA.Scaled: black >1", + fields: fields{R: 1, G: 1, B: 1, A: 1}, + args: args{scale: 2}, + want: pixel.RGBA{R: 2, G: 2, B: 2, A: 2}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.RGBA{ + R: tt.fields.R, + G: tt.fields.G, + B: tt.fields.B, + A: tt.fields.A, + } + if got := c.Scaled(tt.args.scale); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RGBA.Scaled() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRGBA_RGBA(t *testing.T) { + type fields struct { + R float64 + G float64 + B float64 + A float64 + } + tests := []struct { + name string + fields fields + wantR uint32 + wantG uint32 + wantB uint32 + wantA uint32 + }{ + { + name: "RGBA.RGBA: black", + fields: fields{R: 0, G: 0, B: 0, A: 0}, + wantR: 0, + wantG: 0, + wantB: 0, + wantA: 0, + }, + { + name: "RGBA.RGBA: white", + fields: fields{R: 1, G: 1, B: 1, A: 1}, + wantR: 65535, + wantG: 65535, + wantB: 65535, + wantA: 65535, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.RGBA{ + R: tt.fields.R, + G: tt.fields.G, + B: tt.fields.B, + A: tt.fields.A, + } + gotR, gotG, gotB, gotA := c.RGBA() + if gotR != tt.wantR { + t.Errorf("RGBA.RGBA() gotR = %v, want %v", gotR, tt.wantR) + } + if gotG != tt.wantG { + t.Errorf("RGBA.RGBA() gotG = %v, want %v", gotG, tt.wantG) + } + if gotB != tt.wantB { + t.Errorf("RGBA.RGBA() gotB = %v, want %v", gotB, tt.wantB) + } + if gotA != tt.wantA { + t.Errorf("RGBA.RGBA() gotA = %v, want %v", gotA, tt.wantA) + } + }) + } +} + +func TestToRGBA(t *testing.T) { + type args struct { + c color.Color + } + tests := []struct { + name string + args args + want pixel.RGBA + }{ + { + name: "ToRGBA: black", + args: args{c: color.Black}, + want: pixel.RGBA{R: 0, G: 0, B: 0, A: 1}, + }, + { + name: "ToRGBA: white", + args: args{c: color.White}, + want: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.ToRGBA(tt.args.c); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToRGBA() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/compose_test.go b/compose_test.go new file mode 100644 index 00000000..2bddf0f1 --- /dev/null +++ b/compose_test.go @@ -0,0 +1,101 @@ +package pixel_test + +import ( + "reflect" + "testing" + + "github.com/faiface/pixel" +) + +func TestComposeMethod_Compose(t *testing.T) { + type args struct { + a pixel.RGBA + b pixel.RGBA + } + + a := pixel.RGBA{R: 200, G: 200, B: 200, A: .8} + b := pixel.RGBA{R: 100, G: 100, B: 100, A: .5} + c := pixel.RGBA{R: 200, G: 200, B: 200, A: .5} + + tests := []struct { + name string + cm pixel.ComposeMethod + args args + want pixel.RGBA + }{ + { + name: "ComposeMethod.Compose: ComposeOver", + cm: pixel.ComposeOver, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 220, G: 220, B: 220, A: .9}, + }, + { + name: "ComposeMethod.Compose: ComposeIn", + cm: pixel.ComposeIn, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 100, G: 100, B: 100, A: .4}, + }, + { + name: "ComposeMethod.Compose: ComposeOut", + cm: pixel.ComposeOut, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 100, G: 100, B: 100, A: .4}, + }, + { + name: "ComposeMethod.Compose: ComposeAtop", + cm: pixel.ComposeAtop, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 120, G: 120, B: 120, A: .5}, + }, + { + name: "ComposeMethod.Compose: ComposeRover", + cm: pixel.ComposeRover, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 200, G: 200, B: 200, A: .9}, + }, + { + name: "ComposeMethod.Compose: ComposeRin", + cm: pixel.ComposeRin, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 80, G: 80, B: 80, A: .4}, + }, + { + name: "ComposeMethod.Compose: ComposeRout", + cm: pixel.ComposeRout, + // Using 'c' here to make the "want"ed RGBA rational + args: args{a: c, b: b}, + want: pixel.RGBA{R: 50, G: 50, B: 50, A: .25}, + }, + { + name: "ComposeMethod.Compose: ComposeRatop", + cm: pixel.ComposeRatop, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 180, G: 180, B: 180, A: .8}, + }, + { + name: "ComposeMethod.Compose: ComposeXor", + cm: pixel.ComposeXor, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 120, G: 120, B: 120, A: .5}, + }, + { + name: "ComposeMethod.Compose: ComposePlus", + cm: pixel.ComposePlus, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 300, G: 300, B: 300, A: 1.3}, + }, + { + name: "ComposeMethod.Compose: ComposeCopy", + cm: pixel.ComposeCopy, + args: args{a: a, b: b}, + want: pixel.RGBA{R: 200, G: 200, B: 200, A: .8}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.cm.Compose(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ComposeMethod.Compose() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/data_test.go b/data_test.go new file mode 100644 index 00000000..8c621bbd --- /dev/null +++ b/data_test.go @@ -0,0 +1,325 @@ +package pixel_test + +import ( + "image" + "reflect" + "testing" + + "github.com/faiface/pixel" +) + +func TestMakeTrianglesData(t *testing.T) { + type args struct { + len int + } + tests := []struct { + name string + args args + want *pixel.TrianglesData + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MakeTrianglesData(tt.args.len); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MakeTrianglesData() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTrianglesData_Len(t *testing.T) { + tests := []struct { + name string + td *pixel.TrianglesData + want int + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.td.Len(); got != tt.want { + t.Errorf("TrianglesData.Len() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTrianglesData_SetLen(t *testing.T) { + type args struct { + len int + } + tests := []struct { + name string + td *pixel.TrianglesData + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetLen(tt.args.len) + }) + } +} + +func TestTrianglesData_Slice(t *testing.T) { + type args struct { + i int + j int + } + tests := []struct { + name string + td *pixel.TrianglesData + args args + want pixel.Triangles + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.td.Slice(tt.args.i, tt.args.j); !reflect.DeepEqual(got, tt.want) { + t.Errorf("TrianglesData.Slice() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTrianglesData_Update(t *testing.T) { + type args struct { + t pixel.Triangles + } + tests := []struct { + name string + td *pixel.TrianglesData + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.Update(tt.args.t) + }) + } +} + +func TestTrianglesData_Copy(t *testing.T) { + tests := []struct { + name string + td *pixel.TrianglesData + want pixel.Triangles + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.td.Copy(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("TrianglesData.Copy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTrianglesData_Position(t *testing.T) { + type args struct { + i int + } + tests := []struct { + name string + td *pixel.TrianglesData + args args + want pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.td.Position(tt.args.i); !reflect.DeepEqual(got, tt.want) { + t.Errorf("TrianglesData.Position() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTrianglesData_Color(t *testing.T) { + type args struct { + i int + } + tests := []struct { + name string + td *pixel.TrianglesData + args args + want pixel.RGBA + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.td.Color(tt.args.i); !reflect.DeepEqual(got, tt.want) { + t.Errorf("TrianglesData.Color() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTrianglesData_Picture(t *testing.T) { + type args struct { + i int + } + tests := []struct { + name string + td *pixel.TrianglesData + args args + wantPic pixel.Vec + wantIntensity float64 + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotPic, gotIntensity := tt.td.Picture(tt.args.i) + if !reflect.DeepEqual(gotPic, tt.wantPic) { + t.Errorf("TrianglesData.Picture() gotPic = %v, want %v", gotPic, tt.wantPic) + } + if gotIntensity != tt.wantIntensity { + t.Errorf("TrianglesData.Picture() gotIntensity = %v, want %v", gotIntensity, tt.wantIntensity) + } + }) + } +} + +func TestMakePictureData(t *testing.T) { + type args struct { + rect pixel.Rect + } + tests := []struct { + name string + args args + want *pixel.PictureData + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MakePictureData(tt.args.rect); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MakePictureData() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPictureDataFromImage(t *testing.T) { + type args struct { + img image.Image + } + tests := []struct { + name string + args args + want *pixel.PictureData + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.PictureDataFromImage(tt.args.img); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PictureDataFromImage() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPictureDataFromPicture(t *testing.T) { + type args struct { + pic pixel.Picture + } + tests := []struct { + name string + args args + want *pixel.PictureData + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.PictureDataFromPicture(tt.args.pic); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PictureDataFromPicture() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPictureData_Image(t *testing.T) { + tests := []struct { + name string + pd *pixel.PictureData + want *image.RGBA + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.pd.Image(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PictureData.Image() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPictureData_Index(t *testing.T) { + type args struct { + at pixel.Vec + } + tests := []struct { + name string + pd *pixel.PictureData + args args + want int + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.pd.Index(tt.args.at); got != tt.want { + t.Errorf("PictureData.Index() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPictureData_Bounds(t *testing.T) { + tests := []struct { + name string + pd *pixel.PictureData + want pixel.Rect + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.pd.Bounds(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PictureData.Bounds() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPictureData_Color(t *testing.T) { + type args struct { + at pixel.Vec + } + tests := []struct { + name string + pd *pixel.PictureData + args args + want pixel.RGBA + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.pd.Color(tt.args.at); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PictureData.Color() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/drawer_test.go b/drawer_test.go index 233f510e..09a684de 100644 --- a/drawer_test.go +++ b/drawer_test.go @@ -16,3 +16,121 @@ func BenchmarkSpriteDrawBatch(b *testing.B) { sprite.Draw(batch, pixel.IM) } } + +func TestDrawer_Dirty(t *testing.T) { + tests := []struct { + name string + d *pixel.Drawer + }{ + { + name: "Drawer.Dirty", + d: &pixel.Drawer{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.d.Dirty() + }) + } +} + +type targetMock struct { + makeTrianglesCount int + makePictureCount int +} + +func (t *targetMock) MakePicture(_ pixel.Picture) pixel.TargetPicture { + t.makePictureCount++ + return targetPictureMock{} +} + +func (t *targetMock) MakeTriangles(_ pixel.Triangles) pixel.TargetTriangles { + t.makeTrianglesCount++ + return targetTrianglesMock{} +} + +type targetTrianglesMock struct{} + +func (targetTrianglesMock) Len() int { + return 0 +} + +func (targetTrianglesMock) SetLen(_ int) { + +} + +func (targetTrianglesMock) Slice(_, _ int) pixel.Triangles { + return nil +} + +func (targetTrianglesMock) Update(_ pixel.Triangles) { +} + +func (targetTrianglesMock) Copy() pixel.Triangles { + return nil +} + +func (targetTrianglesMock) Draw() { +} + +type targetPictureMock struct{} + +func (targetPictureMock) Bounds() pixel.Rect { + return pixel.R(0, 0, 0, 0) +} + +func (targetPictureMock) Draw(_ pixel.TargetTriangles) { + +} + +func TestDrawer_Draw(t *testing.T) { + type args struct { + t pixel.Target + } + tests := []struct { + name string + d *pixel.Drawer + args args + wantPictureCount int + wantTriangleCount int + }{ + { + name: "Drawer.Draw: nil triangles", + d: &pixel.Drawer{}, + args: args{t: &targetMock{}}, + wantPictureCount: 0, + wantTriangleCount: 0, + }, + { + name: "Drawer.Draw: non-nil triangles", + d: &pixel.Drawer{Triangles: pixel.MakeTrianglesData(1)}, + args: args{t: &targetMock{}}, + wantPictureCount: 0, + wantTriangleCount: 1, + }, + { + name: "Drawer.Draw: non-nil picture", + d: &pixel.Drawer{ + Triangles: pixel.MakeTrianglesData(1), + Picture: pixel.MakePictureData(pixel.R(0, 0, 0, 0)), + }, + args: args{t: &targetMock{}}, + wantPictureCount: 1, + wantTriangleCount: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.d.Draw(tt.args.t) + + target := tt.args.t.(*targetMock) + + if tt.wantPictureCount != target.makePictureCount { + t.Fatalf("MakePicture not called. Expected %d, got: %d", tt.wantPictureCount, target.makePictureCount) + } + if tt.wantTriangleCount != target.makeTrianglesCount { + t.Fatalf("MakeTriangles not called. Expected %d, got: %d", tt.wantTriangleCount, target.makeTrianglesCount) + } + }) + } +} diff --git a/geometry_test.go b/geometry_test.go index e1c1a6f9..a944a198 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1,7 +1,6 @@ package pixel_test import ( - "fmt" "testing" "github.com/faiface/pixel" @@ -12,67 +11,42 @@ type rectTestTransform struct { f func(pixel.Rect) pixel.Rect } -func TestResizeRect(t *testing.T) { - - // rectangles - squareAroundOrigin := pixel.R(-10, -10, 10, 10) - squareAround2020 := pixel.R(10, 10, 30, 30) - rectangleAroundOrigin := pixel.R(-20, -10, 20, 10) - rectangleAround2020 := pixel.R(0, 10, 40, 30) - - // resize transformations - resizeByHalfAroundCenter := rectTestTransform{"by half around center", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(rect.Center(), rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundMin := rectTestTransform{"by half around Min", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(rect.Min, rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundMax := rectTestTransform{"by half around Max", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(rect.Max, rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundMiddleOfLeftSide := rectTestTransform{"by half around middle of left side", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(pixel.V(rect.Min.X, rect.Center().Y), rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundOrigin := rectTestTransform{"by half around the origin", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(pixel.ZV, rect.Size().Scaled(0.5)) - }} - - testCases := []struct { - input pixel.Rect - transform rectTestTransform - answer pixel.Rect +func TestClamp(t *testing.T) { + type args struct { + x float64 + min float64 + max float64 + } + tests := []struct { + name string + args args + want float64 }{ - {squareAroundOrigin, resizeByHalfAroundCenter, pixel.R(-5, -5, 5, 5)}, - {squareAround2020, resizeByHalfAroundCenter, pixel.R(15, 15, 25, 25)}, - {rectangleAroundOrigin, resizeByHalfAroundCenter, pixel.R(-10, -5, 10, 5)}, - {rectangleAround2020, resizeByHalfAroundCenter, pixel.R(10, 15, 30, 25)}, - - {squareAroundOrigin, resizeByHalfAroundMin, pixel.R(-10, -10, 0, 0)}, - {squareAround2020, resizeByHalfAroundMin, pixel.R(10, 10, 20, 20)}, - {rectangleAroundOrigin, resizeByHalfAroundMin, pixel.R(-20, -10, 0, 0)}, - {rectangleAround2020, resizeByHalfAroundMin, pixel.R(0, 10, 20, 20)}, - - {squareAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 10, 10)}, - {squareAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 30, 30)}, - {rectangleAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 20, 10)}, - {rectangleAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 40, 30)}, - - {squareAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-10, -5, 0, 5)}, - {squareAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(10, 15, 20, 25)}, - {rectangleAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-20, -5, 0, 5)}, - {rectangleAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(0, 15, 20, 25)}, - - {squareAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-5, -5, 5, 5)}, - {squareAround2020, resizeByHalfAroundOrigin, pixel.R(5, 5, 15, 15)}, - {rectangleAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-10, -5, 10, 5)}, - {rectangleAround2020, resizeByHalfAroundOrigin, pixel.R(0, 5, 20, 15)}, + { + name: "Clamp: x < min < max", + args: args{x: 1, min: 2, max: 3}, + want: 2, + }, + { + name: "Clamp: min < x < max", + args: args{x: 2, min: 1, max: 3}, + want: 2, + }, + { + name: "Clamp: min < max < x", + args: args{x: 3, min: 1, max: 2}, + want: 2, + }, + { + name: "Clamp: x > min > max", + args: args{x: 3, min: 2, max: 1}, + want: 1, + }, } - - for _, testCase := range testCases { - t.Run(fmt.Sprintf("Resize %v %s", testCase.input, testCase.transform.name), func(t *testing.T) { - testResult := testCase.transform.f(testCase.input) - if testResult != testCase.answer { - t.Errorf("Got: %v, wanted: %v\n", testResult, testCase.answer) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.Clamp(tt.args.x, tt.args.min, tt.args.max); got != tt.want { + t.Errorf("Clamp() = %v, want %v", got, tt.want) } }) } diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..96ab6ebd --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/faiface/pixel + +require ( + github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 + github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 + github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 // indirect + github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f + github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + github.com/pkg/errors v0.8.1 + golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..0860a70e --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= +github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= +github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk= +github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= +github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM= +github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a h1:2n5w2v3knlspzjJWyQPC0j88Mwvq0SZV0Jdws34GJwc= +github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a/go.mod h1:dvrdneKbyWbK2skTda0nM4B9zSlS2GZSnjX7itr/skQ= +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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM= +golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= diff --git a/matrix_test.go b/matrix_test.go index e8d0ec91..08db8479 100644 --- a/matrix_test.go +++ b/matrix_test.go @@ -2,6 +2,7 @@ package pixel_test import ( "math/rand" + "reflect" "testing" "github.com/faiface/pixel" @@ -61,3 +62,170 @@ func BenchmarkMatrix(b *testing.B) { } }) } + +func TestMatrix_String(t *testing.T) { + tests := []struct { + name string + m pixel.Matrix + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.String(); got != tt.want { + t.Errorf("Matrix.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatrix_Moved(t *testing.T) { + type args struct { + delta pixel.Vec + } + tests := []struct { + name string + m pixel.Matrix + args args + want pixel.Matrix + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Matrix.Moved() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatrix_ScaledXY(t *testing.T) { + type args struct { + around pixel.Vec + scale pixel.Vec + } + tests := []struct { + name string + m pixel.Matrix + args args + want pixel.Matrix + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.ScaledXY(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Matrix.ScaledXY() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatrix_Scaled(t *testing.T) { + type args struct { + around pixel.Vec + scale float64 + } + tests := []struct { + name string + m pixel.Matrix + args args + want pixel.Matrix + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Scaled(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Matrix.Scaled() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatrix_Rotated(t *testing.T) { + type args struct { + around pixel.Vec + angle float64 + } + tests := []struct { + name string + m pixel.Matrix + args args + want pixel.Matrix + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Rotated(tt.args.around, tt.args.angle); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Matrix.Rotated() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatrix_Chained(t *testing.T) { + type args struct { + next pixel.Matrix + } + tests := []struct { + name string + m pixel.Matrix + args args + want pixel.Matrix + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Chained(tt.args.next); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Matrix.Chained() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatrix_Project(t *testing.T) { + type args struct { + u pixel.Vec + } + tests := []struct { + name string + m pixel.Matrix + args args + want pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Project(tt.args.u); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Matrix.Project() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMatrix_Unproject(t *testing.T) { + type args struct { + u pixel.Vec + } + tests := []struct { + name string + m pixel.Matrix + args args + want pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.m.Unproject(tt.args.u); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Matrix.Unproject() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/rect_test.go b/rect_test.go new file mode 100644 index 00000000..87250536 --- /dev/null +++ b/rect_test.go @@ -0,0 +1,609 @@ +package pixel_test + +import ( + "fmt" + "reflect" + "testing" + + "github.com/faiface/pixel" +) + +var ( + squareAroundOrigin = pixel.R(-10, -10, 10, 10) + squareAround2020 = pixel.R(10, 10, 30, 30) + rectangleAroundOrigin = pixel.R(-20, -10, 20, 10) + rectangleAround2020 = pixel.R(0, 10, 40, 30) +) + +func TestR(t *testing.T) { + type args struct { + minX float64 + minY float64 + maxX float64 + maxY float64 + } + tests := []struct { + name string + args args + want pixel.Rect + }{ + { + name: "R(): square around origin", + args: args{minX: -10, minY: -10, maxX: 10, maxY: 10}, + want: squareAroundOrigin, + }, + { + name: "R(): square around 20 20", + args: args{minX: 10, minY: 10, maxX: 30, maxY: 30}, + want: squareAround2020, + }, + { + name: "R(): rectangle around origin", + args: args{minX: -20, minY: -10, maxX: 20, maxY: 10}, + want: rectangleAroundOrigin, + }, + { + name: "R(): square around 20 20", + args: args{minX: 0, minY: 10, maxX: 40, maxY: 30}, + want: rectangleAround2020, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.R(tt.args.minX, tt.args.minY, tt.args.maxX, tt.args.maxY); !reflect.DeepEqual(got, tt.want) { + t.Errorf("R() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_String(t *testing.T) { + tests := []struct { + name string + r pixel.Rect + want string + }{ + { + name: "Rect.String(): square around origin", + r: squareAroundOrigin, + want: "Rect(-10, -10, 10, 10)", + }, + { + name: "Rect.String(): square around 20 20", + r: squareAround2020, + want: "Rect(10, 10, 30, 30)", + }, + { + name: "Rect.String(): rectangle around origin", + r: rectangleAroundOrigin, + want: "Rect(-20, -10, 20, 10)", + }, + { + name: "Rect.String(): square around 20 20", + r: rectangleAround2020, + want: "Rect(0, 10, 40, 30)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.String(); got != tt.want { + t.Errorf("Rect.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Norm(t *testing.T) { + tests := []struct { + name string + r pixel.Rect + want pixel.Rect + }{ + { + name: "Rect.Norm(): square around origin", + r: squareAroundOrigin, + want: squareAroundOrigin, + }, + { + name: "Rect.Norm(): square around 20 20", + r: squareAround2020, + want: squareAround2020, + }, + { + name: "Rect.Norm(): rectangle around origin", + r: rectangleAroundOrigin, + want: rectangleAroundOrigin, + }, + { + name: "Rect.Norm(): square around 20 20", + r: rectangleAround2020, + want: rectangleAround2020, + }, + { + name: "Rect.Norm(): square around origin unnormalized", + r: pixel.Rect{Min: squareAroundOrigin.Max, Max: squareAroundOrigin.Min}, + want: squareAroundOrigin, + }, + { + name: "Rect.Norm(): square around 20 20 unnormalized", + r: pixel.Rect{Min: squareAround2020.Max, Max: squareAround2020.Min}, + want: squareAround2020, + }, + { + name: "Rect.Norm(): rectangle around origin unnormalized", + r: pixel.Rect{Min: rectangleAroundOrigin.Max, Max: rectangleAroundOrigin.Min}, + want: rectangleAroundOrigin, + }, + { + name: "Rect.Norm(): square around 20 20 unnormalized", + r: pixel.Rect{Min: rectangleAround2020.Max, Max: rectangleAround2020.Min}, + want: rectangleAround2020, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Norm(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Norm() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_W(t *testing.T) { + tests := []struct { + name string + r pixel.Rect + want float64 + }{ + { + name: "Rect.W(): square around origin", + r: squareAroundOrigin, + want: 20, + }, + { + name: "Rect.W(): square around 20 20", + r: squareAround2020, + want: 20, + }, + { + name: "Rect.W(): rectangle around origin", + r: rectangleAroundOrigin, + want: 40, + }, + { + name: "Rect.W(): square around 20 20", + r: rectangleAround2020, + want: 40, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.W(); got != tt.want { + t.Errorf("Rect.W() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_H(t *testing.T) { + tests := []struct { + name string + r pixel.Rect + want float64 + }{ + { + name: "Rect.H(): square around origin", + r: squareAroundOrigin, + want: 20, + }, + { + name: "Rect.H(): square around 20 20", + r: squareAround2020, + want: 20, + }, + { + name: "Rect.H(): rectangle around origin", + r: rectangleAroundOrigin, + want: 20, + }, + { + name: "Rect.H(): square around 20 20", + r: rectangleAround2020, + want: 20, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.H(); got != tt.want { + t.Errorf("Rect.H() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Size(t *testing.T) { + tests := []struct { + name string + r pixel.Rect + want pixel.Vec + }{ + { + name: "Rect.Size(): square around origin", + r: squareAroundOrigin, + want: pixel.V(20, 20), + }, + { + name: "Rect.Size(): square around 20 20", + r: squareAround2020, + want: pixel.V(20, 20), + }, + { + name: "Rect.Size(): rectangle around origin", + r: rectangleAroundOrigin, + want: pixel.V(40, 20), + }, + { + name: "Rect.Size(): square around 20 20", + r: rectangleAround2020, + want: pixel.V(40, 20), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Size(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Size() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Area(t *testing.T) { + tests := []struct { + name string + r pixel.Rect + want float64 + }{ + { + name: "Rect.Area(): square around origin", + r: squareAroundOrigin, + want: 400, + }, + { + name: "Rect.Area(): square around 20 20", + r: squareAround2020, + want: 400, + }, + { + name: "Rect.Area(): rectangle around origin", + r: rectangleAroundOrigin, + want: 800, + }, + { + name: "Rect.Area(): square around 20 20", + r: rectangleAround2020, + want: 800, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Area(); got != tt.want { + t.Errorf("Rect.Area() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Center(t *testing.T) { + tests := []struct { + name string + r pixel.Rect + want pixel.Vec + }{ + { + name: "Rect.Center(): square around origin", + r: squareAroundOrigin, + want: pixel.V(0, 0), + }, + { + name: "Rect.Center(): square around 20 20", + r: squareAround2020, + want: pixel.V(20, 20), + }, + { + name: "Rect.Center(): rectangle around origin", + r: rectangleAroundOrigin, + want: pixel.V(0, 0), + }, + { + name: "Rect.Center(): square around 20 20", + r: rectangleAround2020, + want: pixel.V(20, 20), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Center(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Center() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Moved(t *testing.T) { + positiveShift := pixel.V(10, 10) + negativeShift := pixel.V(-10, -10) + + type args struct { + delta pixel.Vec + } + tests := []struct { + name string + r pixel.Rect + args args + want pixel.Rect + }{ + { + name: "Rect.Moved(): positive shift", + r: squareAroundOrigin, + args: args{delta: positiveShift}, + want: pixel.R(0, 0, 20, 20), + }, + { + name: "Rect.Moved(): negative shift", + r: squareAroundOrigin, + args: args{delta: negativeShift}, + want: pixel.R(-20, -20, 0, 0), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Moved() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Resized(t *testing.T) { + // resize transformations + resizeByHalfAroundCenter := rectTestTransform{"by half around center", func(rect pixel.Rect) pixel.Rect { + return rect.Resized(rect.Center(), rect.Size().Scaled(0.5)) + }} + resizeByHalfAroundMin := rectTestTransform{"by half around Min", func(rect pixel.Rect) pixel.Rect { + return rect.Resized(rect.Min, rect.Size().Scaled(0.5)) + }} + resizeByHalfAroundMax := rectTestTransform{"by half around Max", func(rect pixel.Rect) pixel.Rect { + return rect.Resized(rect.Max, rect.Size().Scaled(0.5)) + }} + resizeByHalfAroundMiddleOfLeftSide := rectTestTransform{"by half around middle of left side", func(rect pixel.Rect) pixel.Rect { + return rect.Resized(pixel.V(rect.Min.X, rect.Center().Y), rect.Size().Scaled(0.5)) + }} + resizeByHalfAroundOrigin := rectTestTransform{"by half around the origin", func(rect pixel.Rect) pixel.Rect { + return rect.Resized(pixel.ZV, rect.Size().Scaled(0.5)) + }} + + testCases := []struct { + input pixel.Rect + transform rectTestTransform + answer pixel.Rect + }{ + {squareAroundOrigin, resizeByHalfAroundCenter, pixel.R(-5, -5, 5, 5)}, + {squareAround2020, resizeByHalfAroundCenter, pixel.R(15, 15, 25, 25)}, + {rectangleAroundOrigin, resizeByHalfAroundCenter, pixel.R(-10, -5, 10, 5)}, + {rectangleAround2020, resizeByHalfAroundCenter, pixel.R(10, 15, 30, 25)}, + + {squareAroundOrigin, resizeByHalfAroundMin, pixel.R(-10, -10, 0, 0)}, + {squareAround2020, resizeByHalfAroundMin, pixel.R(10, 10, 20, 20)}, + {rectangleAroundOrigin, resizeByHalfAroundMin, pixel.R(-20, -10, 0, 0)}, + {rectangleAround2020, resizeByHalfAroundMin, pixel.R(0, 10, 20, 20)}, + + {squareAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 10, 10)}, + {squareAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 30, 30)}, + {rectangleAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 20, 10)}, + {rectangleAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 40, 30)}, + + {squareAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-10, -5, 0, 5)}, + {squareAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(10, 15, 20, 25)}, + {rectangleAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-20, -5, 0, 5)}, + {rectangleAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(0, 15, 20, 25)}, + + {squareAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-5, -5, 5, 5)}, + {squareAround2020, resizeByHalfAroundOrigin, pixel.R(5, 5, 15, 15)}, + {rectangleAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-10, -5, 10, 5)}, + {rectangleAround2020, resizeByHalfAroundOrigin, pixel.R(0, 5, 20, 15)}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("Resize %v %s", testCase.input, testCase.transform.name), func(t *testing.T) { + testResult := testCase.transform.f(testCase.input) + if testResult != testCase.answer { + t.Errorf("Got: %v, wanted: %v\n", testResult, testCase.answer) + } + }) + } +} + +func TestRect_ResizedMin(t *testing.T) { + grow := pixel.V(5, 5) + shrink := pixel.V(-5, -5) + + type args struct { + size pixel.Vec + } + tests := []struct { + name string + r pixel.Rect + args args + want pixel.Rect + }{ + { + name: "Rect.ResizedMin(): square around origin - growing", + r: squareAroundOrigin, + args: args{size: grow}, + want: pixel.R(-10, -10, -5, -5), + }, + { + name: "Rect.ResizedMin(): square around 20 20 - growing", + r: squareAround2020, + args: args{size: grow}, + want: pixel.R(10, 10, 15, 15), + }, + { + name: "Rect.ResizedMin(): rectangle around origin - growing", + r: rectangleAroundOrigin, + args: args{size: grow}, + want: pixel.R(-20, -10, -15, -5), + }, + { + name: "Rect.ResizedMin(): square around 20 20 - growing", + r: rectangleAround2020, + args: args{size: grow}, + want: pixel.R(0, 10, 5, 15), + }, + { + name: "Rect.ResizedMin(): square around origin - growing", + r: squareAroundOrigin, + args: args{size: shrink}, + want: pixel.R(-10, -10, -15, -15), + }, + { + name: "Rect.ResizedMin(): square around 20 20 - growing", + r: squareAround2020, + args: args{size: shrink}, + want: pixel.R(10, 10, 5, 5), + }, + { + name: "Rect.ResizedMin(): rectangle around origin - growing", + r: rectangleAroundOrigin, + args: args{size: shrink}, + want: pixel.R(-20, -10, -25, -15), + }, + { + name: "Rect.ResizedMin(): square around 20 20 - growing", + r: rectangleAround2020, + args: args{size: shrink}, + want: pixel.R(0, 10, -5, 5), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.ResizedMin(tt.args.size); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.ResizedMin() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Contains(t *testing.T) { + type args struct { + u pixel.Vec + } + tests := []struct { + name string + r pixel.Rect + args args + want bool + }{ + { + name: "Rect.Contains(): square and contained vector", + r: squareAroundOrigin, + args: args{u: pixel.V(-5, 5)}, + want: true, + }, + { + name: "Rect.Contains(): square and seperate vector", + r: squareAroundOrigin, + args: args{u: pixel.V(50, 55)}, + want: false, + }, + { + name: "Rect.Contains(): square and overlapping vector", + r: squareAroundOrigin, + args: args{u: pixel.V(0, 50)}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Contains(tt.args.u); got != tt.want { + t.Errorf("Rect.Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Union(t *testing.T) { + type args struct { + s pixel.Rect + } + tests := []struct { + name string + r pixel.Rect + args args + want pixel.Rect + }{ + { + name: "Rect.Union(): seperate squares", + r: squareAroundOrigin, + args: args{s: squareAround2020}, + want: pixel.R(-10, -10, 30, 30), + }, + { + name: "Rect.Union(): overlapping squares", + r: squareAroundOrigin, + args: args{s: pixel.R(0, 0, 20, 20)}, + want: pixel.R(-10, -10, 20, 20), + }, + { + name: "Rect.Union(): square within a square", + r: squareAroundOrigin, + args: args{s: pixel.R(-5, -5, 5, 5)}, + want: squareAroundOrigin, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Union(tt.args.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Union() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRect_Intersect(t *testing.T) { + type args struct { + s pixel.Rect + } + tests := []struct { + name string + r pixel.Rect + args args + want pixel.Rect + }{ + { + name: "Rect.Union(): seperate squares", + r: squareAroundOrigin, + args: args{s: squareAround2020}, + want: pixel.R(0, 0, 0, 0), + }, + { + name: "Rect.Union(): overlapping squares", + r: squareAroundOrigin, + args: args{s: pixel.R(0, 0, 20, 20)}, + want: pixel.R(0, 0, 10, 10), + }, + { + name: "Rect.Union(): square within a square", + r: squareAroundOrigin, + args: args{s: pixel.R(-5, -5, 5, 5)}, + want: pixel.R(-5, -5, 5, 5), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.r.Intersect(tt.args.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Intersect() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/sprite_test.go b/sprite_test.go new file mode 100644 index 00000000..9f8b9e9b --- /dev/null +++ b/sprite_test.go @@ -0,0 +1,122 @@ +package pixel_test + +import ( + "image/color" + "reflect" + "testing" + + "github.com/faiface/pixel" +) + +func TestNewSprite(t *testing.T) { + type args struct { + pic pixel.Picture + frame pixel.Rect + } + tests := []struct { + name string + args args + want *pixel.Sprite + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.NewSprite(tt.args.pic, tt.args.frame); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewSprite() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSprite_Set(t *testing.T) { + type args struct { + pic pixel.Picture + frame pixel.Rect + } + tests := []struct { + name string + s *pixel.Sprite + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Set(tt.args.pic, tt.args.frame) + }) + } +} + +func TestSprite_Picture(t *testing.T) { + tests := []struct { + name string + s *pixel.Sprite + want pixel.Picture + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Picture(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Sprite.Picture() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSprite_Frame(t *testing.T) { + tests := []struct { + name string + s *pixel.Sprite + want pixel.Rect + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Frame(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Sprite.Frame() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSprite_Draw(t *testing.T) { + type args struct { + t pixel.Target + matrix pixel.Matrix + } + tests := []struct { + name string + s *pixel.Sprite + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Draw(tt.args.t, tt.args.matrix) + }) + } +} + +func TestSprite_DrawColorMask(t *testing.T) { + type args struct { + t pixel.Target + matrix pixel.Matrix + mask color.Color + } + tests := []struct { + name string + s *pixel.Sprite + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.DrawColorMask(tt.args.t, tt.args.matrix, tt.args.mask) + }) + } +} diff --git a/vec_test.go b/vec_test.go new file mode 100644 index 00000000..67275ccb --- /dev/null +++ b/vec_test.go @@ -0,0 +1,707 @@ +package pixel_test + +import ( + "math" + "reflect" + "testing" + + "github.com/faiface/pixel" +) + +// closeEnough is a test helper function to establish if vectors are "close enough". This is to resolve floating point +// errors, specifically when dealing with `math.Pi` +func closeEnough(u, v pixel.Vec) bool { + uX, uY := math.Round(u.X), math.Round(u.Y) + vX, vY := math.Round(v.X), math.Round(v.Y) + return uX == vX && uY == vY +} + +func TestV(t *testing.T) { + type args struct { + x float64 + y float64 + } + tests := []struct { + name string + args args + want pixel.Vec + }{ + { + name: "V(): both 0", + args: args{x: 0, y: 0}, + want: pixel.ZV, + }, + { + name: "V(): x < y", + args: args{x: 0, y: 10}, + want: pixel.Vec{X: 0, Y: 10}, + }, + { + name: "V(): x > y", + args: args{x: 10, y: 0}, + want: pixel.Vec{X: 10, Y: 0}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.V(tt.args.x, tt.args.y); !reflect.DeepEqual(got, tt.want) { + t.Errorf("V() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit(t *testing.T) { + type args struct { + angle float64 + } + tests := []struct { + name string + args args + want pixel.Vec + }{ + { + name: "Unit(): 0 radians", + args: args{angle: 0}, + want: pixel.V(1, 0), + }, + { + name: "Unit(): pi radians", + args: args{angle: math.Pi}, + want: pixel.V(-1, 0), + }, + { + name: "Unit(): 10 * pi radians", + args: args{angle: 10 * math.Pi}, + want: pixel.V(1, 0), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.Unit(tt.args.angle); !closeEnough(got, tt.want) { + t.Errorf("Unit() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_String(t *testing.T) { + tests := []struct { + name string + u pixel.Vec + want string + }{ + { + name: "Vec.String(): both 0", + u: pixel.Vec{X: 0, Y: 0}, + want: "Vec(0, 0)", + }, + { + name: "Vec.String(): x < y", + u: pixel.Vec{X: 0, Y: 10}, + want: "Vec(0, 10)", + }, + { + name: "Vec.String(): x > y", + u: pixel.Vec{X: 10, Y: 0}, + want: "Vec(10, 0)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.String(); got != tt.want { + t.Errorf("Vec.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_XY(t *testing.T) { + tests := []struct { + name string + u pixel.Vec + wantX float64 + wantY float64 + }{ + { + name: "Vec.XY(): both 0", + u: pixel.Vec{X: 0, Y: 0}, + wantX: 0, + wantY: 0, + }, + { + name: "Vec.XY(): x < y", + u: pixel.Vec{X: 0, Y: 10}, + wantX: 0, + wantY: 10, + }, + { + name: "Vec.XY(): x > y", + u: pixel.Vec{X: 10, Y: 0}, + wantX: 10, + wantY: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotX, gotY := tt.u.XY() + if gotX != tt.wantX { + t.Errorf("Vec.XY() gotX = %v, want %v", gotX, tt.wantX) + } + if gotY != tt.wantY { + t.Errorf("Vec.XY() gotY = %v, want %v", gotY, tt.wantY) + } + }) + } +} + +func TestVec_Add(t *testing.T) { + type args struct { + v pixel.Vec + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.Add(): positive vector", + u: pixel.V(0, 10), + args: args{v: pixel.V(10, 10)}, + want: pixel.V(10, 20), + }, + { + name: "Vec.Add(): zero vector", + u: pixel.V(0, 10), + args: args{v: pixel.ZV}, + want: pixel.V(0, 10), + }, + { + name: "Vec.Add(): negative vector", + u: pixel.V(0, 10), + args: args{v: pixel.V(-20, -30)}, + want: pixel.V(-20, -20), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Add(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.Add() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Sub(t *testing.T) { + type args struct { + v pixel.Vec + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.Sub(): positive vector", + u: pixel.V(0, 10), + args: args{v: pixel.V(10, 10)}, + want: pixel.V(-10, 0), + }, + { + name: "Vec.Sub(): zero vector", + u: pixel.V(0, 10), + args: args{v: pixel.ZV}, + want: pixel.V(0, 10), + }, + { + name: "Vec.Sub(): negative vector", + u: pixel.V(0, 10), + args: args{v: pixel.V(-20, -30)}, + want: pixel.V(20, 40), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Sub(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.Sub() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_To(t *testing.T) { + type args struct { + v pixel.Vec + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.To(): positive vector", + u: pixel.V(0, 10), + args: args{v: pixel.V(10, 10)}, + want: pixel.V(10, 0), + }, + { + name: "Vec.To(): zero vector", + u: pixel.V(0, 10), + args: args{v: pixel.ZV}, + want: pixel.V(0, -10), + }, + { + name: "Vec.To(): negative vector", + u: pixel.V(0, 10), + args: args{v: pixel.V(-20, -30)}, + want: pixel.V(-20, -40), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.To(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.To() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Scaled(t *testing.T) { + type args struct { + c float64 + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.Scaled(): positive scale", + u: pixel.V(0, 10), + args: args{c: 10}, + want: pixel.V(0, 100), + }, + { + name: "Vec.Scaled(): zero scale", + u: pixel.V(0, 10), + args: args{c: 0}, + want: pixel.ZV, + }, + { + name: "Vec.Scaled(): identity scale", + u: pixel.V(0, 10), + args: args{c: 1}, + want: pixel.V(0, 10), + }, + { + name: "Vec.Scaled(): negative scale", + u: pixel.V(0, 10), + args: args{c: -10}, + want: pixel.V(0, -100), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Scaled(tt.args.c); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.Scaled() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_ScaledXY(t *testing.T) { + type args struct { + v pixel.Vec + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.ScaledXY(): positive scale", + u: pixel.V(0, 10), + args: args{v: pixel.V(10, 20)}, + want: pixel.V(0, 200), + }, + { + name: "Vec.ScaledXY(): zero scale", + u: pixel.V(0, 10), + args: args{v: pixel.ZV}, + want: pixel.ZV, + }, + { + name: "Vec.ScaledXY(): identity scale", + u: pixel.V(0, 10), + args: args{v: pixel.V(1, 1)}, + want: pixel.V(0, 10), + }, + { + name: "Vec.ScaledXY(): negative scale", + u: pixel.V(0, 10), + args: args{v: pixel.V(-5, -10)}, + want: pixel.V(0, -100), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.ScaledXY(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.ScaledXY() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Len(t *testing.T) { + tests := []struct { + name string + u pixel.Vec + want float64 + }{ + { + name: "Vec.Len(): positive vector", + u: pixel.V(40, 30), + want: 50, + }, + { + name: "Vec.Len(): zero vector", + u: pixel.ZV, + want: 0, + }, + { + name: "Vec.Len(): negative vector", + u: pixel.V(-5, -12), + want: 13, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Len(); got != tt.want { + t.Errorf("Vec.Len() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Angle(t *testing.T) { + tests := []struct { + name string + u pixel.Vec + want float64 + }{ + { + name: "Vec.Angle(): positive vector", + u: pixel.V(0, 30), + want: math.Pi / 2, + }, + { + name: "Vec.Angle(): zero vector", + u: pixel.ZV, + want: 0, + }, + { + name: "Vec.Angle(): negative vector", + u: pixel.V(-5, -0), + want: math.Pi, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Angle(); got != tt.want { + t.Errorf("Vec.Angle() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Unit(t *testing.T) { + tests := []struct { + name string + u pixel.Vec + want pixel.Vec + }{ + { + name: "Vec.Unit(): positive vector", + u: pixel.V(0, 30), + want: pixel.V(0, 1), + }, + { + name: "Vec.Unit(): zero vector", + u: pixel.ZV, + want: pixel.V(1, 0), + }, + { + name: "Vec.Unit(): negative vector", + u: pixel.V(-5, 0), + want: pixel.V(-1, 0), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Unit(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.Unit() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Rotated(t *testing.T) { + type args struct { + angle float64 + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.Rotated(): partial rotation", + u: pixel.V(0, 1), + args: args{angle: math.Pi / 2}, + want: pixel.V(-1, 0), + }, + { + name: "Vec.Rotated(): full rotation", + u: pixel.V(0, 1), + args: args{angle: 2 * math.Pi}, + want: pixel.V(0, 1), + }, + { + name: "Vec.Rotated(): zero rotation", + u: pixel.V(0, 1), + args: args{angle: 0}, + want: pixel.V(0, 1), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Rotated(tt.args.angle); !closeEnough(got, tt.want) { + t.Errorf("Vec.Rotated() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Normal(t *testing.T) { + tests := []struct { + name string + u pixel.Vec + want pixel.Vec + }{ + { + name: "Vec.Normal(): positive vector", + u: pixel.V(0, 30), + want: pixel.V(-30, 0), + }, + { + name: "Vec.Normal(): zero vector", + u: pixel.ZV, + want: pixel.ZV, + }, + { + name: "Vec.Normal(): negative vector", + u: pixel.V(-5, 0), + want: pixel.V(0, -5), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Normal(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.Normal() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Dot(t *testing.T) { + type args struct { + v pixel.Vec + } + tests := []struct { + name string + u pixel.Vec + args args + want float64 + }{ + { + name: "Vec.Dot(): positive vector", + u: pixel.V(0, 30), + args: args{v: pixel.V(10, 10)}, + want: 300, + }, + { + name: "Vec.Dot(): zero vector", + u: pixel.ZV, + args: args{v: pixel.V(10, 10)}, + want: 0, + }, + { + name: "Vec.Dot(): negative vector", + u: pixel.V(-5, 1), + args: args{v: pixel.V(10, 10)}, + want: -40, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Dot(tt.args.v); got != tt.want { + t.Errorf("Vec.Dot() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Cross(t *testing.T) { + type args struct { + v pixel.Vec + } + tests := []struct { + name string + u pixel.Vec + args args + want float64 + }{ + { + name: "Vec.Cross(): positive vector", + u: pixel.V(0, 30), + args: args{v: pixel.V(10, 10)}, + want: -300, + }, + { + name: "Vec.Cross(): zero vector", + u: pixel.ZV, + args: args{v: pixel.V(10, 10)}, + want: 0, + }, + { + name: "Vec.Cross(): negative vector", + u: pixel.V(-5, 1), + args: args{v: pixel.V(10, 10)}, + want: -60, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Cross(tt.args.v); got != tt.want { + t.Errorf("Vec.Cross() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Project(t *testing.T) { + type args struct { + v pixel.Vec + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.Project(): positive vector", + u: pixel.V(0, 30), + args: args{v: pixel.V(10, 10)}, + want: pixel.V(15, 15), + }, + { + name: "Vec.Project(): zero vector", + u: pixel.ZV, + args: args{v: pixel.V(10, 10)}, + want: pixel.ZV, + }, + { + name: "Vec.Project(): negative vector", + u: pixel.V(-30, 0), + args: args{v: pixel.V(10, 10)}, + want: pixel.V(-15, -15), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Project(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.Project() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestVec_Map(t *testing.T) { + type args struct { + f func(float64) float64 + } + tests := []struct { + name string + u pixel.Vec + args args + want pixel.Vec + }{ + { + name: "Vec.Map(): positive vector", + u: pixel.V(0, 25), + args: args{f: math.Sqrt}, + want: pixel.V(0, 5), + }, + { + name: "Vec.Map(): zero vector", + u: pixel.ZV, + args: args{f: math.Sqrt}, + want: pixel.ZV, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.u.Map(tt.args.f); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Vec.Map() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLerp(t *testing.T) { + type args struct { + a pixel.Vec + b pixel.Vec + t float64 + } + tests := []struct { + name string + args args + want pixel.Vec + }{ + { + name: "Lerp(): t = 0", + args: args{a: pixel.V(10, 10), b: pixel.ZV, t: 0}, + want: pixel.V(10, 10), + }, + { + name: "Lerp(): t = 1/4", + args: args{a: pixel.V(10, 10), b: pixel.ZV, t: .25}, + want: pixel.V(7.5, 7.5), + }, + { + name: "Lerp(): t = 1/2", + args: args{a: pixel.V(10, 10), b: pixel.ZV, t: .5}, + want: pixel.V(5, 5), + }, + { + name: "Lerp(): t = 1", + args: args{a: pixel.V(10, 10), b: pixel.ZV, t: 1}, + want: pixel.ZV, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.Lerp(tt.args.a, tt.args.b, tt.args.t); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Lerp() = %v, want %v", got, tt.want) + } + }) + } +} From 4c817d7c20f95a89a22f15566e9b8c8009bb0133 Mon Sep 17 00:00:00 2001 From: Stephen Michaelis Date: Sat, 19 Jan 2019 10:24:01 -0500 Subject: [PATCH 008/156] adding mouse operations --- pixelgl/input.go | 30 ++++++++++++++++++++++++++++++ pixelgl/window.go | 1 + 2 files changed, 31 insertions(+) diff --git a/pixelgl/input.go b/pixelgl/input.go index a2ef8087..01eeb624 100644 --- a/pixelgl/input.go +++ b/pixelgl/input.go @@ -33,6 +33,32 @@ func (w *Window) MousePosition() pixel.Vec { return w.currInp.mouse } +// MousePreviousPosition returns the previous mouse position in the Window's Bounds. +func (w *Window) MousePreviousPosition() pixel.Vec { + return w.prevInp.mouse +} + +// SetMousePosition positions the mouse cursor anywhere within the Window's Bounds. +func (w *Window) SetMousePosition(v pixel.Vec) { + mainthread.Call(func() { + if (v.X >= 0 && v.X <= w.bounds.W()) && + (v.Y >= 0 && v.Y <= w.bounds.H()) { + w.window.SetCursorPos( + v.X+w.bounds.Min.X, + (w.bounds.H()-v.Y)+w.bounds.Min.Y, + ) + w.prevInp.mouse = v + w.currInp.mouse = v + w.tempInp.mouse = v + } + }) +} + +// MouseEntered returns true if the mouse position is within the Window's Bounds. +func (w *Window) MouseEntered() bool { + return w.cursorEntered +} + // MouseScroll returns the mouse scroll amount (in both axes) since the last call to Window.Update. func (w *Window) MouseScroll() pixel.Vec { return w.currInp.scroll @@ -354,6 +380,10 @@ func (w *Window) initInput() { } }) + w.window.SetCursorEnterCallback(func(_ *glfw.Window, entered bool) { + w.cursorEntered = entered + }) + w.window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { w.tempInp.mouse = pixel.V( x+w.bounds.Min.X, diff --git a/pixelgl/window.go b/pixelgl/window.go index c124152b..7fb4dda2 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -59,6 +59,7 @@ type Window struct { canvas *Canvas vsync bool cursorVisible bool + cursorEntered bool // need to save these to correctly restore a fullscreen window restore struct { From 2b79131104d904de1d7392c8168ee206c132fe15 Mon Sep 17 00:00:00 2001 From: Stephen Michaelis Date: Sat, 19 Jan 2019 13:59:38 -0500 Subject: [PATCH 009/156] added go mod files --- go.mod | 15 +++++++++++++++ go.sum | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..ffb933ad --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/StephenMichaelis/pixel + +go 1.12 + +require ( + github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 + github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 + github.com/faiface/pixel v0.8.0 + github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 // indirect + github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f + github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + github.com/pkg/errors v0.8.1 + golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..e49b3702 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= +github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= +github.com/faiface/pixel v0.8.0 h1:phOHW6ixfMAKRamjnvhI6FFI2VRyPEq7+LmmkDGXB/4= +github.com/faiface/pixel v0.8.0/go.mod h1:CEUU/s9E82Kqp01Boj1O67KnBskqiLghANqvUJGgDAM= +github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk= +github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= +github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM= +github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a h1:2n5w2v3knlspzjJWyQPC0j88Mwvq0SZV0Jdws34GJwc= +github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a/go.mod h1:dvrdneKbyWbK2skTda0nM4B9zSlS2GZSnjX7itr/skQ= +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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 h1:FNSSV4jv1PrPsiM2iKGpqLPPgYACqh9Muav7Pollk1k= +golang.org/x/image v0.0.0-20190118043309-183bebdce1b2/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= From ee54171247d3217e039e3470a69f923d19b606c4 Mon Sep 17 00:00:00 2001 From: Stephen Michaelis Date: Sat, 19 Jan 2019 14:39:29 -0500 Subject: [PATCH 010/156] Revert to 4c817d7 --- go.mod | 15 --------------- go.sum | 18 ------------------ 2 files changed, 33 deletions(-) delete mode 100644 go.mod delete mode 100644 go.sum diff --git a/go.mod b/go.mod deleted file mode 100644 index ffb933ad..00000000 --- a/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module github.com/StephenMichaelis/pixel - -go 1.12 - -require ( - github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 - github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 - github.com/faiface/pixel v0.8.0 - github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 // indirect - github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f - github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/pkg/errors v0.8.1 - golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index e49b3702..00000000 --- a/go.sum +++ /dev/null @@ -1,18 +0,0 @@ -github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= -github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= -github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= -github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= -github.com/faiface/pixel v0.8.0 h1:phOHW6ixfMAKRamjnvhI6FFI2VRyPEq7+LmmkDGXB/4= -github.com/faiface/pixel v0.8.0/go.mod h1:CEUU/s9E82Kqp01Boj1O67KnBskqiLghANqvUJGgDAM= -github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk= -github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= -github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM= -github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a h1:2n5w2v3knlspzjJWyQPC0j88Mwvq0SZV0Jdws34GJwc= -github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a/go.mod h1:dvrdneKbyWbK2skTda0nM4B9zSlS2GZSnjX7itr/skQ= -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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 h1:FNSSV4jv1PrPsiM2iKGpqLPPgYACqh9Muav7Pollk1k= -golang.org/x/image v0.0.0-20190118043309-183bebdce1b2/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= From d3f6331240c49ca3701505c6d86314f5b8fc3412 Mon Sep 17 00:00:00 2001 From: Stephen Michaelis Date: Sun, 20 Jan 2019 09:56:30 -0500 Subject: [PATCH 011/156] Adding a pixel.Vec Floor method to geometry.go --- geometry.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/geometry.go b/geometry.go index 5f16ba45..6642b661 100644 --- a/geometry.go +++ b/geometry.go @@ -84,6 +84,14 @@ func (u Vec) Sub(v Vec) Vec { } } +// Floor returns converts x and y to their integer equivalents. +func (u Vec) Floor(v Vec) Vec { + return Vec{ + math.Floor(u.X), + math.Floor(u.Y), + } +} + // To returns the vector from u to v. Equivalent to v.Sub(u). func (u Vec) To(v Vec) Vec { return Vec{ From e7a625f5d29eb22dbaebb31dacf672e13f45fd9f Mon Sep 17 00:00:00 2001 From: Stephen Michaelis Date: Sun, 20 Jan 2019 10:00:35 -0500 Subject: [PATCH 012/156] updated Floor method --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 6642b661..05438422 100644 --- a/geometry.go +++ b/geometry.go @@ -85,7 +85,7 @@ func (u Vec) Sub(v Vec) Vec { } // Floor returns converts x and y to their integer equivalents. -func (u Vec) Floor(v Vec) Vec { +func (u Vec) Floor() Vec { return Vec{ math.Floor(u.X), math.Floor(u.Y), From 8d20deea05e465254cef2122b467b0cc8248be3e Mon Sep 17 00:00:00 2001 From: Stephen Michaelis Date: Mon, 21 Jan 2019 17:13:51 -0500 Subject: [PATCH 013/156] update MouseEntered to MouseInsideWindow --- geometry.go | 2 +- pixelgl/input.go | 6 +++--- pixelgl/window.go | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/geometry.go b/geometry.go index 05438422..72d148e0 100644 --- a/geometry.go +++ b/geometry.go @@ -84,7 +84,7 @@ func (u Vec) Sub(v Vec) Vec { } } -// Floor returns converts x and y to their integer equivalents. +// Floor converts x and y to their integer equivalents. func (u Vec) Floor() Vec { return Vec{ math.Floor(u.X), diff --git a/pixelgl/input.go b/pixelgl/input.go index 01eeb624..5cf2d9aa 100644 --- a/pixelgl/input.go +++ b/pixelgl/input.go @@ -55,8 +55,8 @@ func (w *Window) SetMousePosition(v pixel.Vec) { } // MouseEntered returns true if the mouse position is within the Window's Bounds. -func (w *Window) MouseEntered() bool { - return w.cursorEntered +func (w *Window) MouseInsideWindow() bool { + return w.cursorInsideWindow } // MouseScroll returns the mouse scroll amount (in both axes) since the last call to Window.Update. @@ -381,7 +381,7 @@ func (w *Window) initInput() { }) w.window.SetCursorEnterCallback(func(_ *glfw.Window, entered bool) { - w.cursorEntered = entered + w.cursorInsideWindow = entered }) w.window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { diff --git a/pixelgl/window.go b/pixelgl/window.go index 7fb4dda2..8e652776 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -55,11 +55,11 @@ type WindowConfig struct { type Window struct { window *glfw.Window - bounds pixel.Rect - canvas *Canvas - vsync bool - cursorVisible bool - cursorEntered bool + bounds pixel.Rect + canvas *Canvas + vsync bool + cursorVisible bool + cursorInsideWindow bool // need to save these to correctly restore a fullscreen window restore struct { From a46f6f6cf41c5ca62b488e7e7c2dd49ab6da276e Mon Sep 17 00:00:00 2001 From: Humphrey Shotton Date: Tue, 22 Jan 2019 21:37:15 +0000 Subject: [PATCH 014/156] Correctly index joysticks in internal state --- pixelgl/joystick.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pixelgl/joystick.go b/pixelgl/joystick.go index 3cdec27b..70bcf510 100644 --- a/pixelgl/joystick.go +++ b/pixelgl/joystick.go @@ -25,6 +25,8 @@ const ( Joystick14 = Joystick(glfw.Joystick14) Joystick15 = Joystick(glfw.Joystick15) Joystick16 = Joystick(glfw.Joystick16) + + JoystickLast = Joystick(glfw.JoystickLast) ) // JoystickPresent returns if the joystick is currently connected. @@ -90,7 +92,7 @@ func (w *Window) JoystickAxis(js Joystick, axis int) float64 { // Used internally during Window.UpdateInput to update the state of the joysticks. func (w *Window) updateJoystickInput() { - for js := Joystick1; js < Joystick16; js++ { + for js := Joystick1; js <= Joystick16; js++ { // Determine and store if the joystick was connected joystickPresent := glfw.JoystickPresent(glfw.Joystick(js)) w.tempJoy.connected[js] = joystickPresent @@ -118,10 +120,10 @@ func (w *Window) updateJoystickInput() { } type joystickState struct { - connected [Joystick16]bool - name [Joystick16]string - buttons [Joystick16][]byte - axis [Joystick16][]float32 + connected [Joystick16 + 1]bool + name [Joystick16 + 1]string + buttons [Joystick16 + 1][]byte + axis [Joystick16 + 1][]float32 } // Returns if a button on a joystick is down, returning false if the button or joystick is invalid. From 3be890ea80ead3afbce6f9d029e05c54c74f0a6d Mon Sep 17 00:00:00 2001 From: Humphrey Shotton Date: Tue, 22 Jan 2019 21:48:24 +0000 Subject: [PATCH 015/156] Use JoystickLast instead of Joystick16 --- pixelgl/joystick.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pixelgl/joystick.go b/pixelgl/joystick.go index 70bcf510..cfedd351 100644 --- a/pixelgl/joystick.go +++ b/pixelgl/joystick.go @@ -92,7 +92,7 @@ func (w *Window) JoystickAxis(js Joystick, axis int) float64 { // Used internally during Window.UpdateInput to update the state of the joysticks. func (w *Window) updateJoystickInput() { - for js := Joystick1; js <= Joystick16; js++ { + for js := Joystick1; js <= JoystickLast; js++ { // Determine and store if the joystick was connected joystickPresent := glfw.JoystickPresent(glfw.Joystick(js)) w.tempJoy.connected[js] = joystickPresent @@ -120,10 +120,10 @@ func (w *Window) updateJoystickInput() { } type joystickState struct { - connected [Joystick16 + 1]bool - name [Joystick16 + 1]string - buttons [Joystick16 + 1][]byte - axis [Joystick16 + 1][]float32 + connected [JoystickLast + 1]bool + name [JoystickLast + 1]string + buttons [JoystickLast + 1][]byte + axis [JoystickLast + 1][]float32 } // Returns if a button on a joystick is down, returning false if the button or joystick is invalid. From d102169151d5522ba9dc176531dad6839fff2091 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 28 Jan 2019 09:00:24 +0000 Subject: [PATCH 016/156] Added Circle geometry and tests --- geometry.go | 119 ++++++++++++++ geometry_test.go | 398 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 517 insertions(+) diff --git a/geometry.go b/geometry.go index 5f16ba45..591a7942 100644 --- a/geometry.go +++ b/geometry.go @@ -310,6 +310,125 @@ func (r Rect) Intersect(s Rect) Rect { return t } +// Circle is a 2D circle. It is defined by two properties: +// - Radius float64 +// - Center vector +type Circle struct { + Radius float64 + Center Vec +} + +// C returns a new Circle with the given radius and center coordinates. +// +// Note that a negative radius is valid. +func C(radius float64, center Vec) Circle { + return Circle{ + Radius: radius, + Center: center, + } +} + +// String returns the string representation of the Circle. +// +// c := pixel.C(10.1234, pixel.ZV) +// c.String() // returns "Circle(10.12, Vec(0, 0))" +// fmt.Println(c) // Circle(10.12, Vec(0, 0)) +func (c Circle) String() string { + return fmt.Sprintf("Circle(%.2f, %s)", c.Radius, c.Center) +} + +// Norm returns the Circle in normalized form - this is that the radius is set to an absolute version. +// +// c := pixel.C(-10, pixel.ZV) +// c.Norm() // returns pixel.Circle{10, pixel.Vec{0, 0}} +func (c Circle) Norm() Circle { + return Circle{ + Radius: math.Abs(c.Radius), + Center: c.Center, + } +} + +// Diameter returns the diameter of the Circle. +func (c Circle) Diameter() float64 { + return c.Radius * 2 +} + +// Area returns the area of the Circle. +func (c Circle) Area() float64 { + return math.Pi * c.Diameter() +} + +// Moved returns the Circle moved by the given vector delta. +func (c Circle) Moved(delta Vec) Circle { + return Circle{ + Radius: c.Radius, + Center: c.Center.Add(delta), + } +} + +// Resized returns the Circle resized by the given delta. +// +// c := pixel.C(10, pixel.ZV) +// c.Resized(-5) // returns pixel.Circle{5, pixel.Vec{0, 0}} +// c.Resized(25) // returns pixel.Circle{35, pixel.Vec{0, 0}} +func (c Circle) Resized(radiusDelta float64) Circle { + return Circle{ + Radius: c.Radius + radiusDelta, + Center: c.Center, + } +} + +// Contains checks whether a vector `u` is contained within this Circle (including it's perimeter). +func (c Circle) Contains(u Vec) bool { + toCenter := c.Center.To(u) + return c.Radius >= toCenter.Len() +} + +// Union returns the minimal Circle which covers both `c` and `d`. +func (c Circle) Union(d Circle) Circle { + biggerC := c + smallerC := d + if c.Radius < d.Radius { + biggerC = d + smallerC = c + } + + // Get distance between centers + dist := c.Center.To(d.Center).Len() + + // If the bigger Circle encompasses the smaller one, we have the result + if dist+smallerC.Radius <= biggerC.Radius { + return biggerC + } + + // Calculate radius for encompassing Circle + r := (dist + biggerC.Radius + smallerC.Radius) / 2 + + // Calculate center for encompassing Circle + theta := .5 + (biggerC.Radius-smallerC.Radius)/(2*dist) + center := smallerC.Center.Scaled(1 - theta).Add(biggerC.Center.Scaled(theta)) + + return Circle{ + Radius: r, + Center: center, + } +} + +// Intersect returns the maximal Circle which is covered by both `c` and `d`. +// +// If `c` and `d` don't overlap, this function returns a zero-sized circle at the centerpoint between the two Circle's +// centers. +func (c Circle) Intersect(d Circle) Circle { + center := Lerp(c.Center, d.Center, 0.5) + + radius := math.Min(0, c.Center.To(d.Center).Len()-(c.Radius+d.Radius)) + + return Circle{ + Radius: math.Abs(radius), + Center: center, + } +} + // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such // as movement, scaling and rotations. // diff --git a/geometry_test.go b/geometry_test.go index e1c1a6f9..98a44721 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -2,6 +2,8 @@ package pixel_test import ( "fmt" + "math" + "reflect" "testing" "github.com/faiface/pixel" @@ -77,3 +79,399 @@ func TestResizeRect(t *testing.T) { }) } } + +func TestC(t *testing.T) { + type args struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "C(): positive radius", + args: args{radius: 10, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 10, Center: pixel.V(0, 0)}, + }, + { + name: "C(): zero radius", + args: args{radius: 0, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 0, Center: pixel.V(0, 0)}, + }, + { + name: "C(): negative radius", + args: args{radius: -5, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: -5, Center: pixel.V(0, 0)}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.C(tt.args.radius, tt.args.center); !reflect.DeepEqual(got, tt.want) { + t.Errorf("C() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_String(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "Circle.String(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: "Circle(10.00, Vec(0, 0))", + }, + { + name: "Circle.String(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: "Circle(0.00, Vec(0, 0))", + }, + { + name: "Circle.String(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: "Circle(-5.00, Vec(0, 0))", + }, + { + name: "Circle.String(): irrational radius", + fields: fields{radius: math.Pi, center: pixel.V(0, 0)}, + want: "Circle(3.14, Vec(0, 0))", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.String(); got != tt.want { + t.Errorf("Circle.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Norm(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want pixel.Circle + }{ + { + name: "Circle.Norm(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Norm(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 0, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Norm(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Norm(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Norm() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Diameter(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want float64 + }{ + { + name: "Circle.Diameter(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: 20, + }, + { + name: "Circle.Diameter(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: 0, + }, + { + name: "Circle.Diameter(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: -10, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Diameter(); got != tt.want { + t.Errorf("Circle.Diameter() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Area(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want float64 + }{ + { + name: "Circle.Area(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: 20 * math.Pi, + }, + { + name: "Circle.Area(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: 0, + }, + { + name: "Circle.Area(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: -10 * math.Pi, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Area(); got != tt.want { + t.Errorf("Circle.Area() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Moved(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + delta pixel.Vec + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Moved(): positive movement", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{delta: pixel.V(10, 20)}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 10, Y: 20}}, + }, + { + name: "Circle.Moved(): zero movement", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{delta: pixel.ZV}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Moved(): negative movement", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{delta: pixel.V(-5, -10)}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: -5, Y: -10}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Moved() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Resized(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + radiusDelta float64 + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Resized(): positive delta", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{radiusDelta: 5}, + want: pixel.Circle{Radius: 15, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Resized(): zero delta", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{radiusDelta: 0}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Resized(): negative delta", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{radiusDelta: -5}, + want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Resized(tt.args.radiusDelta); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Resized() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Contains(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + u pixel.Vec + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "Circle.Contains(): point on cicles' center", + fields: fields{radius: 10, center: pixel.ZV}, + args: args{u: pixel.ZV}, + want: true, + }, + { + name: "Circle.Contains(): point offcenter", + fields: fields{radius: 10, center: pixel.V(5, 0)}, + args: args{u: pixel.ZV}, + want: true, + }, + { + name: "Circle.Contains(): point on circumference", + fields: fields{radius: 10, center: pixel.V(10, 0)}, + args: args{u: pixel.ZV}, + want: true, + }, + { + name: "Circle.Contains(): point outside circle", + fields: fields{radius: 10, center: pixel.V(15, 0)}, + args: args{u: pixel.ZV}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Contains(tt.args.u); got != tt.want { + t.Errorf("Circle.Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Union(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + d pixel.Circle + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Union(): overlapping circles", + fields: fields{radius: 5, center: pixel.ZV}, + args: args{d: pixel.C(5, pixel.ZV)}, + want: pixel.C(5, pixel.ZV), + }, + { + name: "Circle.Union(): separate circles", + fields: fields{radius: 1, center: pixel.ZV}, + args: args{d: pixel.C(1, pixel.V(0, 2))}, + want: pixel.C(2, pixel.V(0, 1)), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Union(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Union() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Intersect(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + d pixel.Circle + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Intersect(): intersecting circles", + fields: fields{radius: 1, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(1, 0))}, + want: pixel.C(1, pixel.V(0.5, 0)), + }, + { + name: "Circle.Intersect(): non-intersecting circles", + fields: fields{radius: 1, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(3, 3))}, + want: pixel.C(0, pixel.V(1.5, 1.5)), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C( + tt.fields.radius, + tt.fields.center, + ) + if got := c.Intersect(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Intersect() = %v, want %v", got, tt.want) + } + }) + } +} From 20b05d9ec2dfe0e444bacf754aa7750a18aea696 Mon Sep 17 00:00:00 2001 From: faiface Date: Mon, 28 Jan 2019 21:54:37 +0100 Subject: [PATCH 017/156] add Vulkan support + strike-through GPU effects from the roadmap --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 01b56f57..27cfe9d5 100644 --- a/README.md +++ b/README.md @@ -104,8 +104,9 @@ Pixel is in development and still missing few critical features. Here're the mos - ~~Advanced window manipulation (cursor hiding, window icon, ...)~~ - Better support for Hi-DPI displays - Mobile (and perhaps HTML5?) backend -- More advanced graphical effects (e.g. blur) +- ~~More advanced graphical effects (e.g. blur)~~ (solved with the addition of GLSL effects) - Tests and benchmarks +- Vulkan support **Implementing these features will get us to the 1.0 release.** Contribute, so that it's as soon as possible! From 0d5ba92fbe8562a6dca5487cf194451eaa20349c Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 09:33:20 +0000 Subject: [PATCH 018/156] fixed circle.Intersect --- geometry.go | 51 +++++++++++++++++++++----- geometry_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 9 deletions(-) diff --git a/geometry.go b/geometry.go index 591a7942..46cfb7c4 100644 --- a/geometry.go +++ b/geometry.go @@ -337,7 +337,7 @@ func (c Circle) String() string { return fmt.Sprintf("Circle(%.2f, %s)", c.Radius, c.Center) } -// Norm returns the Circle in normalized form - this is that the radius is set to an absolute version. +// Norm returns the Circle in normalized form - this sets the radius to its absolute value. // // c := pixel.C(-10, pixel.ZV) // c.Norm() // returns pixel.Circle{10, pixel.Vec{0, 0}} @@ -366,7 +366,7 @@ func (c Circle) Moved(delta Vec) Circle { } } -// Resized returns the Circle resized by the given delta. +// Resized returns the Circle resized by the given delta. The Circles center is use as the anchor. // // c := pixel.C(10, pixel.ZV) // c.Resized(-5) // returns pixel.Circle{5, pixel.Vec{0, 0}} @@ -384,14 +384,26 @@ func (c Circle) Contains(u Vec) bool { return c.Radius >= toCenter.Len() } -// Union returns the minimal Circle which covers both `c` and `d`. -func (c Circle) Union(d Circle) Circle { - biggerC := c - smallerC := d +// MaxCircle will return the larger circle based on the radius. +func MaxCircle(c, d Circle) Circle { + if c.Radius < d.Radius { + return d + } + return c +} + +// MinCircle will return the smaller circle based on the radius. +func MinCircle(c, d Circle) Circle { if c.Radius < d.Radius { - biggerC = d - smallerC = c + return c } + return d +} + +// Union returns the minimal Circle which covers both `c` and `d`. +func (c Circle) Union(d Circle) Circle { + biggerC := MaxCircle(c, d) + smallerC := MinCircle(c, d) // Get distance between centers dist := c.Center.To(d.Center).Len() @@ -419,7 +431,28 @@ func (c Circle) Union(d Circle) Circle { // If `c` and `d` don't overlap, this function returns a zero-sized circle at the centerpoint between the two Circle's // centers. func (c Circle) Intersect(d Circle) Circle { - center := Lerp(c.Center, d.Center, 0.5) + // Check if one of the circles encompasses the other; if so, return that one + biggerC := MaxCircle(c, d) + smallerC := MinCircle(c, d) + + if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius { + return biggerC + } + + // Calculate the midpoint between the two radii + // Distance between centers + dist := c.Center.To(d.Center).Len() + // Difference between radii + diff := dist - (c.Radius + d.Radius) + // Distance from c.Center to the weighted midpoint + distToMidpoint := c.Radius + 0.5*diff + // Weighted midpoint + center := Lerp(c.Center, d.Center, distToMidpoint/dist) + + // No need to calculate radius if the circles do not overlap + if c.Center.To(d.Center).Len() >= c.Radius+d.Radius { + return C(0, center) + } radius := math.Min(0, c.Center.To(d.Center).Len()-(c.Radius+d.Radius)) diff --git a/geometry_test.go b/geometry_test.go index 98a44721..361ca355 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -462,6 +462,24 @@ func TestCircle_Intersect(t *testing.T) { args: args{d: pixel.C(1, pixel.V(3, 3))}, want: pixel.C(0, pixel.V(1.5, 1.5)), }, + { + name: "Circle.Intersect(): first circle encompassing second", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(3, 3))}, + want: pixel.C(10, pixel.V(0, 0)), + }, + { + name: "Circle.Intersect(): second circle encompassing first", + fields: fields{radius: 1, center: pixel.V(-1, -4)}, + args: args{d: pixel.C(10, pixel.V(0, 0))}, + want: pixel.C(10, pixel.V(0, 0)), + }, + { + name: "Circle.Intersect(): matching circles", + fields: fields{radius: 1, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(0, 0))}, + want: pixel.C(1, pixel.V(0, 0)), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -475,3 +493,79 @@ func TestCircle_Intersect(t *testing.T) { }) } } + +func TestMaxCircle(t *testing.T) { + bigCircle := pixel.C(10, pixel.ZV) + smallCircle := pixel.C(1, pixel.ZV) + + type args struct { + c pixel.Circle + d pixel.Circle + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "MaxCircle(): first bigger", + args: args{c: bigCircle, d: smallCircle}, + want: bigCircle, + }, + { + name: "MaxCircle(): first smaller", + args: args{c: smallCircle, d: bigCircle}, + want: bigCircle, + }, + { + name: "MaxCircle(): both same size", + args: args{c: smallCircle, d: smallCircle}, + want: smallCircle, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MaxCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MaxCircle() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMinCircle(t *testing.T) { + bigCircle := pixel.C(10, pixel.ZV) + smallCircle := pixel.C(1, pixel.ZV) + + type args struct { + c pixel.Circle + d pixel.Circle + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "MinCircle(): first bigger", + args: args{c: bigCircle, d: smallCircle}, + want: smallCircle, + }, + { + name: "MinCircle(): first smaller", + args: args{c: smallCircle, d: bigCircle}, + want: smallCircle, + }, + { + name: "MinCircle(): both same size", + args: args{c: smallCircle, d: smallCircle}, + want: smallCircle, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MinCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MinCircle() = %v, want %v", got, tt.want) + } + }) + } +} From a56f7fa42296b14f68b17bf6281e8ec5f0b26dd4 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 09:38:24 +0000 Subject: [PATCH 019/156] not exporting circle size comparisons --- geometry.go | 16 +++++----- geometry_test.go | 76 ------------------------------------------------ 2 files changed, 8 insertions(+), 84 deletions(-) diff --git a/geometry.go b/geometry.go index 46cfb7c4..02084d2e 100644 --- a/geometry.go +++ b/geometry.go @@ -384,16 +384,16 @@ func (c Circle) Contains(u Vec) bool { return c.Radius >= toCenter.Len() } -// MaxCircle will return the larger circle based on the radius. -func MaxCircle(c, d Circle) Circle { +// maxCircle will return the larger circle based on the radius. +func maxCircle(c, d Circle) Circle { if c.Radius < d.Radius { return d } return c } -// MinCircle will return the smaller circle based on the radius. -func MinCircle(c, d Circle) Circle { +// minCircle will return the smaller circle based on the radius. +func minCircle(c, d Circle) Circle { if c.Radius < d.Radius { return c } @@ -402,8 +402,8 @@ func MinCircle(c, d Circle) Circle { // Union returns the minimal Circle which covers both `c` and `d`. func (c Circle) Union(d Circle) Circle { - biggerC := MaxCircle(c, d) - smallerC := MinCircle(c, d) + biggerC := maxCircle(c, d) + smallerC := minCircle(c, d) // Get distance between centers dist := c.Center.To(d.Center).Len() @@ -432,8 +432,8 @@ func (c Circle) Union(d Circle) Circle { // centers. func (c Circle) Intersect(d Circle) Circle { // Check if one of the circles encompasses the other; if so, return that one - biggerC := MaxCircle(c, d) - smallerC := MinCircle(c, d) + biggerC := maxCircle(c, d) + smallerC := minCircle(c, d) if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius { return biggerC diff --git a/geometry_test.go b/geometry_test.go index 361ca355..fc6e3fb3 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -493,79 +493,3 @@ func TestCircle_Intersect(t *testing.T) { }) } } - -func TestMaxCircle(t *testing.T) { - bigCircle := pixel.C(10, pixel.ZV) - smallCircle := pixel.C(1, pixel.ZV) - - type args struct { - c pixel.Circle - d pixel.Circle - } - tests := []struct { - name string - args args - want pixel.Circle - }{ - { - name: "MaxCircle(): first bigger", - args: args{c: bigCircle, d: smallCircle}, - want: bigCircle, - }, - { - name: "MaxCircle(): first smaller", - args: args{c: smallCircle, d: bigCircle}, - want: bigCircle, - }, - { - name: "MaxCircle(): both same size", - args: args{c: smallCircle, d: smallCircle}, - want: smallCircle, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.MaxCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MaxCircle() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMinCircle(t *testing.T) { - bigCircle := pixel.C(10, pixel.ZV) - smallCircle := pixel.C(1, pixel.ZV) - - type args struct { - c pixel.Circle - d pixel.Circle - } - tests := []struct { - name string - args args - want pixel.Circle - }{ - { - name: "MinCircle(): first bigger", - args: args{c: bigCircle, d: smallCircle}, - want: smallCircle, - }, - { - name: "MinCircle(): first smaller", - args: args{c: smallCircle, d: bigCircle}, - want: smallCircle, - }, - { - name: "MinCircle(): both same size", - args: args{c: smallCircle, d: smallCircle}, - want: smallCircle, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.MinCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MinCircle() = %v, want %v", got, tt.want) - } - }) - } -} From 9b8d4c7461021338fe2ebedb11b9b1e095f0defe Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 09:39:44 +0000 Subject: [PATCH 020/156] moved rect test struct --- geometry_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index fc6e3fb3..06388376 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -9,12 +9,11 @@ import ( "github.com/faiface/pixel" ) -type rectTestTransform struct { - name string - f func(pixel.Rect) pixel.Rect -} - func TestResizeRect(t *testing.T) { + type rectTestTransform struct { + name string + f func(pixel.Rect) pixel.Rect + } // rectangles squareAroundOrigin := pixel.R(-10, -10, 10, 10) From fdc068e855bad330093cba1b3f9fb5391654adc5 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 09:40:44 +0000 Subject: [PATCH 021/156] renamed test function to match convention --- geometry_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry_test.go b/geometry_test.go index 06388376..2192c261 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -9,7 +9,7 @@ import ( "github.com/faiface/pixel" ) -func TestResizeRect(t *testing.T) { +func TestRect_Resize(t *testing.T) { type rectTestTransform struct { name string f func(pixel.Rect) pixel.Rect From 9a32601c6b02375da44867816545cd3f28991a0b Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:20:21 +0000 Subject: [PATCH 022/156] added rect-circle intersection functions --- geometry.go | 32 ++++++++++++++++++++++ geometry_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/geometry.go b/geometry.go index 02084d2e..98fd8c1e 100644 --- a/geometry.go +++ b/geometry.go @@ -310,6 +310,16 @@ func (r Rect) Intersect(s Rect) Rect { return t } +// IntersectsCircle returns whether the Circle and the Rect intersect. +// +// This function will return true if: +// - The Rect contains the Circle, partially or fully +// - The Circle contains the Rect, partially of fully +// - An edge of the Rect is a tangent to the Circle +func (r Rect) IntersectsCircle(c Circle) bool { + return c.IntersectsRect(r) +} + // Circle is a 2D circle. It is defined by two properties: // - Radius float64 // - Center vector @@ -462,6 +472,28 @@ func (c Circle) Intersect(d Circle) Circle { } } +// IntersectsRect returns whether the Circle and the Rect intersect. +// +// This function will return true if: +// - The Rect contains the Circle, partially or fully +// - The Circle contains the Rect, partially of fully +// - An edge of the Rect is a tangent to the Circle +func (c Circle) IntersectsRect(r Rect) bool { + // Checks if the c.Center is not in the diagonal quadrants of the rectangle + var grownR Rect + if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { + // 'grow' the Rect by c.Radius in each diagonal + grownR = Rect{ + Min: r.Min.Sub(V(c.Radius, c.Radius)), + Max: r.Max.Add(V(c.Radius, c.Radius)), + } + + return grownR.Contains(c.Center) + } + // The center is in the diagonal quadrants + return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius +} + // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such // as movement, scaling and rotations. // diff --git a/geometry_test.go b/geometry_test.go index 2192c261..37f42292 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -492,3 +492,73 @@ func TestCircle_Intersect(t *testing.T) { }) } } + +func TestRect_IntersectsCircle(t *testing.T) { + type fields struct { + Min pixel.Vec + Max pixel.Vec + } + type args struct { + c pixel.Circle + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "Rect.IntersectsCircle(): no overlap", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(50, 50))}, + want: false, + }, + { + name: "Rect.IntersectsCircle(): circle contains rect", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(10, pixel.V(5, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): rect contains circle", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(5, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps one corner", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(0, 0))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps two corner", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(11, pixel.V(0, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps one edge", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(0, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): edge is tangent", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(-1, 5))}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := pixel.Rect{ + Min: tt.fields.Min, + Max: tt.fields.Max, + } + if got := r.IntersectsCircle(tt.args.c); got != tt.want { + t.Errorf("Rect.IntersectsCircle() = %v, want %v", got, tt.want) + } + }) + } +} From 8efed1de5cfa511b0e055bd7208ab1e646f64893 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:23:55 +0000 Subject: [PATCH 023/156] Removed Diameter function --- geometry.go | 7 +------ geometry_test.go | 36 ------------------------------------ 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/geometry.go b/geometry.go index 98fd8c1e..7e179243 100644 --- a/geometry.go +++ b/geometry.go @@ -358,14 +358,9 @@ func (c Circle) Norm() Circle { } } -// Diameter returns the diameter of the Circle. -func (c Circle) Diameter() float64 { - return c.Radius * 2 -} - // Area returns the area of the Circle. func (c Circle) Area() float64 { - return math.Pi * c.Diameter() + return math.Pi * c.Radius * 2 } // Moved returns the Circle moved by the given vector delta. diff --git a/geometry_test.go b/geometry_test.go index 37f42292..3bebbbe4 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -191,42 +191,6 @@ func TestCircle_Norm(t *testing.T) { } } -func TestCircle_Diameter(t *testing.T) { - type fields struct { - radius float64 - center pixel.Vec - } - tests := []struct { - name string - fields fields - want float64 - }{ - { - name: "Circle.Diameter(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, - want: 20, - }, - { - name: "Circle.Diameter(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, - want: 0, - }, - { - name: "Circle.Diameter(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, - want: -10, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) - if got := c.Diameter(); got != tt.want { - t.Errorf("Circle.Diameter() = %v, want %v", got, tt.want) - } - }) - } -} - func TestCircle_Area(t *testing.T) { type fields struct { radius float64 From 56cd321ab883bed9867ff0acd9849a47cfbb4689 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:33:12 +0000 Subject: [PATCH 024/156] normalising before getting bigger/smaller --- geometry.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/geometry.go b/geometry.go index 7e179243..fd9b3c38 100644 --- a/geometry.go +++ b/geometry.go @@ -407,8 +407,8 @@ func minCircle(c, d Circle) Circle { // Union returns the minimal Circle which covers both `c` and `d`. func (c Circle) Union(d Circle) Circle { - biggerC := maxCircle(c, d) - smallerC := minCircle(c, d) + biggerC := maxCircle(c.Norm(), d.Norm()) + smallerC := minCircle(c.Norm(), d.Norm()) // Get distance between centers dist := c.Center.To(d.Center).Len() @@ -437,8 +437,8 @@ func (c Circle) Union(d Circle) Circle { // centers. func (c Circle) Intersect(d Circle) Circle { // Check if one of the circles encompasses the other; if so, return that one - biggerC := maxCircle(c, d) - smallerC := minCircle(c, d) + biggerC := maxCircle(c.Norm(), d.Norm()) + smallerC := minCircle(c.Norm(), d.Norm()) if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius { return biggerC From d0ceea684902c10ff3d19f880e8cc93072f7a92f Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:34:59 +0000 Subject: [PATCH 025/156] remove local var --- geometry.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/geometry.go b/geometry.go index fd9b3c38..9a4265ca 100644 --- a/geometry.go +++ b/geometry.go @@ -475,15 +475,12 @@ func (c Circle) Intersect(d Circle) Circle { // - An edge of the Rect is a tangent to the Circle func (c Circle) IntersectsRect(r Rect) bool { // Checks if the c.Center is not in the diagonal quadrants of the rectangle - var grownR Rect if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { // 'grow' the Rect by c.Radius in each diagonal - grownR = Rect{ + return Rect{ Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius)), - } - - return grownR.Contains(c.Center) + }.Contains(c.Center) } // The center is in the diagonal quadrants return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius From cff8697c97a47adbd11b1b082fa192a2d6010ed7 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:41:10 +0000 Subject: [PATCH 026/156] Made test clearer --- geometry_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 3bebbbe4..eb1d541b 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -496,9 +496,9 @@ func TestRect_IntersectsCircle(t *testing.T) { want: true, }, { - name: "Rect.IntersectsCircle(): circle overlaps two corner", + name: "Rect.IntersectsCircle(): circle overlaps two corners", fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, - args: args{c: pixel.C(11, pixel.V(0, 5))}, + args: args{c: pixel.C(6, pixel.V(0, 5))}, want: true, }, { From 4b6cf201f7e6c5ea99852c52eaf3fa1591fcdabb Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:45:00 +0000 Subject: [PATCH 027/156] using Lerp --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 9a4265ca..72485b98 100644 --- a/geometry.go +++ b/geometry.go @@ -423,7 +423,7 @@ func (c Circle) Union(d Circle) Circle { // Calculate center for encompassing Circle theta := .5 + (biggerC.Radius-smallerC.Radius)/(2*dist) - center := smallerC.Center.Scaled(1 - theta).Add(biggerC.Center.Scaled(theta)) + center := Lerp(smallerC.Center, biggerC.Center, theta) return Circle{ Radius: r, From 0cddb56114b9792d676c4ca46ed23be3c08210d4 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:47:49 +0000 Subject: [PATCH 028/156] corrected comment --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 72485b98..599c3682 100644 --- a/geometry.go +++ b/geometry.go @@ -476,7 +476,7 @@ func (c Circle) Intersect(d Circle) Circle { func (c Circle) IntersectsRect(r Rect) bool { // Checks if the c.Center is not in the diagonal quadrants of the rectangle if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { - // 'grow' the Rect by c.Radius in each diagonal + // 'grow' the Rect by c.Radius in each orthagonal return Rect{ Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius)), From 4eba5e37ae5eb17dfacb6a9ce95a223f0d011a09 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 12:18:35 +0000 Subject: [PATCH 029/156] made test param generation more consistant --- geometry_test.go | 96 ++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index eb1d541b..3738b865 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -91,18 +91,18 @@ func TestC(t *testing.T) { }{ { name: "C(): positive radius", - args: args{radius: 10, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 10, Center: pixel.V(0, 0)}, + args: args{radius: 10, center: pixel.ZV}, + want: pixel.Circle{Radius: 10, Center: pixel.ZV}, }, { name: "C(): zero radius", - args: args{radius: 0, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 0, Center: pixel.V(0, 0)}, + args: args{radius: 0, center: pixel.ZV}, + want: pixel.Circle{Radius: 0, Center: pixel.ZV}, }, { name: "C(): negative radius", - args: args{radius: -5, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: -5, Center: pixel.V(0, 0)}, + args: args{radius: -5, center: pixel.ZV}, + want: pixel.Circle{Radius: -5, Center: pixel.ZV}, }, } for _, tt := range tests { @@ -126,22 +126,22 @@ func TestCircle_String(t *testing.T) { }{ { name: "Circle.String(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, want: "Circle(10.00, Vec(0, 0))", }, { name: "Circle.String(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, + fields: fields{radius: 0, center: pixel.ZV}, want: "Circle(0.00, Vec(0, 0))", }, { name: "Circle.String(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, + fields: fields{radius: -5, center: pixel.ZV}, want: "Circle(-5.00, Vec(0, 0))", }, { name: "Circle.String(): irrational radius", - fields: fields{radius: math.Pi, center: pixel.V(0, 0)}, + fields: fields{radius: math.Pi, center: pixel.ZV}, want: "Circle(3.14, Vec(0, 0))", }, } @@ -167,18 +167,18 @@ func TestCircle_Norm(t *testing.T) { }{ { name: "Circle.Norm(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + fields: fields{radius: 10, center: pixel.ZV}, + want: pixel.C(10, pixel.ZV), }, { name: "Circle.Norm(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 0, Center: pixel.Vec{X: 0, Y: 0}}, + fields: fields{radius: 0, center: pixel.ZV}, + want: pixel.C(0, pixel.ZV), }, { name: "Circle.Norm(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + fields: fields{radius: -5, center: pixel.ZV}, + want: pixel.C(5, pixel.ZV), }, } for _, tt := range tests { @@ -203,17 +203,17 @@ func TestCircle_Area(t *testing.T) { }{ { name: "Circle.Area(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, want: 20 * math.Pi, }, { name: "Circle.Area(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, + fields: fields{radius: 0, center: pixel.ZV}, want: 0, }, { name: "Circle.Area(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, + fields: fields{radius: -5, center: pixel.ZV}, want: -10 * math.Pi, }, } @@ -243,21 +243,21 @@ func TestCircle_Moved(t *testing.T) { }{ { name: "Circle.Moved(): positive movement", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(10, 20)}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 10, Y: 20}}, + want: pixel.C(10, pixel.V(10, 20)), }, { name: "Circle.Moved(): zero movement", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.ZV}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(10, pixel.V(0, 0)), }, { name: "Circle.Moved(): negative movement", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(-5, -10)}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: -5, Y: -10}}, + want: pixel.C(10, pixel.V(-5, -10)), }, } for _, tt := range tests { @@ -286,21 +286,21 @@ func TestCircle_Resized(t *testing.T) { }{ { name: "Circle.Resized(): positive delta", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 5}, - want: pixel.Circle{Radius: 15, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(15, pixel.V(0, 0)), }, { name: "Circle.Resized(): zero delta", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 0}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(10, pixel.V(0, 0)), }, { name: "Circle.Resized(): negative delta", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: -5}, - want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(5, pixel.V(0, 0)), }, } for _, tt := range tests { @@ -415,33 +415,33 @@ func TestCircle_Intersect(t *testing.T) { }{ { name: "Circle.Intersect(): intersecting circles", - fields: fields{radius: 1, center: pixel.V(0, 0)}, + fields: fields{radius: 1, center: pixel.ZV}, args: args{d: pixel.C(1, pixel.V(1, 0))}, want: pixel.C(1, pixel.V(0.5, 0)), }, { name: "Circle.Intersect(): non-intersecting circles", - fields: fields{radius: 1, center: pixel.V(0, 0)}, + fields: fields{radius: 1, center: pixel.ZV}, args: args{d: pixel.C(1, pixel.V(3, 3))}, want: pixel.C(0, pixel.V(1.5, 1.5)), }, { name: "Circle.Intersect(): first circle encompassing second", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{d: pixel.C(1, pixel.V(3, 3))}, - want: pixel.C(10, pixel.V(0, 0)), + want: pixel.C(10, pixel.ZV), }, { name: "Circle.Intersect(): second circle encompassing first", fields: fields{radius: 1, center: pixel.V(-1, -4)}, - args: args{d: pixel.C(10, pixel.V(0, 0))}, - want: pixel.C(10, pixel.V(0, 0)), + args: args{d: pixel.C(10, pixel.ZV)}, + want: pixel.C(10, pixel.ZV), }, { name: "Circle.Intersect(): matching circles", - fields: fields{radius: 1, center: pixel.V(0, 0)}, - args: args{d: pixel.C(1, pixel.V(0, 0))}, - want: pixel.C(1, pixel.V(0, 0)), + fields: fields{radius: 1, center: pixel.ZV}, + args: args{d: pixel.C(1, pixel.ZV)}, + want: pixel.C(1, pixel.ZV), }, } for _, tt := range tests { @@ -473,43 +473,43 @@ func TestRect_IntersectsCircle(t *testing.T) { }{ { name: "Rect.IntersectsCircle(): no overlap", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(50, 50))}, want: false, }, { name: "Rect.IntersectsCircle(): circle contains rect", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(10, pixel.V(5, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): rect contains circle", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(5, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps one corner", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(0, 0))}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.ZV)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps two corners", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(6, pixel.V(0, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps one edge", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(0, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): edge is tangent", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(-1, 5))}, want: true, }, From c70d7575ce9e0b3b7bedf955823c2717c2e7326c Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 12:27:38 +0000 Subject: [PATCH 030/156] removed unneeded Min call --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 599c3682..70404bcc 100644 --- a/geometry.go +++ b/geometry.go @@ -459,7 +459,7 @@ func (c Circle) Intersect(d Circle) Circle { return C(0, center) } - radius := math.Min(0, c.Center.To(d.Center).Len()-(c.Radius+d.Radius)) + radius := c.Center.To(d.Center).Len() - (c.Radius + d.Radius) return Circle{ Radius: math.Abs(radius), From 2cce50832d2200d47aa046479ee531ce3406cff0 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 12:31:59 +0000 Subject: [PATCH 031/156] fixed intersect function --- geometry.go | 3 ++- geometry_test.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/geometry.go b/geometry.go index 70404bcc..ab7573ef 100644 --- a/geometry.go +++ b/geometry.go @@ -483,7 +483,8 @@ func (c Circle) IntersectsRect(r Rect) bool { }.Contains(c.Center) } // The center is in the diagonal quadrants - return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius + return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius || + c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius || c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such diff --git a/geometry_test.go b/geometry_test.go index 3738b865..0ea1f1c9 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -490,9 +490,15 @@ func TestRect_IntersectsCircle(t *testing.T) { want: true, }, { - name: "Rect.IntersectsCircle(): circle overlaps one corner", + name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.ZV)}, + args: args{c: pixel.C(1, pixel.V(-.5, -.5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps top-left corner", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(-.5, 10.5))}, want: true, }, { From ecd686769ccf90eba1100fcca6d467b24878a843 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 30 Jan 2019 08:37:27 +0000 Subject: [PATCH 032/156] swapped radius and center order --- geometry.go | 30 ++++++++--------- geometry_test.go | 88 ++++++++++++++++++++++++------------------------ 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/geometry.go b/geometry.go index ab7573ef..17c574b6 100644 --- a/geometry.go +++ b/geometry.go @@ -321,20 +321,20 @@ func (r Rect) IntersectsCircle(c Circle) bool { } // Circle is a 2D circle. It is defined by two properties: -// - Radius float64 // - Center vector +// - Radius float64 type Circle struct { - Radius float64 Center Vec + Radius float64 } // C returns a new Circle with the given radius and center coordinates. // // Note that a negative radius is valid. -func C(radius float64, center Vec) Circle { +func C(center Vec, radius float64) Circle { return Circle{ - Radius: radius, Center: center, + Radius: radius, } } @@ -344,17 +344,17 @@ func C(radius float64, center Vec) Circle { // c.String() // returns "Circle(10.12, Vec(0, 0))" // fmt.Println(c) // Circle(10.12, Vec(0, 0)) func (c Circle) String() string { - return fmt.Sprintf("Circle(%.2f, %s)", c.Radius, c.Center) + return fmt.Sprintf("Circle(%s, %.2f)", c.Center, c.Radius) } // Norm returns the Circle in normalized form - this sets the radius to its absolute value. // // c := pixel.C(-10, pixel.ZV) -// c.Norm() // returns pixel.Circle{10, pixel.Vec{0, 0}} +// c.Norm() // returns pixel.Circle{pixel.Vec{0, 0}, 10} func (c Circle) Norm() Circle { return Circle{ - Radius: math.Abs(c.Radius), Center: c.Center, + Radius: math.Abs(c.Radius), } } @@ -366,20 +366,20 @@ func (c Circle) Area() float64 { // Moved returns the Circle moved by the given vector delta. func (c Circle) Moved(delta Vec) Circle { return Circle{ - Radius: c.Radius, Center: c.Center.Add(delta), + Radius: c.Radius, } } // Resized returns the Circle resized by the given delta. The Circles center is use as the anchor. // -// c := pixel.C(10, pixel.ZV) -// c.Resized(-5) // returns pixel.Circle{5, pixel.Vec{0, 0}} -// c.Resized(25) // returns pixel.Circle{35, pixel.Vec{0, 0}} +// c := pixel.C(pixel.ZV, 10) +// c.Resized(-5) // returns pixel.Circle{pixel.Vec{0, 0}, 5} +// c.Resized(25) // returns pixel.Circle{pixel.Vec{0, 0}, 35} func (c Circle) Resized(radiusDelta float64) Circle { return Circle{ - Radius: c.Radius + radiusDelta, Center: c.Center, + Radius: c.Radius + radiusDelta, } } @@ -426,8 +426,8 @@ func (c Circle) Union(d Circle) Circle { center := Lerp(smallerC.Center, biggerC.Center, theta) return Circle{ - Radius: r, Center: center, + Radius: r, } } @@ -456,14 +456,14 @@ func (c Circle) Intersect(d Circle) Circle { // No need to calculate radius if the circles do not overlap if c.Center.To(d.Center).Len() >= c.Radius+d.Radius { - return C(0, center) + return C(center, 0) } radius := c.Center.To(d.Center).Len() - (c.Radius + d.Radius) return Circle{ - Radius: math.Abs(radius), Center: center, + Radius: math.Abs(radius), } } diff --git a/geometry_test.go b/geometry_test.go index 0ea1f1c9..eec854de 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -107,7 +107,7 @@ func TestC(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := pixel.C(tt.args.radius, tt.args.center); !reflect.DeepEqual(got, tt.want) { + if got := pixel.C(tt.args.center, tt.args.radius); !reflect.DeepEqual(got, tt.want) { t.Errorf("C() = %v, want %v", got, tt.want) } }) @@ -127,27 +127,27 @@ func TestCircle_String(t *testing.T) { { name: "Circle.String(): positive radius", fields: fields{radius: 10, center: pixel.ZV}, - want: "Circle(10.00, Vec(0, 0))", + want: "Circle(Vec(0, 0), 10.00)", }, { name: "Circle.String(): zero radius", fields: fields{radius: 0, center: pixel.ZV}, - want: "Circle(0.00, Vec(0, 0))", + want: "Circle(Vec(0, 0), 0.00)", }, { name: "Circle.String(): negative radius", fields: fields{radius: -5, center: pixel.ZV}, - want: "Circle(-5.00, Vec(0, 0))", + want: "Circle(Vec(0, 0), -5.00)", }, { name: "Circle.String(): irrational radius", fields: fields{radius: math.Pi, center: pixel.ZV}, - want: "Circle(3.14, Vec(0, 0))", + want: "Circle(Vec(0, 0), 3.14)", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.String(); got != tt.want { t.Errorf("Circle.String() = %v, want %v", got, tt.want) } @@ -168,22 +168,22 @@ func TestCircle_Norm(t *testing.T) { { name: "Circle.Norm(): positive radius", fields: fields{radius: 10, center: pixel.ZV}, - want: pixel.C(10, pixel.ZV), + want: pixel.C(pixel.ZV, 10), }, { name: "Circle.Norm(): zero radius", fields: fields{radius: 0, center: pixel.ZV}, - want: pixel.C(0, pixel.ZV), + want: pixel.C(pixel.ZV, 0), }, { name: "Circle.Norm(): negative radius", fields: fields{radius: -5, center: pixel.ZV}, - want: pixel.C(5, pixel.ZV), + want: pixel.C(pixel.ZV, 5), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Norm(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Norm() = %v, want %v", got, tt.want) } @@ -219,7 +219,7 @@ func TestCircle_Area(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Area(); got != tt.want { t.Errorf("Circle.Area() = %v, want %v", got, tt.want) } @@ -245,24 +245,24 @@ func TestCircle_Moved(t *testing.T) { name: "Circle.Moved(): positive movement", fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(10, 20)}, - want: pixel.C(10, pixel.V(10, 20)), + want: pixel.C(pixel.V(10, 20), 10), }, { name: "Circle.Moved(): zero movement", fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.ZV}, - want: pixel.C(10, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 10), }, { name: "Circle.Moved(): negative movement", fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(-5, -10)}, - want: pixel.C(10, pixel.V(-5, -10)), + want: pixel.C(pixel.V(-5, -10), 10), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Moved() = %v, want %v", got, tt.want) } @@ -288,24 +288,24 @@ func TestCircle_Resized(t *testing.T) { name: "Circle.Resized(): positive delta", fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 5}, - want: pixel.C(15, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 15), }, { name: "Circle.Resized(): zero delta", fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 0}, - want: pixel.C(10, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 10), }, { name: "Circle.Resized(): negative delta", fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: -5}, - want: pixel.C(5, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 5), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Resized(tt.args.radiusDelta); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Resized() = %v, want %v", got, tt.want) } @@ -354,7 +354,7 @@ func TestCircle_Contains(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Contains(tt.args.u); got != tt.want { t.Errorf("Circle.Contains() = %v, want %v", got, tt.want) } @@ -379,19 +379,19 @@ func TestCircle_Union(t *testing.T) { { name: "Circle.Union(): overlapping circles", fields: fields{radius: 5, center: pixel.ZV}, - args: args{d: pixel.C(5, pixel.ZV)}, - want: pixel.C(5, pixel.ZV), + args: args{d: pixel.C(pixel.ZV, 5)}, + want: pixel.C(pixel.ZV, 5), }, { name: "Circle.Union(): separate circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(0, 2))}, - want: pixel.C(2, pixel.V(0, 1)), + args: args{d: pixel.C(pixel.V(0, 2), 1)}, + want: pixel.C(pixel.V(0, 1), 2), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Union(tt.args.d); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Union() = %v, want %v", got, tt.want) } @@ -416,39 +416,39 @@ func TestCircle_Intersect(t *testing.T) { { name: "Circle.Intersect(): intersecting circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(1, 0))}, - want: pixel.C(1, pixel.V(0.5, 0)), + args: args{d: pixel.C(pixel.V(1, 0), 1)}, + want: pixel.C(pixel.V(0.5, 0), 1), }, { name: "Circle.Intersect(): non-intersecting circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(3, 3))}, - want: pixel.C(0, pixel.V(1.5, 1.5)), + args: args{d: pixel.C(pixel.V(3, 3), 1)}, + want: pixel.C(pixel.V(1.5, 1.5), 0), }, { name: "Circle.Intersect(): first circle encompassing second", fields: fields{radius: 10, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(3, 3))}, - want: pixel.C(10, pixel.ZV), + args: args{d: pixel.C(pixel.V(3, 3), 1)}, + want: pixel.C(pixel.ZV, 10), }, { name: "Circle.Intersect(): second circle encompassing first", fields: fields{radius: 1, center: pixel.V(-1, -4)}, - args: args{d: pixel.C(10, pixel.ZV)}, - want: pixel.C(10, pixel.ZV), + args: args{d: pixel.C(pixel.ZV, 10)}, + want: pixel.C(pixel.ZV, 10), }, { name: "Circle.Intersect(): matching circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.ZV)}, - want: pixel.C(1, pixel.ZV), + args: args{d: pixel.C(pixel.ZV, 1)}, + want: pixel.C(pixel.ZV, 1), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := pixel.C( - tt.fields.radius, tt.fields.center, + tt.fields.radius, ) if got := c.Intersect(tt.args.d); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Intersect() = %v, want %v", got, tt.want) @@ -474,49 +474,49 @@ func TestRect_IntersectsCircle(t *testing.T) { { name: "Rect.IntersectsCircle(): no overlap", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(50, 50))}, + args: args{c: pixel.C(pixel.V(50, 50), 1)}, want: false, }, { name: "Rect.IntersectsCircle(): circle contains rect", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(10, pixel.V(5, 5))}, + args: args{c: pixel.C(pixel.V(5, 5), 10)}, want: true, }, { name: "Rect.IntersectsCircle(): rect contains circle", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(5, 5))}, + args: args{c: pixel.C(pixel.V(5, 5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(-.5, -.5))}, + args: args{c: pixel.C(pixel.V(-.5, -.5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(-.5, 10.5))}, + args: args{c: pixel.C(pixel.V(-.5, 10.5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps two corners", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(6, pixel.V(0, 5))}, + args: args{c: pixel.C(pixel.V(0, 5), 6)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps one edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(0, 5))}, + args: args{c: pixel.C(pixel.V(0, 5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): edge is tangent", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(-1, 5))}, + args: args{c: pixel.C(pixel.V(-1, 5), 1)}, want: true, }, } From ba4f417559e58d44f9eaa09079c8228d0bf6d01e Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 30 Jan 2019 08:38:51 +0000 Subject: [PATCH 033/156] corrected area formula --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 17c574b6..7353f1af 100644 --- a/geometry.go +++ b/geometry.go @@ -360,7 +360,7 @@ func (c Circle) Norm() Circle { // Area returns the area of the Circle. func (c Circle) Area() float64 { - return math.Pi * c.Radius * 2 + return math.Pi * math.Pow(c.Radius, 2) } // Moved returns the Circle moved by the given vector delta. From 8650692efa621bc831694fa37a2589d33901ae21 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 31 Jan 2019 08:22:59 +0000 Subject: [PATCH 034/156] wip --- geometry.go | 84 ++++++++++++++++++++++++++++++++++++++++-------- geometry_test.go | 54 ++++++++++++++++++++++++------- 2 files changed, 112 insertions(+), 26 deletions(-) diff --git a/geometry.go b/geometry.go index 7353f1af..59373610 100644 --- a/geometry.go +++ b/geometry.go @@ -310,14 +310,15 @@ func (r Rect) Intersect(s Rect) Rect { return t } -// IntersectsCircle returns whether the Circle and the Rect intersect. +// IntersectsCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only +// the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -// - An edge of the Rect is a tangent to the Circle -func (r Rect) IntersectsCircle(c Circle) bool { - return c.IntersectsRect(r) +func (r Rect) IntersectsCircle(c Circle) Vec { + return c.IntersectsRect(r).Scaled(-1) } // Circle is a 2D circle. It is defined by two properties: @@ -467,24 +468,79 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectsRect returns whether the Circle and the Rect intersect. +// IntersectsRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only +// the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -// - An edge of the Rect is a tangent to the Circle -func (c Circle) IntersectsRect(r Rect) bool { +func (c Circle) IntersectsRect(r Rect) Vec { + // h and v will hold the minimum horizontal and vertical distances (respectively) to avoid overlapping + var h, v float64 + // Checks if the c.Center is not in the diagonal quadrants of the rectangle if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { // 'grow' the Rect by c.Radius in each orthagonal - return Rect{ - Min: r.Min.Sub(V(c.Radius, c.Radius)), - Max: r.Max.Add(V(c.Radius, c.Radius)), - }.Contains(c.Center) + grown := Rect{Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius))} + if !grown.Contains(c.Center) { + // c.Center not close enough to overlap, return zero-vector + return ZV + } + + // Get minimum distance to travel out of Rect + rToC := r.Center().To(c.Center) + h = c.Radius - math.Abs(rToC.X) + (r.W() / 2) + v = c.Radius - math.Abs(rToC.Y) + (r.H() / 2) + + if rToC.X < 0 { + h = -h + } + if rToC.Y < 0 { + v = -v + } + } else { + // The center is in the diagonal quadrants + if c.Center.To(r.Min).Len() <= c.Radius { + // Closest to bottom-left + cornerToCenter := r.Min.To(c.Center) + // Get the horizontal and vertical overlaps + h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)) + v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))) + } + if c.Center.To(r.Max).Len() <= c.Radius { + // Closest to top-right + cornerToCenter := r.Max.To(c.Center) + // Get the horizontal and vertical overlaps + h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)) + v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)) + } + if c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius { + // Closest to top-left + cornerToCenter := V(r.Min.X, r.Max.Y).To(c.Center) + // Get the horizontal and vertical overlaps + h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))) + v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)) + } + if c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius { + // Closest to bottom-right + cornerToCenter := V(r.Max.X, r.Min.Y).To(c.Center) + // Get the horizontal and vertical overlaps + h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))) + v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))) + } + } + + // No intersect + if h == 0 && v == 0 { + return ZV + } + + if math.Abs(h) > math.Abs(v) { + // Vertical distance shorter + return V(0, v) } - // The center is in the diagonal quadrants - return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius || - c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius || c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius + return V(h, 0) } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such diff --git a/geometry_test.go b/geometry_test.go index eec854de..2bdc2840 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -469,55 +469,85 @@ func TestRect_IntersectsCircle(t *testing.T) { name string fields fields args args - want bool + want pixel.Vec }{ { name: "Rect.IntersectsCircle(): no overlap", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(50, 50), 1)}, - want: false, + want: pixel.ZV, }, { name: "Rect.IntersectsCircle(): circle contains rect", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 10)}, - want: true, + want: pixel.V(-15, 0), }, { name: "Rect.IntersectsCircle(): rect contains circle", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 1)}, - want: true, + want: pixel.V(-6, 0), }, { name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(-.5, -.5), 1)}, - want: true, + args: args{c: pixel.C(pixel.V(0, 0), 1)}, + want: pixel.V(1, 0), }, { name: "Rect.IntersectsCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(-.5, 10.5), 1)}, - want: true, + args: args{c: pixel.C(pixel.V(0, 10), 1)}, + want: pixel.V(1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps bottom-right corner", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(10, 0), 1)}, + want: pixel.V(-1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps top-right corner", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(10, 10), 1)}, + want: pixel.V(-1, 0), }, { name: "Rect.IntersectsCircle(): circle overlaps two corners", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 6)}, - want: true, + want: pixel.V(6, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps one edge", + name: "Rect.IntersectsCircle(): circle overlaps left edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 1)}, - want: true, + want: pixel.V(1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps bottom edge", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, 0), 1)}, + want: pixel.V(0, 1), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps right edge", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(10, 5), 1)}, + want: pixel.V(-1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps top edge", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, 10), 1)}, + want: pixel.V(0, -1), }, { name: "Rect.IntersectsCircle(): edge is tangent", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(-1, 5), 1)}, - want: true, + want: pixel.ZV, }, } for _, tt := range tests { From cabaee680e66651f2a9a6e72996e9f21d47c274f Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Tue, 12 Feb 2019 14:57:03 +0100 Subject: [PATCH 035/156] #159 Fix unproject for rotated matrix --- geometry.go | 12 +++++------- geometry_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/geometry.go b/geometry.go index 72d148e0..621a0b43 100644 --- a/geometry.go +++ b/geometry.go @@ -401,13 +401,11 @@ func (m Matrix) Project(u Vec) Vec { // Unproject does the inverse operation to Project. // -// It turns out that multiplying a vector by the inverse matrix of m can be nearly-accomplished by -// subtracting the translate part of the matrix and multplying by the inverse of the top-left 2x2 -// matrix, and the inverse of a 2x2 matrix is simple enough to just be inlined in the computation. -// // Time complexity is O(1). func (m Matrix) Unproject(u Vec) Vec { - d := (m[0] * m[3]) - (m[1] * m[2]) - u.X, u.Y = (u.X-m[4])/d, (u.Y-m[5])/d - return Vec{u.X*m[3] - u.Y*m[1], u.Y*m[0] - u.X*m[2]} + det := m[0]*m[3] - m[2]*m[1] + return Vec{ + m[3]/det*u.X - m[2]/det*u.Y + m[2]*m[5] - m[3]*m[4], + -m[1]/det*u.X + m[0]/det*u.Y + m[1]*m[4] - m[0]*m[5], + } } diff --git a/geometry_test.go b/geometry_test.go index e1c1a6f9..f97b8cdf 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -2,6 +2,8 @@ package pixel_test import ( "fmt" + "github.com/stretchr/testify/assert" + "math" "testing" "github.com/faiface/pixel" @@ -77,3 +79,30 @@ func TestResizeRect(t *testing.T) { }) } } + +func TestMatrix_Unproject(t *testing.T) { + t.Run("for rotated matrix", func(t *testing.T) { + matrix := pixel.IM.Rotated(pixel.ZV, math.Pi/2) + unprojected := matrix.Unproject(pixel.V(0, 1)) + assert.InDelta(t, unprojected.X, 1, 0.01) + assert.InDelta(t, unprojected.Y, 0, 0.01) + }) + t.Run("for moved matrix", func(t *testing.T) { + matrix := pixel.IM.Moved(pixel.V(5, 5)) + unprojected := matrix.Unproject(pixel.V(0, 0)) + assert.InDelta(t, unprojected.X, -5, 0.01) + assert.InDelta(t, unprojected.Y, -5, 0.01) + }) + t.Run("for scaled matrix", func(t *testing.T) { + matrix := pixel.IM.Scaled(pixel.ZV, 2) + unprojected := matrix.Unproject(pixel.V(4, 4)) + assert.InDelta(t, unprojected.X, 2, 0.01) + assert.InDelta(t, unprojected.Y, 2, 0.01) + }) + t.Run("for singular matrix", func(t *testing.T) { + matrix := pixel.Matrix{0, 0, 0, 0, 0, 0} + unprojected := matrix.Unproject(pixel.ZV) + assert.True(t, math.IsNaN(unprojected.X)) + assert.True(t, math.IsNaN(unprojected.Y)) + }) +} From 3b366d1edd6c8827556c1ff08c25d89808415fc7 Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Tue, 12 Feb 2019 15:24:17 +0100 Subject: [PATCH 036/156] #159 Use more precise delta for floats comparision --- geometry_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index f97b8cdf..13f90e2f 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -81,23 +81,24 @@ func TestResizeRect(t *testing.T) { } func TestMatrix_Unproject(t *testing.T) { + const delta = 4e-16 t.Run("for rotated matrix", func(t *testing.T) { matrix := pixel.IM.Rotated(pixel.ZV, math.Pi/2) unprojected := matrix.Unproject(pixel.V(0, 1)) - assert.InDelta(t, unprojected.X, 1, 0.01) - assert.InDelta(t, unprojected.Y, 0, 0.01) + assert.InDelta(t, unprojected.X, 1, delta) + assert.InDelta(t, unprojected.Y, 0, delta) }) t.Run("for moved matrix", func(t *testing.T) { matrix := pixel.IM.Moved(pixel.V(5, 5)) unprojected := matrix.Unproject(pixel.V(0, 0)) - assert.InDelta(t, unprojected.X, -5, 0.01) - assert.InDelta(t, unprojected.Y, -5, 0.01) + assert.InDelta(t, unprojected.X, -5, delta) + assert.InDelta(t, unprojected.Y, -5, delta) }) t.Run("for scaled matrix", func(t *testing.T) { matrix := pixel.IM.Scaled(pixel.ZV, 2) unprojected := matrix.Unproject(pixel.V(4, 4)) - assert.InDelta(t, unprojected.X, 2, 0.01) - assert.InDelta(t, unprojected.Y, 2, 0.01) + assert.InDelta(t, unprojected.X, 2, delta) + assert.InDelta(t, unprojected.Y, 2, delta) }) t.Run("for singular matrix", func(t *testing.T) { matrix := pixel.Matrix{0, 0, 0, 0, 0, 0} From a0713598da8ef18c7dbe0742ce8d44670cd98abb Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Tue, 12 Feb 2019 16:26:19 +0100 Subject: [PATCH 037/156] #159 Add test case for rotated and moved matrix --- geometry_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/geometry_test.go b/geometry_test.go index 13f90e2f..b1ce00fd 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -100,6 +100,12 @@ func TestMatrix_Unproject(t *testing.T) { assert.InDelta(t, unprojected.X, 2, delta) assert.InDelta(t, unprojected.Y, 2, delta) }) + t.Run("for rotated and moved matrix", func(t *testing.T) { + matrix := pixel.IM.Rotated(pixel.ZV, math.Pi/2).Moved(pixel.V(1, 1)) + unprojected := matrix.Unproject(pixel.V(1, 2)) + assert.InDelta(t, unprojected.X, 1, delta) + assert.InDelta(t, unprojected.Y, 0, delta) + }) t.Run("for singular matrix", func(t *testing.T) { matrix := pixel.Matrix{0, 0, 0, 0, 0, 0} unprojected := matrix.Unproject(pixel.ZV) From 1e19db5b25bbff71728a9fc283acf3775a23c4be Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Tue, 12 Feb 2019 18:40:15 +0100 Subject: [PATCH 038/156] #159 Add test case when matrix determinant is not 1 --- geometry.go | 4 ++-- geometry_test.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index 621a0b43..e847b528 100644 --- a/geometry.go +++ b/geometry.go @@ -405,7 +405,7 @@ func (m Matrix) Project(u Vec) Vec { func (m Matrix) Unproject(u Vec) Vec { det := m[0]*m[3] - m[2]*m[1] return Vec{ - m[3]/det*u.X - m[2]/det*u.Y + m[2]*m[5] - m[3]*m[4], - -m[1]/det*u.X + m[0]/det*u.Y + m[1]*m[4] - m[0]*m[5], + m[3]/det*u.X - m[2]/det*u.Y + (m[2]*m[5]-m[3]*m[4])/det, + -m[1]/det*u.X + m[0]/det*u.Y + (m[1]*m[4]-m[0]*m[5])/det, } } diff --git a/geometry_test.go b/geometry_test.go index b1ce00fd..17b184f3 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -100,6 +100,12 @@ func TestMatrix_Unproject(t *testing.T) { assert.InDelta(t, unprojected.X, 2, delta) assert.InDelta(t, unprojected.Y, 2, delta) }) + t.Run("for scaled, rotated and moved matrix", func(t *testing.T) { + matrix := pixel.IM.Scaled(pixel.ZV, 2).Rotated(pixel.ZV, math.Pi/2).Moved(pixel.V(2, 2)) + unprojected := matrix.Unproject(pixel.V(-2, 6)) + assert.InDelta(t, unprojected.X, 2, delta) + assert.InDelta(t, unprojected.Y, 2, delta) + }) t.Run("for rotated and moved matrix", func(t *testing.T) { matrix := pixel.IM.Rotated(pixel.ZV, math.Pi/2).Moved(pixel.V(1, 1)) unprojected := matrix.Unproject(pixel.V(1, 2)) From 0aec9fead05ecd72fb6e04bec42523e9ec187232 Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Tue, 12 Feb 2019 18:53:38 +0100 Subject: [PATCH 039/156] #159 Format code in TestMatrix_Unproject --- geometry_test.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 17b184f3..9eb83670 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -83,31 +83,39 @@ func TestResizeRect(t *testing.T) { func TestMatrix_Unproject(t *testing.T) { const delta = 4e-16 t.Run("for rotated matrix", func(t *testing.T) { - matrix := pixel.IM.Rotated(pixel.ZV, math.Pi/2) + matrix := pixel.IM. + Rotated(pixel.ZV, math.Pi/2) unprojected := matrix.Unproject(pixel.V(0, 1)) assert.InDelta(t, unprojected.X, 1, delta) assert.InDelta(t, unprojected.Y, 0, delta) }) t.Run("for moved matrix", func(t *testing.T) { - matrix := pixel.IM.Moved(pixel.V(5, 5)) + matrix := pixel.IM. + Moved(pixel.V(5, 5)) unprojected := matrix.Unproject(pixel.V(0, 0)) assert.InDelta(t, unprojected.X, -5, delta) assert.InDelta(t, unprojected.Y, -5, delta) }) t.Run("for scaled matrix", func(t *testing.T) { - matrix := pixel.IM.Scaled(pixel.ZV, 2) + matrix := pixel.IM. + Scaled(pixel.ZV, 2) unprojected := matrix.Unproject(pixel.V(4, 4)) assert.InDelta(t, unprojected.X, 2, delta) assert.InDelta(t, unprojected.Y, 2, delta) }) t.Run("for scaled, rotated and moved matrix", func(t *testing.T) { - matrix := pixel.IM.Scaled(pixel.ZV, 2).Rotated(pixel.ZV, math.Pi/2).Moved(pixel.V(2, 2)) + matrix := pixel.IM. + Scaled(pixel.ZV, 2). + Rotated(pixel.ZV, math.Pi/2). + Moved(pixel.V(2, 2)) unprojected := matrix.Unproject(pixel.V(-2, 6)) assert.InDelta(t, unprojected.X, 2, delta) assert.InDelta(t, unprojected.Y, 2, delta) }) t.Run("for rotated and moved matrix", func(t *testing.T) { - matrix := pixel.IM.Rotated(pixel.ZV, math.Pi/2).Moved(pixel.V(1, 1)) + matrix := pixel.IM. + Rotated(pixel.ZV, math.Pi/2). + Moved(pixel.V(1, 1)) unprojected := matrix.Unproject(pixel.V(1, 2)) assert.InDelta(t, unprojected.X, 1, delta) assert.InDelta(t, unprojected.Y, 0, delta) From b0ed22e0ec1500d051d2410ce04129aa0e43c3eb Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Tue, 12 Feb 2019 21:00:05 +0100 Subject: [PATCH 040/156] #159 Simplify Matrix.Unproject formulas --- geometry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index e847b528..03b2feaf 100644 --- a/geometry.go +++ b/geometry.go @@ -405,7 +405,7 @@ func (m Matrix) Project(u Vec) Vec { func (m Matrix) Unproject(u Vec) Vec { det := m[0]*m[3] - m[2]*m[1] return Vec{ - m[3]/det*u.X - m[2]/det*u.Y + (m[2]*m[5]-m[3]*m[4])/det, - -m[1]/det*u.X + m[0]/det*u.Y + (m[1]*m[4]-m[0]*m[5])/det, + (m[3]*u.X - m[2]*u.Y + m[2]*m[5] - m[3]*m[4]) / det, + (-m[1]*u.X + m[0]*u.Y + m[1]*m[4] - m[0]*m[5]) / det, } } From 2e0da4f44a831cb7944766cc9041d2e350eb433a Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Tue, 12 Feb 2019 21:06:37 +0100 Subject: [PATCH 041/156] #159 Another reduction of Simplify Matrix.Unproject formulas --- geometry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index 03b2feaf..46808726 100644 --- a/geometry.go +++ b/geometry.go @@ -405,7 +405,7 @@ func (m Matrix) Project(u Vec) Vec { func (m Matrix) Unproject(u Vec) Vec { det := m[0]*m[3] - m[2]*m[1] return Vec{ - (m[3]*u.X - m[2]*u.Y + m[2]*m[5] - m[3]*m[4]) / det, - (-m[1]*u.X + m[0]*u.Y + m[1]*m[4] - m[0]*m[5]) / det, + (m[3]*(u.X-m[4]) - m[2]*(u.Y-m[5])) / det, + (-m[1]*(u.X-m[4]) + m[0]*(u.Y-m[5])) / det, } } From 392fe90b11287faee831a9167e00588d1cea760a Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Wed, 13 Feb 2019 14:39:47 +0100 Subject: [PATCH 042/156] #159 Test whether Matrix.Unprejected(Matrix.Projected(vertex)) == vertex --- geometry_test.go | 49 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 9eb83670..8ac01c36 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -81,7 +81,7 @@ func TestResizeRect(t *testing.T) { } func TestMatrix_Unproject(t *testing.T) { - const delta = 4e-16 + const delta = 1e-15 t.Run("for rotated matrix", func(t *testing.T) { matrix := pixel.IM. Rotated(pixel.ZV, math.Pi/2) @@ -91,16 +91,16 @@ func TestMatrix_Unproject(t *testing.T) { }) t.Run("for moved matrix", func(t *testing.T) { matrix := pixel.IM. - Moved(pixel.V(5, 5)) - unprojected := matrix.Unproject(pixel.V(0, 0)) - assert.InDelta(t, unprojected.X, -5, delta) - assert.InDelta(t, unprojected.Y, -5, delta) + Moved(pixel.V(1, 2)) + unprojected := matrix.Unproject(pixel.V(2, 5)) + assert.InDelta(t, unprojected.X, 1, delta) + assert.InDelta(t, unprojected.Y, 3, delta) }) t.Run("for scaled matrix", func(t *testing.T) { matrix := pixel.IM. Scaled(pixel.ZV, 2) - unprojected := matrix.Unproject(pixel.V(4, 4)) - assert.InDelta(t, unprojected.X, 2, delta) + unprojected := matrix.Unproject(pixel.V(2, 4)) + assert.InDelta(t, unprojected.X, 1, delta) assert.InDelta(t, unprojected.Y, 2, delta) }) t.Run("for scaled, rotated and moved matrix", func(t *testing.T) { @@ -120,6 +120,41 @@ func TestMatrix_Unproject(t *testing.T) { assert.InDelta(t, unprojected.X, 1, delta) assert.InDelta(t, unprojected.Y, 0, delta) }) + t.Run("for projected vertices using all kinds of matrices", func(t *testing.T) { + matrices := [...]pixel.Matrix{ + pixel.IM, + pixel.IM.Scaled(pixel.ZV, 0.5), + pixel.IM.Scaled(pixel.ZV, 2), + pixel.IM.Rotated(pixel.ZV, math.Pi/4), + pixel.IM.Moved(pixel.V(0.5, 1)), + pixel.IM.Moved(pixel.V(-1, -0.5)), + pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4), + pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)), + pixel.IM.Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)), + } + vertices := [...]pixel.Vec{ + pixel.V(0, 0), + pixel.V(5, 0), + pixel.V(5, 10), + pixel.V(0, 10), + pixel.V(-5, 10), + pixel.V(-5, 0), + pixel.V(-5, -10), + pixel.V(0, -10), + pixel.V(5, -10), + } + for _, matrix := range matrices { + for _, vertex := range vertices { + testCase := fmt.Sprintf("for matrix %v and vertex %v", matrix, vertex) + t.Run(testCase, func(t *testing.T) { + projected := matrix.Project(vertex) + unprojected := matrix.Unproject(projected) + assert.InDelta(t, vertex.X, unprojected.X, delta) + assert.InDelta(t, vertex.Y, unprojected.Y, delta) + }) + } + } + }) t.Run("for singular matrix", func(t *testing.T) { matrix := pixel.Matrix{0, 0, 0, 0, 0, 0} unprojected := matrix.Unproject(pixel.ZV) From 0a054f35fd8d9af99cd894aec2b9df855b0cf21b Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Wed, 13 Feb 2019 15:06:50 +0100 Subject: [PATCH 043/156] #159 Name matrices in unit test to avoid confusion with test cases --- geometry_test.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 8ac01c36..b517f486 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -121,16 +121,19 @@ func TestMatrix_Unproject(t *testing.T) { assert.InDelta(t, unprojected.Y, 0, delta) }) t.Run("for projected vertices using all kinds of matrices", func(t *testing.T) { - matrices := [...]pixel.Matrix{ - pixel.IM, - pixel.IM.Scaled(pixel.ZV, 0.5), - pixel.IM.Scaled(pixel.ZV, 2), - pixel.IM.Rotated(pixel.ZV, math.Pi/4), - pixel.IM.Moved(pixel.V(0.5, 1)), - pixel.IM.Moved(pixel.V(-1, -0.5)), - pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4), - pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)), - pixel.IM.Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)), + namedMatrices := []struct { + name string + matrix pixel.Matrix + }{ + {"IM", pixel.IM}, + {"Scaled", pixel.IM.Scaled(pixel.ZV, 0.5)}, + {"Scaled x 2", pixel.IM.Scaled(pixel.ZV, 2)}, + {"Rotated", pixel.IM.Rotated(pixel.ZV, math.Pi/4)}, + {"Moved", pixel.IM.Moved(pixel.V(0.5, 1))}, + {"Moved 2", pixel.IM.Moved(pixel.V(-1, -0.5))}, + {"Scaled and Rotated", pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4)}, + {"Scaled, Rotated and Moved", pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2))}, + {"Rotated and Moved", pixel.IM.Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2))}, } vertices := [...]pixel.Vec{ pixel.V(0, 0), @@ -143,10 +146,11 @@ func TestMatrix_Unproject(t *testing.T) { pixel.V(0, -10), pixel.V(5, -10), } - for _, matrix := range matrices { + for _, namedMatrix := range namedMatrices { for _, vertex := range vertices { - testCase := fmt.Sprintf("for matrix %v and vertex %v", matrix, vertex) + testCase := fmt.Sprintf("for matrix %s and vertex %v", namedMatrix.name, vertex) t.Run(testCase, func(t *testing.T) { + matrix := namedMatrix.matrix projected := matrix.Project(vertex) unprojected := matrix.Unproject(projected) assert.InDelta(t, vertex.X, unprojected.X, delta) From 742d1226d8fa6008dfffbb1b6a239d697855273d Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Wed, 13 Feb 2019 15:13:04 +0100 Subject: [PATCH 044/156] #159 Make namedMatrices an array instead of slice --- geometry_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry_test.go b/geometry_test.go index b517f486..766f506d 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -121,7 +121,7 @@ func TestMatrix_Unproject(t *testing.T) { assert.InDelta(t, unprojected.Y, 0, delta) }) t.Run("for projected vertices using all kinds of matrices", func(t *testing.T) { - namedMatrices := []struct { + namedMatrices := [...]struct { name string matrix pixel.Matrix }{ From 42cc2a0376f49fb3c6c1be02369d3b2c6d6427a5 Mon Sep 17 00:00:00 2001 From: Jacek Olszak Date: Wed, 13 Feb 2019 15:25:12 +0100 Subject: [PATCH 045/156] #159 Use map instead of array for named Matrices --- geometry_test.go | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 766f506d..d766b1d6 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -121,19 +121,16 @@ func TestMatrix_Unproject(t *testing.T) { assert.InDelta(t, unprojected.Y, 0, delta) }) t.Run("for projected vertices using all kinds of matrices", func(t *testing.T) { - namedMatrices := [...]struct { - name string - matrix pixel.Matrix - }{ - {"IM", pixel.IM}, - {"Scaled", pixel.IM.Scaled(pixel.ZV, 0.5)}, - {"Scaled x 2", pixel.IM.Scaled(pixel.ZV, 2)}, - {"Rotated", pixel.IM.Rotated(pixel.ZV, math.Pi/4)}, - {"Moved", pixel.IM.Moved(pixel.V(0.5, 1))}, - {"Moved 2", pixel.IM.Moved(pixel.V(-1, -0.5))}, - {"Scaled and Rotated", pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4)}, - {"Scaled, Rotated and Moved", pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2))}, - {"Rotated and Moved", pixel.IM.Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2))}, + namedMatrices := map[string]pixel.Matrix{ + "IM": pixel.IM, + "Scaled": pixel.IM.Scaled(pixel.ZV, 0.5), + "Scaled x 2": pixel.IM.Scaled(pixel.ZV, 2), + "Rotated": pixel.IM.Rotated(pixel.ZV, math.Pi/4), + "Moved": pixel.IM.Moved(pixel.V(0.5, 1)), + "Moved 2": pixel.IM.Moved(pixel.V(-1, -0.5)), + "Scaled and Rotated": pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4), + "Scaled, Rotated and Moved": pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)), + "Rotated and Moved": pixel.IM.Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)), } vertices := [...]pixel.Vec{ pixel.V(0, 0), @@ -146,11 +143,10 @@ func TestMatrix_Unproject(t *testing.T) { pixel.V(0, -10), pixel.V(5, -10), } - for _, namedMatrix := range namedMatrices { + for matrixName, matrix := range namedMatrices { for _, vertex := range vertices { - testCase := fmt.Sprintf("for matrix %s and vertex %v", namedMatrix.name, vertex) + testCase := fmt.Sprintf("for matrix %s and vertex %v", matrixName, vertex) t.Run(testCase, func(t *testing.T) { - matrix := namedMatrix.matrix projected := matrix.Project(vertex) unprojected := matrix.Unproject(projected) assert.InDelta(t, vertex.X, unprojected.X, delta) From e8a3621140c0a1eb3280a6a1af34dc9551e859e5 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:12:05 +0000 Subject: [PATCH 046/156] Made naming consistent --- geometry.go | 10 +++++----- geometry_test.go | 32 ++++++++++++++++---------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/geometry.go b/geometry.go index 59373610..eb87ede3 100644 --- a/geometry.go +++ b/geometry.go @@ -310,15 +310,15 @@ func (r Rect) Intersect(s Rect) Rect { return t } -// IntersectsCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// IntersectCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -func (r Rect) IntersectsCircle(c Circle) Vec { - return c.IntersectsRect(r).Scaled(-1) +func (r Rect) IntersectCircle(c Circle) Vec { + return c.IntersectRect(r).Scaled(-1) } // Circle is a 2D circle. It is defined by two properties: @@ -468,14 +468,14 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectsRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// IntersectRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -func (c Circle) IntersectsRect(r Rect) Vec { +func (c Circle) IntersectRect(r Rect) Vec { // h and v will hold the minimum horizontal and vertical distances (respectively) to avoid overlapping var h, v float64 diff --git a/geometry_test.go b/geometry_test.go index 2bdc2840..e0cd3054 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -457,7 +457,7 @@ func TestCircle_Intersect(t *testing.T) { } } -func TestRect_IntersectsCircle(t *testing.T) { +func TestRect_IntersectCircle(t *testing.T) { type fields struct { Min pixel.Vec Max pixel.Vec @@ -472,79 +472,79 @@ func TestRect_IntersectsCircle(t *testing.T) { want pixel.Vec }{ { - name: "Rect.IntersectsCircle(): no overlap", + name: "Rect.IntersectCircle(): no overlap", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(50, 50), 1)}, want: pixel.ZV, }, { - name: "Rect.IntersectsCircle(): circle contains rect", + name: "Rect.IntersectCircle(): circle contains rect", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 10)}, want: pixel.V(-15, 0), }, { - name: "Rect.IntersectsCircle(): rect contains circle", + name: "Rect.IntersectCircle(): rect contains circle", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 1)}, want: pixel.V(-6, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", + name: "Rect.IntersectCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 0), 1)}, want: pixel.V(1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps top-left corner", + name: "Rect.IntersectCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 10), 1)}, want: pixel.V(1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps bottom-right corner", + name: "Rect.IntersectCircle(): circle overlaps bottom-right corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(10, 0), 1)}, want: pixel.V(-1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps top-right corner", + name: "Rect.IntersectCircle(): circle overlaps top-right corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(10, 10), 1)}, want: pixel.V(-1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps two corners", + name: "Rect.IntersectCircle(): circle overlaps two corners", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 6)}, want: pixel.V(6, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps left edge", + name: "Rect.IntersectCircle(): circle overlaps left edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 1)}, want: pixel.V(1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps bottom edge", + name: "Rect.IntersectCircle(): circle overlaps bottom edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 0), 1)}, want: pixel.V(0, 1), }, { - name: "Rect.IntersectsCircle(): circle overlaps right edge", + name: "Rect.IntersectCircle(): circle overlaps right edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(10, 5), 1)}, want: pixel.V(-1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps top edge", + name: "Rect.IntersectCircle(): circle overlaps top edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 10), 1)}, want: pixel.V(0, -1), }, { - name: "Rect.IntersectsCircle(): edge is tangent", + name: "Rect.IntersectCircle(): edge is tangent", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(-1, 5), 1)}, want: pixel.ZV, @@ -556,8 +556,8 @@ func TestRect_IntersectsCircle(t *testing.T) { Min: tt.fields.Min, Max: tt.fields.Max, } - if got := r.IntersectsCircle(tt.args.c); got != tt.want { - t.Errorf("Rect.IntersectsCircle() = %v, want %v", got, tt.want) + if got := r.IntersectCircle(tt.args.c); got != tt.want { + t.Errorf("Rect.IntersectCircle() = %v, want %v", got, tt.want) } }) } From 91c16c34dad41b55854a6c4ca9e7d29ad29ec078 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:12:54 +0000 Subject: [PATCH 047/156] fixed area tests --- geometry_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index e0cd3054..914c9447 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -204,7 +204,7 @@ func TestCircle_Area(t *testing.T) { { name: "Circle.Area(): positive radius", fields: fields{radius: 10, center: pixel.ZV}, - want: 20 * math.Pi, + want: 100 * math.Pi, }, { name: "Circle.Area(): zero radius", @@ -214,7 +214,7 @@ func TestCircle_Area(t *testing.T) { { name: "Circle.Area(): negative radius", fields: fields{radius: -5, center: pixel.ZV}, - want: -10 * math.Pi, + want: 25 * math.Pi, }, } for _, tt := range tests { From 3b63b7eff9c2ff1597dcfde11764ec476c6ee655 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:15:26 +0000 Subject: [PATCH 048/156] corrected function preambles --- geometry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index eb87ede3..c7d85712 100644 --- a/geometry.go +++ b/geometry.go @@ -314,7 +314,7 @@ func (r Rect) Intersect(s Rect) Rect { // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // -// This function will return true if: +// This function will return a non-zero vector if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully func (r Rect) IntersectCircle(c Circle) Vec { @@ -472,7 +472,7 @@ func (c Circle) Intersect(d Circle) Circle { // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // -// This function will return true if: +// This function will return a non-zero vector if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully func (c Circle) IntersectRect(r Rect) Vec { From d4530ca9feb371bdfc7f5b0fb34c0747732f3bed Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:18:14 +0000 Subject: [PATCH 049/156] More intersection tests --- geometry_test.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/geometry_test.go b/geometry_test.go index 914c9447..2a4a6604 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -544,11 +544,41 @@ func TestRect_IntersectCircle(t *testing.T) { want: pixel.V(0, -1), }, { - name: "Rect.IntersectCircle(): edge is tangent", + name: "Rect.IntersectCircle(): edge is tangent of left side", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(-1, 5), 1)}, want: pixel.ZV, }, + { + name: "Rect.IntersectCircle(): edge is tangent of top side", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, -1), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle above rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, 12), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle below rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, -2), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle left of rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(-1, 5), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle right of rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(11, 5), 1)}, + want: pixel.ZV, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 5072f34b916197895fb476c65c79a047c6908149 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 28 Jan 2019 09:00:24 +0000 Subject: [PATCH 050/156] Added Circle geometry and tests --- geometry.go | 119 ++++++++++++++ geometry_test.go | 402 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 518 insertions(+), 3 deletions(-) diff --git a/geometry.go b/geometry.go index 46808726..82ff17b1 100644 --- a/geometry.go +++ b/geometry.go @@ -318,6 +318,125 @@ func (r Rect) Intersect(s Rect) Rect { return t } +// Circle is a 2D circle. It is defined by two properties: +// - Radius float64 +// - Center vector +type Circle struct { + Radius float64 + Center Vec +} + +// C returns a new Circle with the given radius and center coordinates. +// +// Note that a negative radius is valid. +func C(radius float64, center Vec) Circle { + return Circle{ + Radius: radius, + Center: center, + } +} + +// String returns the string representation of the Circle. +// +// c := pixel.C(10.1234, pixel.ZV) +// c.String() // returns "Circle(10.12, Vec(0, 0))" +// fmt.Println(c) // Circle(10.12, Vec(0, 0)) +func (c Circle) String() string { + return fmt.Sprintf("Circle(%.2f, %s)", c.Radius, c.Center) +} + +// Norm returns the Circle in normalized form - this is that the radius is set to an absolute version. +// +// c := pixel.C(-10, pixel.ZV) +// c.Norm() // returns pixel.Circle{10, pixel.Vec{0, 0}} +func (c Circle) Norm() Circle { + return Circle{ + Radius: math.Abs(c.Radius), + Center: c.Center, + } +} + +// Diameter returns the diameter of the Circle. +func (c Circle) Diameter() float64 { + return c.Radius * 2 +} + +// Area returns the area of the Circle. +func (c Circle) Area() float64 { + return math.Pi * c.Diameter() +} + +// Moved returns the Circle moved by the given vector delta. +func (c Circle) Moved(delta Vec) Circle { + return Circle{ + Radius: c.Radius, + Center: c.Center.Add(delta), + } +} + +// Resized returns the Circle resized by the given delta. +// +// c := pixel.C(10, pixel.ZV) +// c.Resized(-5) // returns pixel.Circle{5, pixel.Vec{0, 0}} +// c.Resized(25) // returns pixel.Circle{35, pixel.Vec{0, 0}} +func (c Circle) Resized(radiusDelta float64) Circle { + return Circle{ + Radius: c.Radius + radiusDelta, + Center: c.Center, + } +} + +// Contains checks whether a vector `u` is contained within this Circle (including it's perimeter). +func (c Circle) Contains(u Vec) bool { + toCenter := c.Center.To(u) + return c.Radius >= toCenter.Len() +} + +// Union returns the minimal Circle which covers both `c` and `d`. +func (c Circle) Union(d Circle) Circle { + biggerC := c + smallerC := d + if c.Radius < d.Radius { + biggerC = d + smallerC = c + } + + // Get distance between centers + dist := c.Center.To(d.Center).Len() + + // If the bigger Circle encompasses the smaller one, we have the result + if dist+smallerC.Radius <= biggerC.Radius { + return biggerC + } + + // Calculate radius for encompassing Circle + r := (dist + biggerC.Radius + smallerC.Radius) / 2 + + // Calculate center for encompassing Circle + theta := .5 + (biggerC.Radius-smallerC.Radius)/(2*dist) + center := smallerC.Center.Scaled(1 - theta).Add(biggerC.Center.Scaled(theta)) + + return Circle{ + Radius: r, + Center: center, + } +} + +// Intersect returns the maximal Circle which is covered by both `c` and `d`. +// +// If `c` and `d` don't overlap, this function returns a zero-sized circle at the centerpoint between the two Circle's +// centers. +func (c Circle) Intersect(d Circle) Circle { + center := Lerp(c.Center, d.Center, 0.5) + + radius := math.Min(0, c.Center.To(d.Center).Len()-(c.Radius+d.Radius)) + + return Circle{ + Radius: math.Abs(radius), + Center: center, + } +} + // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such // as movement, scaling and rotations. // diff --git a/geometry_test.go b/geometry_test.go index d766b1d6..70106203 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -2,11 +2,12 @@ package pixel_test import ( "fmt" - "github.com/stretchr/testify/assert" "math" + "reflect" "testing" "github.com/faiface/pixel" + "github.com/stretchr/testify/assert" ) type rectTestTransform struct { @@ -14,8 +15,7 @@ type rectTestTransform struct { f func(pixel.Rect) pixel.Rect } -func TestResizeRect(t *testing.T) { - +func TestRect_Resize(t *testing.T) { // rectangles squareAroundOrigin := pixel.R(-10, -10, 10, 10) squareAround2020 := pixel.R(10, 10, 30, 30) @@ -162,3 +162,399 @@ func TestMatrix_Unproject(t *testing.T) { assert.True(t, math.IsNaN(unprojected.Y)) }) } + +func TestC(t *testing.T) { + type args struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "C(): positive radius", + args: args{radius: 10, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 10, Center: pixel.V(0, 0)}, + }, + { + name: "C(): zero radius", + args: args{radius: 0, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 0, Center: pixel.V(0, 0)}, + }, + { + name: "C(): negative radius", + args: args{radius: -5, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: -5, Center: pixel.V(0, 0)}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.C(tt.args.radius, tt.args.center); !reflect.DeepEqual(got, tt.want) { + t.Errorf("C() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_String(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "Circle.String(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: "Circle(10.00, Vec(0, 0))", + }, + { + name: "Circle.String(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: "Circle(0.00, Vec(0, 0))", + }, + { + name: "Circle.String(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: "Circle(-5.00, Vec(0, 0))", + }, + { + name: "Circle.String(): irrational radius", + fields: fields{radius: math.Pi, center: pixel.V(0, 0)}, + want: "Circle(3.14, Vec(0, 0))", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.String(); got != tt.want { + t.Errorf("Circle.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Norm(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want pixel.Circle + }{ + { + name: "Circle.Norm(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Norm(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 0, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Norm(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Norm(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Norm() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Diameter(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want float64 + }{ + { + name: "Circle.Diameter(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: 20, + }, + { + name: "Circle.Diameter(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: 0, + }, + { + name: "Circle.Diameter(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: -10, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Diameter(); got != tt.want { + t.Errorf("Circle.Diameter() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Area(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + tests := []struct { + name string + fields fields + want float64 + }{ + { + name: "Circle.Area(): positive radius", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + want: 20 * math.Pi, + }, + { + name: "Circle.Area(): zero radius", + fields: fields{radius: 0, center: pixel.V(0, 0)}, + want: 0, + }, + { + name: "Circle.Area(): negative radius", + fields: fields{radius: -5, center: pixel.V(0, 0)}, + want: -10 * math.Pi, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Area(); got != tt.want { + t.Errorf("Circle.Area() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Moved(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + delta pixel.Vec + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Moved(): positive movement", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{delta: pixel.V(10, 20)}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 10, Y: 20}}, + }, + { + name: "Circle.Moved(): zero movement", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{delta: pixel.ZV}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Moved(): negative movement", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{delta: pixel.V(-5, -10)}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: -5, Y: -10}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Moved() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Resized(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + radiusDelta float64 + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Resized(): positive delta", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{radiusDelta: 5}, + want: pixel.Circle{Radius: 15, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Resized(): zero delta", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{radiusDelta: 0}, + want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + }, + { + name: "Circle.Resized(): negative delta", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{radiusDelta: -5}, + want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Resized(tt.args.radiusDelta); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Resized() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Contains(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + u pixel.Vec + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "Circle.Contains(): point on cicles' center", + fields: fields{radius: 10, center: pixel.ZV}, + args: args{u: pixel.ZV}, + want: true, + }, + { + name: "Circle.Contains(): point offcenter", + fields: fields{radius: 10, center: pixel.V(5, 0)}, + args: args{u: pixel.ZV}, + want: true, + }, + { + name: "Circle.Contains(): point on circumference", + fields: fields{radius: 10, center: pixel.V(10, 0)}, + args: args{u: pixel.ZV}, + want: true, + }, + { + name: "Circle.Contains(): point outside circle", + fields: fields{radius: 10, center: pixel.V(15, 0)}, + args: args{u: pixel.ZV}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Contains(tt.args.u); got != tt.want { + t.Errorf("Circle.Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Union(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + d pixel.Circle + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Union(): overlapping circles", + fields: fields{radius: 5, center: pixel.ZV}, + args: args{d: pixel.C(5, pixel.ZV)}, + want: pixel.C(5, pixel.ZV), + }, + { + name: "Circle.Union(): separate circles", + fields: fields{radius: 1, center: pixel.ZV}, + args: args{d: pixel.C(1, pixel.V(0, 2))}, + want: pixel.C(2, pixel.V(0, 1)), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C(tt.fields.radius, tt.fields.center) + if got := c.Union(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Union() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCircle_Intersect(t *testing.T) { + type fields struct { + radius float64 + center pixel.Vec + } + type args struct { + d pixel.Circle + } + tests := []struct { + name string + fields fields + args args + want pixel.Circle + }{ + { + name: "Circle.Intersect(): intersecting circles", + fields: fields{radius: 1, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(1, 0))}, + want: pixel.C(1, pixel.V(0.5, 0)), + }, + { + name: "Circle.Intersect(): non-intersecting circles", + fields: fields{radius: 1, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(3, 3))}, + want: pixel.C(0, pixel.V(1.5, 1.5)), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.C( + tt.fields.radius, + tt.fields.center, + ) + if got := c.Intersect(tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.Intersect() = %v, want %v", got, tt.want) + } + }) + } +} From ee24eeb67d8f7bf81d90dabc195de2fa35f0c34d Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 09:33:20 +0000 Subject: [PATCH 051/156] fixed circle.Intersect --- geometry.go | 51 +++++++++++++++++++++----- geometry_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 9 deletions(-) diff --git a/geometry.go b/geometry.go index 82ff17b1..61b93cac 100644 --- a/geometry.go +++ b/geometry.go @@ -345,7 +345,7 @@ func (c Circle) String() string { return fmt.Sprintf("Circle(%.2f, %s)", c.Radius, c.Center) } -// Norm returns the Circle in normalized form - this is that the radius is set to an absolute version. +// Norm returns the Circle in normalized form - this sets the radius to its absolute value. // // c := pixel.C(-10, pixel.ZV) // c.Norm() // returns pixel.Circle{10, pixel.Vec{0, 0}} @@ -374,7 +374,7 @@ func (c Circle) Moved(delta Vec) Circle { } } -// Resized returns the Circle resized by the given delta. +// Resized returns the Circle resized by the given delta. The Circles center is use as the anchor. // // c := pixel.C(10, pixel.ZV) // c.Resized(-5) // returns pixel.Circle{5, pixel.Vec{0, 0}} @@ -392,14 +392,26 @@ func (c Circle) Contains(u Vec) bool { return c.Radius >= toCenter.Len() } -// Union returns the minimal Circle which covers both `c` and `d`. -func (c Circle) Union(d Circle) Circle { - biggerC := c - smallerC := d +// MaxCircle will return the larger circle based on the radius. +func MaxCircle(c, d Circle) Circle { + if c.Radius < d.Radius { + return d + } + return c +} + +// MinCircle will return the smaller circle based on the radius. +func MinCircle(c, d Circle) Circle { if c.Radius < d.Radius { - biggerC = d - smallerC = c + return c } + return d +} + +// Union returns the minimal Circle which covers both `c` and `d`. +func (c Circle) Union(d Circle) Circle { + biggerC := MaxCircle(c, d) + smallerC := MinCircle(c, d) // Get distance between centers dist := c.Center.To(d.Center).Len() @@ -427,7 +439,28 @@ func (c Circle) Union(d Circle) Circle { // If `c` and `d` don't overlap, this function returns a zero-sized circle at the centerpoint between the two Circle's // centers. func (c Circle) Intersect(d Circle) Circle { - center := Lerp(c.Center, d.Center, 0.5) + // Check if one of the circles encompasses the other; if so, return that one + biggerC := MaxCircle(c, d) + smallerC := MinCircle(c, d) + + if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius { + return biggerC + } + + // Calculate the midpoint between the two radii + // Distance between centers + dist := c.Center.To(d.Center).Len() + // Difference between radii + diff := dist - (c.Radius + d.Radius) + // Distance from c.Center to the weighted midpoint + distToMidpoint := c.Radius + 0.5*diff + // Weighted midpoint + center := Lerp(c.Center, d.Center, distToMidpoint/dist) + + // No need to calculate radius if the circles do not overlap + if c.Center.To(d.Center).Len() >= c.Radius+d.Radius { + return C(0, center) + } radius := math.Min(0, c.Center.To(d.Center).Len()-(c.Radius+d.Radius)) diff --git a/geometry_test.go b/geometry_test.go index 70106203..4cab36f7 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -545,6 +545,24 @@ func TestCircle_Intersect(t *testing.T) { args: args{d: pixel.C(1, pixel.V(3, 3))}, want: pixel.C(0, pixel.V(1.5, 1.5)), }, + { + name: "Circle.Intersect(): first circle encompassing second", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(3, 3))}, + want: pixel.C(10, pixel.V(0, 0)), + }, + { + name: "Circle.Intersect(): second circle encompassing first", + fields: fields{radius: 1, center: pixel.V(-1, -4)}, + args: args{d: pixel.C(10, pixel.V(0, 0))}, + want: pixel.C(10, pixel.V(0, 0)), + }, + { + name: "Circle.Intersect(): matching circles", + fields: fields{radius: 1, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(0, 0))}, + want: pixel.C(1, pixel.V(0, 0)), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -558,3 +576,79 @@ func TestCircle_Intersect(t *testing.T) { }) } } + +func TestMaxCircle(t *testing.T) { + bigCircle := pixel.C(10, pixel.ZV) + smallCircle := pixel.C(1, pixel.ZV) + + type args struct { + c pixel.Circle + d pixel.Circle + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "MaxCircle(): first bigger", + args: args{c: bigCircle, d: smallCircle}, + want: bigCircle, + }, + { + name: "MaxCircle(): first smaller", + args: args{c: smallCircle, d: bigCircle}, + want: bigCircle, + }, + { + name: "MaxCircle(): both same size", + args: args{c: smallCircle, d: smallCircle}, + want: smallCircle, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MaxCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MaxCircle() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMinCircle(t *testing.T) { + bigCircle := pixel.C(10, pixel.ZV) + smallCircle := pixel.C(1, pixel.ZV) + + type args struct { + c pixel.Circle + d pixel.Circle + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "MinCircle(): first bigger", + args: args{c: bigCircle, d: smallCircle}, + want: smallCircle, + }, + { + name: "MinCircle(): first smaller", + args: args{c: smallCircle, d: bigCircle}, + want: smallCircle, + }, + { + name: "MinCircle(): both same size", + args: args{c: smallCircle, d: smallCircle}, + want: smallCircle, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MinCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MinCircle() = %v, want %v", got, tt.want) + } + }) + } +} From 16722d55e1fee93ddd285e987447af542ce9eb9a Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 09:38:24 +0000 Subject: [PATCH 052/156] not exporting circle size comparisons --- geometry.go | 16 +++++----- geometry_test.go | 76 ------------------------------------------------ 2 files changed, 8 insertions(+), 84 deletions(-) diff --git a/geometry.go b/geometry.go index 61b93cac..55fbf538 100644 --- a/geometry.go +++ b/geometry.go @@ -392,16 +392,16 @@ func (c Circle) Contains(u Vec) bool { return c.Radius >= toCenter.Len() } -// MaxCircle will return the larger circle based on the radius. -func MaxCircle(c, d Circle) Circle { +// maxCircle will return the larger circle based on the radius. +func maxCircle(c, d Circle) Circle { if c.Radius < d.Radius { return d } return c } -// MinCircle will return the smaller circle based on the radius. -func MinCircle(c, d Circle) Circle { +// minCircle will return the smaller circle based on the radius. +func minCircle(c, d Circle) Circle { if c.Radius < d.Radius { return c } @@ -410,8 +410,8 @@ func MinCircle(c, d Circle) Circle { // Union returns the minimal Circle which covers both `c` and `d`. func (c Circle) Union(d Circle) Circle { - biggerC := MaxCircle(c, d) - smallerC := MinCircle(c, d) + biggerC := maxCircle(c, d) + smallerC := minCircle(c, d) // Get distance between centers dist := c.Center.To(d.Center).Len() @@ -440,8 +440,8 @@ func (c Circle) Union(d Circle) Circle { // centers. func (c Circle) Intersect(d Circle) Circle { // Check if one of the circles encompasses the other; if so, return that one - biggerC := MaxCircle(c, d) - smallerC := MinCircle(c, d) + biggerC := maxCircle(c, d) + smallerC := minCircle(c, d) if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius { return biggerC diff --git a/geometry_test.go b/geometry_test.go index 4cab36f7..f7308c50 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -576,79 +576,3 @@ func TestCircle_Intersect(t *testing.T) { }) } } - -func TestMaxCircle(t *testing.T) { - bigCircle := pixel.C(10, pixel.ZV) - smallCircle := pixel.C(1, pixel.ZV) - - type args struct { - c pixel.Circle - d pixel.Circle - } - tests := []struct { - name string - args args - want pixel.Circle - }{ - { - name: "MaxCircle(): first bigger", - args: args{c: bigCircle, d: smallCircle}, - want: bigCircle, - }, - { - name: "MaxCircle(): first smaller", - args: args{c: smallCircle, d: bigCircle}, - want: bigCircle, - }, - { - name: "MaxCircle(): both same size", - args: args{c: smallCircle, d: smallCircle}, - want: smallCircle, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.MaxCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MaxCircle() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMinCircle(t *testing.T) { - bigCircle := pixel.C(10, pixel.ZV) - smallCircle := pixel.C(1, pixel.ZV) - - type args struct { - c pixel.Circle - d pixel.Circle - } - tests := []struct { - name string - args args - want pixel.Circle - }{ - { - name: "MinCircle(): first bigger", - args: args{c: bigCircle, d: smallCircle}, - want: smallCircle, - }, - { - name: "MinCircle(): first smaller", - args: args{c: smallCircle, d: bigCircle}, - want: smallCircle, - }, - { - name: "MinCircle(): both same size", - args: args{c: smallCircle, d: smallCircle}, - want: smallCircle, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.MinCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MinCircle() = %v, want %v", got, tt.want) - } - }) - } -} From 620c551f70e87eab0975eaafbfceabada3b0eaf6 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 09:39:44 +0000 Subject: [PATCH 053/156] moved rect test struct --- geometry_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index f7308c50..cda1dc67 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -10,12 +10,12 @@ import ( "github.com/stretchr/testify/assert" ) -type rectTestTransform struct { - name string - f func(pixel.Rect) pixel.Rect -} - func TestRect_Resize(t *testing.T) { + type rectTestTransform struct { + name string + f func(pixel.Rect) pixel.Rect + } + // rectangles squareAroundOrigin := pixel.R(-10, -10, 10, 10) squareAround2020 := pixel.R(10, 10, 30, 30) From 4c6d061455e299aa3ff116c7888ab3860d9aa489 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:20:21 +0000 Subject: [PATCH 054/156] added rect-circle intersection functions --- geometry.go | 32 ++++++++++++++++++++++ geometry_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/geometry.go b/geometry.go index 55fbf538..b19e9025 100644 --- a/geometry.go +++ b/geometry.go @@ -318,6 +318,16 @@ func (r Rect) Intersect(s Rect) Rect { return t } +// IntersectsCircle returns whether the Circle and the Rect intersect. +// +// This function will return true if: +// - The Rect contains the Circle, partially or fully +// - The Circle contains the Rect, partially of fully +// - An edge of the Rect is a tangent to the Circle +func (r Rect) IntersectsCircle(c Circle) bool { + return c.IntersectsRect(r) +} + // Circle is a 2D circle. It is defined by two properties: // - Radius float64 // - Center vector @@ -470,6 +480,28 @@ func (c Circle) Intersect(d Circle) Circle { } } +// IntersectsRect returns whether the Circle and the Rect intersect. +// +// This function will return true if: +// - The Rect contains the Circle, partially or fully +// - The Circle contains the Rect, partially of fully +// - An edge of the Rect is a tangent to the Circle +func (c Circle) IntersectsRect(r Rect) bool { + // Checks if the c.Center is not in the diagonal quadrants of the rectangle + var grownR Rect + if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { + // 'grow' the Rect by c.Radius in each diagonal + grownR = Rect{ + Min: r.Min.Sub(V(c.Radius, c.Radius)), + Max: r.Max.Add(V(c.Radius, c.Radius)), + } + + return grownR.Contains(c.Center) + } + // The center is in the diagonal quadrants + return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius +} + // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such // as movement, scaling and rotations. // diff --git a/geometry_test.go b/geometry_test.go index cda1dc67..a3aa5f81 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -576,3 +576,73 @@ func TestCircle_Intersect(t *testing.T) { }) } } + +func TestRect_IntersectsCircle(t *testing.T) { + type fields struct { + Min pixel.Vec + Max pixel.Vec + } + type args struct { + c pixel.Circle + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "Rect.IntersectsCircle(): no overlap", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(50, 50))}, + want: false, + }, + { + name: "Rect.IntersectsCircle(): circle contains rect", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(10, pixel.V(5, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): rect contains circle", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(5, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps one corner", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(0, 0))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps two corner", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(11, pixel.V(0, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps one edge", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(0, 5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): edge is tangent", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(-1, 5))}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := pixel.Rect{ + Min: tt.fields.Min, + Max: tt.fields.Max, + } + if got := r.IntersectsCircle(tt.args.c); got != tt.want { + t.Errorf("Rect.IntersectsCircle() = %v, want %v", got, tt.want) + } + }) + } +} From b06a31baa30f50c85a1817b143fc9a9f61f92138 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:23:55 +0000 Subject: [PATCH 055/156] Removed Diameter function --- geometry.go | 7 +------ geometry_test.go | 36 ------------------------------------ 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/geometry.go b/geometry.go index b19e9025..a3d09967 100644 --- a/geometry.go +++ b/geometry.go @@ -366,14 +366,9 @@ func (c Circle) Norm() Circle { } } -// Diameter returns the diameter of the Circle. -func (c Circle) Diameter() float64 { - return c.Radius * 2 -} - // Area returns the area of the Circle. func (c Circle) Area() float64 { - return math.Pi * c.Diameter() + return math.Pi * c.Radius * 2 } // Moved returns the Circle moved by the given vector delta. diff --git a/geometry_test.go b/geometry_test.go index a3aa5f81..0a74ffad 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -275,42 +275,6 @@ func TestCircle_Norm(t *testing.T) { } } -func TestCircle_Diameter(t *testing.T) { - type fields struct { - radius float64 - center pixel.Vec - } - tests := []struct { - name string - fields fields - want float64 - }{ - { - name: "Circle.Diameter(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, - want: 20, - }, - { - name: "Circle.Diameter(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, - want: 0, - }, - { - name: "Circle.Diameter(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, - want: -10, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) - if got := c.Diameter(); got != tt.want { - t.Errorf("Circle.Diameter() = %v, want %v", got, tt.want) - } - }) - } -} - func TestCircle_Area(t *testing.T) { type fields struct { radius float64 From 44d030b9cab970fe197555d87fc821f154c3b204 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:33:12 +0000 Subject: [PATCH 056/156] normalising before getting bigger/smaller --- geometry.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/geometry.go b/geometry.go index a3d09967..80682ab4 100644 --- a/geometry.go +++ b/geometry.go @@ -415,8 +415,8 @@ func minCircle(c, d Circle) Circle { // Union returns the minimal Circle which covers both `c` and `d`. func (c Circle) Union(d Circle) Circle { - biggerC := maxCircle(c, d) - smallerC := minCircle(c, d) + biggerC := maxCircle(c.Norm(), d.Norm()) + smallerC := minCircle(c.Norm(), d.Norm()) // Get distance between centers dist := c.Center.To(d.Center).Len() @@ -445,8 +445,8 @@ func (c Circle) Union(d Circle) Circle { // centers. func (c Circle) Intersect(d Circle) Circle { // Check if one of the circles encompasses the other; if so, return that one - biggerC := maxCircle(c, d) - smallerC := minCircle(c, d) + biggerC := maxCircle(c.Norm(), d.Norm()) + smallerC := minCircle(c.Norm(), d.Norm()) if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius { return biggerC From 23168ee324ccba259b1aad6a84f86349a44fd1ef Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:34:59 +0000 Subject: [PATCH 057/156] remove local var --- geometry.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/geometry.go b/geometry.go index 80682ab4..882a282b 100644 --- a/geometry.go +++ b/geometry.go @@ -483,15 +483,12 @@ func (c Circle) Intersect(d Circle) Circle { // - An edge of the Rect is a tangent to the Circle func (c Circle) IntersectsRect(r Rect) bool { // Checks if the c.Center is not in the diagonal quadrants of the rectangle - var grownR Rect if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { // 'grow' the Rect by c.Radius in each diagonal - grownR = Rect{ + return Rect{ Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius)), - } - - return grownR.Contains(c.Center) + }.Contains(c.Center) } // The center is in the diagonal quadrants return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius From 2e275ab0d52263024aa774d35575fa418ef86bec Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:41:10 +0000 Subject: [PATCH 058/156] Made test clearer --- geometry_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 0a74ffad..057ecac3 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -580,9 +580,9 @@ func TestRect_IntersectsCircle(t *testing.T) { want: true, }, { - name: "Rect.IntersectsCircle(): circle overlaps two corner", + name: "Rect.IntersectsCircle(): circle overlaps two corners", fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, - args: args{c: pixel.C(11, pixel.V(0, 5))}, + args: args{c: pixel.C(6, pixel.V(0, 5))}, want: true, }, { From 13171c409b0956539b4cd21d22c60b4f91a93c6d Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:45:00 +0000 Subject: [PATCH 059/156] using Lerp --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 882a282b..5d0db99b 100644 --- a/geometry.go +++ b/geometry.go @@ -431,7 +431,7 @@ func (c Circle) Union(d Circle) Circle { // Calculate center for encompassing Circle theta := .5 + (biggerC.Radius-smallerC.Radius)/(2*dist) - center := smallerC.Center.Scaled(1 - theta).Add(biggerC.Center.Scaled(theta)) + center := Lerp(smallerC.Center, biggerC.Center, theta) return Circle{ Radius: r, From e225e9a1efc7978f0cbd26e3769825ece2b1a80b Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 11:47:49 +0000 Subject: [PATCH 060/156] corrected comment --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 5d0db99b..22f650a0 100644 --- a/geometry.go +++ b/geometry.go @@ -484,7 +484,7 @@ func (c Circle) Intersect(d Circle) Circle { func (c Circle) IntersectsRect(r Rect) bool { // Checks if the c.Center is not in the diagonal quadrants of the rectangle if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { - // 'grow' the Rect by c.Radius in each diagonal + // 'grow' the Rect by c.Radius in each orthagonal return Rect{ Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius)), From 520459bc6d4c36fbf4d6ea6a6659ae547c1aea12 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 12:18:35 +0000 Subject: [PATCH 061/156] made test param generation more consistant --- geometry_test.go | 96 ++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 057ecac3..2ad0b6ef 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -175,18 +175,18 @@ func TestC(t *testing.T) { }{ { name: "C(): positive radius", - args: args{radius: 10, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 10, Center: pixel.V(0, 0)}, + args: args{radius: 10, center: pixel.ZV}, + want: pixel.Circle{Radius: 10, Center: pixel.ZV}, }, { name: "C(): zero radius", - args: args{radius: 0, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 0, Center: pixel.V(0, 0)}, + args: args{radius: 0, center: pixel.ZV}, + want: pixel.Circle{Radius: 0, Center: pixel.ZV}, }, { name: "C(): negative radius", - args: args{radius: -5, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: -5, Center: pixel.V(0, 0)}, + args: args{radius: -5, center: pixel.ZV}, + want: pixel.Circle{Radius: -5, Center: pixel.ZV}, }, } for _, tt := range tests { @@ -210,22 +210,22 @@ func TestCircle_String(t *testing.T) { }{ { name: "Circle.String(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, want: "Circle(10.00, Vec(0, 0))", }, { name: "Circle.String(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, + fields: fields{radius: 0, center: pixel.ZV}, want: "Circle(0.00, Vec(0, 0))", }, { name: "Circle.String(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, + fields: fields{radius: -5, center: pixel.ZV}, want: "Circle(-5.00, Vec(0, 0))", }, { name: "Circle.String(): irrational radius", - fields: fields{radius: math.Pi, center: pixel.V(0, 0)}, + fields: fields{radius: math.Pi, center: pixel.ZV}, want: "Circle(3.14, Vec(0, 0))", }, } @@ -251,18 +251,18 @@ func TestCircle_Norm(t *testing.T) { }{ { name: "Circle.Norm(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + fields: fields{radius: 10, center: pixel.ZV}, + want: pixel.C(10, pixel.ZV), }, { name: "Circle.Norm(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 0, Center: pixel.Vec{X: 0, Y: 0}}, + fields: fields{radius: 0, center: pixel.ZV}, + want: pixel.C(0, pixel.ZV), }, { name: "Circle.Norm(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, - want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + fields: fields{radius: -5, center: pixel.ZV}, + want: pixel.C(5, pixel.ZV), }, } for _, tt := range tests { @@ -287,17 +287,17 @@ func TestCircle_Area(t *testing.T) { }{ { name: "Circle.Area(): positive radius", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, want: 20 * math.Pi, }, { name: "Circle.Area(): zero radius", - fields: fields{radius: 0, center: pixel.V(0, 0)}, + fields: fields{radius: 0, center: pixel.ZV}, want: 0, }, { name: "Circle.Area(): negative radius", - fields: fields{radius: -5, center: pixel.V(0, 0)}, + fields: fields{radius: -5, center: pixel.ZV}, want: -10 * math.Pi, }, } @@ -327,21 +327,21 @@ func TestCircle_Moved(t *testing.T) { }{ { name: "Circle.Moved(): positive movement", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(10, 20)}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 10, Y: 20}}, + want: pixel.C(10, pixel.V(10, 20)), }, { name: "Circle.Moved(): zero movement", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.ZV}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(10, pixel.V(0, 0)), }, { name: "Circle.Moved(): negative movement", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(-5, -10)}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: -5, Y: -10}}, + want: pixel.C(10, pixel.V(-5, -10)), }, } for _, tt := range tests { @@ -370,21 +370,21 @@ func TestCircle_Resized(t *testing.T) { }{ { name: "Circle.Resized(): positive delta", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 5}, - want: pixel.Circle{Radius: 15, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(15, pixel.V(0, 0)), }, { name: "Circle.Resized(): zero delta", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 0}, - want: pixel.Circle{Radius: 10, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(10, pixel.V(0, 0)), }, { name: "Circle.Resized(): negative delta", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: -5}, - want: pixel.Circle{Radius: 5, Center: pixel.Vec{X: 0, Y: 0}}, + want: pixel.C(5, pixel.V(0, 0)), }, } for _, tt := range tests { @@ -499,33 +499,33 @@ func TestCircle_Intersect(t *testing.T) { }{ { name: "Circle.Intersect(): intersecting circles", - fields: fields{radius: 1, center: pixel.V(0, 0)}, + fields: fields{radius: 1, center: pixel.ZV}, args: args{d: pixel.C(1, pixel.V(1, 0))}, want: pixel.C(1, pixel.V(0.5, 0)), }, { name: "Circle.Intersect(): non-intersecting circles", - fields: fields{radius: 1, center: pixel.V(0, 0)}, + fields: fields{radius: 1, center: pixel.ZV}, args: args{d: pixel.C(1, pixel.V(3, 3))}, want: pixel.C(0, pixel.V(1.5, 1.5)), }, { name: "Circle.Intersect(): first circle encompassing second", - fields: fields{radius: 10, center: pixel.V(0, 0)}, + fields: fields{radius: 10, center: pixel.ZV}, args: args{d: pixel.C(1, pixel.V(3, 3))}, - want: pixel.C(10, pixel.V(0, 0)), + want: pixel.C(10, pixel.ZV), }, { name: "Circle.Intersect(): second circle encompassing first", fields: fields{radius: 1, center: pixel.V(-1, -4)}, - args: args{d: pixel.C(10, pixel.V(0, 0))}, - want: pixel.C(10, pixel.V(0, 0)), + args: args{d: pixel.C(10, pixel.ZV)}, + want: pixel.C(10, pixel.ZV), }, { name: "Circle.Intersect(): matching circles", - fields: fields{radius: 1, center: pixel.V(0, 0)}, - args: args{d: pixel.C(1, pixel.V(0, 0))}, - want: pixel.C(1, pixel.V(0, 0)), + fields: fields{radius: 1, center: pixel.ZV}, + args: args{d: pixel.C(1, pixel.ZV)}, + want: pixel.C(1, pixel.ZV), }, } for _, tt := range tests { @@ -557,43 +557,43 @@ func TestRect_IntersectsCircle(t *testing.T) { }{ { name: "Rect.IntersectsCircle(): no overlap", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(50, 50))}, want: false, }, { name: "Rect.IntersectsCircle(): circle contains rect", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(10, pixel.V(5, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): rect contains circle", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(5, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps one corner", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(0, 0))}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.ZV)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps two corners", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(6, pixel.V(0, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps one edge", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(0, 5))}, want: true, }, { name: "Rect.IntersectsCircle(): edge is tangent", - fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(1, pixel.V(-1, 5))}, want: true, }, From ecda96a36ff1a9a03fae4367c3999129b34fed0f Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 12:27:38 +0000 Subject: [PATCH 062/156] removed unneeded Min call --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 22f650a0..3d4e3fa6 100644 --- a/geometry.go +++ b/geometry.go @@ -467,7 +467,7 @@ func (c Circle) Intersect(d Circle) Circle { return C(0, center) } - radius := math.Min(0, c.Center.To(d.Center).Len()-(c.Radius+d.Radius)) + radius := c.Center.To(d.Center).Len() - (c.Radius + d.Radius) return Circle{ Radius: math.Abs(radius), From 4d30a8fe490d5fc99b927ed43a414a5e114d8986 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 29 Jan 2019 12:31:59 +0000 Subject: [PATCH 063/156] fixed intersect function --- geometry.go | 3 ++- geometry_test.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/geometry.go b/geometry.go index 3d4e3fa6..458b23ad 100644 --- a/geometry.go +++ b/geometry.go @@ -491,7 +491,8 @@ func (c Circle) IntersectsRect(r Rect) bool { }.Contains(c.Center) } // The center is in the diagonal quadrants - return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius + return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius || + c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius || c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such diff --git a/geometry_test.go b/geometry_test.go index 2ad0b6ef..ac2198c5 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -574,9 +574,15 @@ func TestRect_IntersectsCircle(t *testing.T) { want: true, }, { - name: "Rect.IntersectsCircle(): circle overlaps one corner", + name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.ZV)}, + args: args{c: pixel.C(1, pixel.V(-.5, -.5))}, + want: true, + }, + { + name: "Rect.IntersectsCircle(): circle overlaps top-left corner", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(1, pixel.V(-.5, 10.5))}, want: true, }, { From 5abf2d29a659bdc058dfd95329ff6ba5397f326f Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 30 Jan 2019 08:37:27 +0000 Subject: [PATCH 064/156] swapped radius and center order --- geometry.go | 30 ++++++++--------- geometry_test.go | 88 ++++++++++++++++++++++++------------------------ 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/geometry.go b/geometry.go index 458b23ad..087b1ad3 100644 --- a/geometry.go +++ b/geometry.go @@ -329,20 +329,20 @@ func (r Rect) IntersectsCircle(c Circle) bool { } // Circle is a 2D circle. It is defined by two properties: -// - Radius float64 // - Center vector +// - Radius float64 type Circle struct { - Radius float64 Center Vec + Radius float64 } // C returns a new Circle with the given radius and center coordinates. // // Note that a negative radius is valid. -func C(radius float64, center Vec) Circle { +func C(center Vec, radius float64) Circle { return Circle{ - Radius: radius, Center: center, + Radius: radius, } } @@ -352,17 +352,17 @@ func C(radius float64, center Vec) Circle { // c.String() // returns "Circle(10.12, Vec(0, 0))" // fmt.Println(c) // Circle(10.12, Vec(0, 0)) func (c Circle) String() string { - return fmt.Sprintf("Circle(%.2f, %s)", c.Radius, c.Center) + return fmt.Sprintf("Circle(%s, %.2f)", c.Center, c.Radius) } // Norm returns the Circle in normalized form - this sets the radius to its absolute value. // // c := pixel.C(-10, pixel.ZV) -// c.Norm() // returns pixel.Circle{10, pixel.Vec{0, 0}} +// c.Norm() // returns pixel.Circle{pixel.Vec{0, 0}, 10} func (c Circle) Norm() Circle { return Circle{ - Radius: math.Abs(c.Radius), Center: c.Center, + Radius: math.Abs(c.Radius), } } @@ -374,20 +374,20 @@ func (c Circle) Area() float64 { // Moved returns the Circle moved by the given vector delta. func (c Circle) Moved(delta Vec) Circle { return Circle{ - Radius: c.Radius, Center: c.Center.Add(delta), + Radius: c.Radius, } } // Resized returns the Circle resized by the given delta. The Circles center is use as the anchor. // -// c := pixel.C(10, pixel.ZV) -// c.Resized(-5) // returns pixel.Circle{5, pixel.Vec{0, 0}} -// c.Resized(25) // returns pixel.Circle{35, pixel.Vec{0, 0}} +// c := pixel.C(pixel.ZV, 10) +// c.Resized(-5) // returns pixel.Circle{pixel.Vec{0, 0}, 5} +// c.Resized(25) // returns pixel.Circle{pixel.Vec{0, 0}, 35} func (c Circle) Resized(radiusDelta float64) Circle { return Circle{ - Radius: c.Radius + radiusDelta, Center: c.Center, + Radius: c.Radius + radiusDelta, } } @@ -434,8 +434,8 @@ func (c Circle) Union(d Circle) Circle { center := Lerp(smallerC.Center, biggerC.Center, theta) return Circle{ - Radius: r, Center: center, + Radius: r, } } @@ -464,14 +464,14 @@ func (c Circle) Intersect(d Circle) Circle { // No need to calculate radius if the circles do not overlap if c.Center.To(d.Center).Len() >= c.Radius+d.Radius { - return C(0, center) + return C(center, 0) } radius := c.Center.To(d.Center).Len() - (c.Radius + d.Radius) return Circle{ - Radius: math.Abs(radius), Center: center, + Radius: math.Abs(radius), } } diff --git a/geometry_test.go b/geometry_test.go index ac2198c5..5425dff3 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -191,7 +191,7 @@ func TestC(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := pixel.C(tt.args.radius, tt.args.center); !reflect.DeepEqual(got, tt.want) { + if got := pixel.C(tt.args.center, tt.args.radius); !reflect.DeepEqual(got, tt.want) { t.Errorf("C() = %v, want %v", got, tt.want) } }) @@ -211,27 +211,27 @@ func TestCircle_String(t *testing.T) { { name: "Circle.String(): positive radius", fields: fields{radius: 10, center: pixel.ZV}, - want: "Circle(10.00, Vec(0, 0))", + want: "Circle(Vec(0, 0), 10.00)", }, { name: "Circle.String(): zero radius", fields: fields{radius: 0, center: pixel.ZV}, - want: "Circle(0.00, Vec(0, 0))", + want: "Circle(Vec(0, 0), 0.00)", }, { name: "Circle.String(): negative radius", fields: fields{radius: -5, center: pixel.ZV}, - want: "Circle(-5.00, Vec(0, 0))", + want: "Circle(Vec(0, 0), -5.00)", }, { name: "Circle.String(): irrational radius", fields: fields{radius: math.Pi, center: pixel.ZV}, - want: "Circle(3.14, Vec(0, 0))", + want: "Circle(Vec(0, 0), 3.14)", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.String(); got != tt.want { t.Errorf("Circle.String() = %v, want %v", got, tt.want) } @@ -252,22 +252,22 @@ func TestCircle_Norm(t *testing.T) { { name: "Circle.Norm(): positive radius", fields: fields{radius: 10, center: pixel.ZV}, - want: pixel.C(10, pixel.ZV), + want: pixel.C(pixel.ZV, 10), }, { name: "Circle.Norm(): zero radius", fields: fields{radius: 0, center: pixel.ZV}, - want: pixel.C(0, pixel.ZV), + want: pixel.C(pixel.ZV, 0), }, { name: "Circle.Norm(): negative radius", fields: fields{radius: -5, center: pixel.ZV}, - want: pixel.C(5, pixel.ZV), + want: pixel.C(pixel.ZV, 5), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Norm(); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Norm() = %v, want %v", got, tt.want) } @@ -303,7 +303,7 @@ func TestCircle_Area(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Area(); got != tt.want { t.Errorf("Circle.Area() = %v, want %v", got, tt.want) } @@ -329,24 +329,24 @@ func TestCircle_Moved(t *testing.T) { name: "Circle.Moved(): positive movement", fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(10, 20)}, - want: pixel.C(10, pixel.V(10, 20)), + want: pixel.C(pixel.V(10, 20), 10), }, { name: "Circle.Moved(): zero movement", fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.ZV}, - want: pixel.C(10, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 10), }, { name: "Circle.Moved(): negative movement", fields: fields{radius: 10, center: pixel.ZV}, args: args{delta: pixel.V(-5, -10)}, - want: pixel.C(10, pixel.V(-5, -10)), + want: pixel.C(pixel.V(-5, -10), 10), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Moved() = %v, want %v", got, tt.want) } @@ -372,24 +372,24 @@ func TestCircle_Resized(t *testing.T) { name: "Circle.Resized(): positive delta", fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 5}, - want: pixel.C(15, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 15), }, { name: "Circle.Resized(): zero delta", fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: 0}, - want: pixel.C(10, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 10), }, { name: "Circle.Resized(): negative delta", fields: fields{radius: 10, center: pixel.ZV}, args: args{radiusDelta: -5}, - want: pixel.C(5, pixel.V(0, 0)), + want: pixel.C(pixel.V(0, 0), 5), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Resized(tt.args.radiusDelta); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Resized() = %v, want %v", got, tt.want) } @@ -438,7 +438,7 @@ func TestCircle_Contains(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Contains(tt.args.u); got != tt.want { t.Errorf("Circle.Contains() = %v, want %v", got, tt.want) } @@ -463,19 +463,19 @@ func TestCircle_Union(t *testing.T) { { name: "Circle.Union(): overlapping circles", fields: fields{radius: 5, center: pixel.ZV}, - args: args{d: pixel.C(5, pixel.ZV)}, - want: pixel.C(5, pixel.ZV), + args: args{d: pixel.C(pixel.ZV, 5)}, + want: pixel.C(pixel.ZV, 5), }, { name: "Circle.Union(): separate circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(0, 2))}, - want: pixel.C(2, pixel.V(0, 1)), + args: args{d: pixel.C(pixel.V(0, 2), 1)}, + want: pixel.C(pixel.V(0, 1), 2), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := pixel.C(tt.fields.radius, tt.fields.center) + c := pixel.C(tt.fields.center, tt.fields.radius) if got := c.Union(tt.args.d); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Union() = %v, want %v", got, tt.want) } @@ -500,39 +500,39 @@ func TestCircle_Intersect(t *testing.T) { { name: "Circle.Intersect(): intersecting circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(1, 0))}, - want: pixel.C(1, pixel.V(0.5, 0)), + args: args{d: pixel.C(pixel.V(1, 0), 1)}, + want: pixel.C(pixel.V(0.5, 0), 1), }, { name: "Circle.Intersect(): non-intersecting circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(3, 3))}, - want: pixel.C(0, pixel.V(1.5, 1.5)), + args: args{d: pixel.C(pixel.V(3, 3), 1)}, + want: pixel.C(pixel.V(1.5, 1.5), 0), }, { name: "Circle.Intersect(): first circle encompassing second", fields: fields{radius: 10, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.V(3, 3))}, - want: pixel.C(10, pixel.ZV), + args: args{d: pixel.C(pixel.V(3, 3), 1)}, + want: pixel.C(pixel.ZV, 10), }, { name: "Circle.Intersect(): second circle encompassing first", fields: fields{radius: 1, center: pixel.V(-1, -4)}, - args: args{d: pixel.C(10, pixel.ZV)}, - want: pixel.C(10, pixel.ZV), + args: args{d: pixel.C(pixel.ZV, 10)}, + want: pixel.C(pixel.ZV, 10), }, { name: "Circle.Intersect(): matching circles", fields: fields{radius: 1, center: pixel.ZV}, - args: args{d: pixel.C(1, pixel.ZV)}, - want: pixel.C(1, pixel.ZV), + args: args{d: pixel.C(pixel.ZV, 1)}, + want: pixel.C(pixel.ZV, 1), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := pixel.C( - tt.fields.radius, tt.fields.center, + tt.fields.radius, ) if got := c.Intersect(tt.args.d); !reflect.DeepEqual(got, tt.want) { t.Errorf("Circle.Intersect() = %v, want %v", got, tt.want) @@ -558,49 +558,49 @@ func TestRect_IntersectsCircle(t *testing.T) { { name: "Rect.IntersectsCircle(): no overlap", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(50, 50))}, + args: args{c: pixel.C(pixel.V(50, 50), 1)}, want: false, }, { name: "Rect.IntersectsCircle(): circle contains rect", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(10, pixel.V(5, 5))}, + args: args{c: pixel.C(pixel.V(5, 5), 10)}, want: true, }, { name: "Rect.IntersectsCircle(): rect contains circle", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(5, 5))}, + args: args{c: pixel.C(pixel.V(5, 5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(-.5, -.5))}, + args: args{c: pixel.C(pixel.V(-.5, -.5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(-.5, 10.5))}, + args: args{c: pixel.C(pixel.V(-.5, 10.5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps two corners", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(6, pixel.V(0, 5))}, + args: args{c: pixel.C(pixel.V(0, 5), 6)}, want: true, }, { name: "Rect.IntersectsCircle(): circle overlaps one edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(0, 5))}, + args: args{c: pixel.C(pixel.V(0, 5), 1)}, want: true, }, { name: "Rect.IntersectsCircle(): edge is tangent", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(1, pixel.V(-1, 5))}, + args: args{c: pixel.C(pixel.V(-1, 5), 1)}, want: true, }, } From 0a0c8ff1100050532d73a8524e48c3c4c7cc0e2a Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 30 Jan 2019 08:38:51 +0000 Subject: [PATCH 065/156] corrected area formula --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 087b1ad3..175b2689 100644 --- a/geometry.go +++ b/geometry.go @@ -368,7 +368,7 @@ func (c Circle) Norm() Circle { // Area returns the area of the Circle. func (c Circle) Area() float64 { - return math.Pi * c.Radius * 2 + return math.Pi * math.Pow(c.Radius, 2) } // Moved returns the Circle moved by the given vector delta. From ab7053379392b598c055a963f8a686101a35bd3c Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 31 Jan 2019 08:22:59 +0000 Subject: [PATCH 066/156] wip --- geometry.go | 86 +++++++++++++++++++++++++++++++++++++++--------- geometry_test.go | 54 +++++++++++++++++++++++------- 2 files changed, 113 insertions(+), 27 deletions(-) diff --git a/geometry.go b/geometry.go index 175b2689..95ccbf92 100644 --- a/geometry.go +++ b/geometry.go @@ -318,14 +318,15 @@ func (r Rect) Intersect(s Rect) Rect { return t } -// IntersectsCircle returns whether the Circle and the Rect intersect. +// IntersectsCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only +// the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -// - An edge of the Rect is a tangent to the Circle -func (r Rect) IntersectsCircle(c Circle) bool { - return c.IntersectsRect(r) +func (r Rect) IntersectsCircle(c Circle) Vec { + return c.IntersectsRect(r).Scaled(-1) } // Circle is a 2D circle. It is defined by two properties: @@ -475,24 +476,79 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectsRect returns whether the Circle and the Rect intersect. +// IntersectsRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only +// the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -// - An edge of the Rect is a tangent to the Circle -func (c Circle) IntersectsRect(r Rect) bool { +func (c Circle) IntersectsRect(r Rect) Vec { + // h and v will hold the minimum horizontal and vertical distances (respectively) to avoid overlapping + var h, v float64 + // Checks if the c.Center is not in the diagonal quadrants of the rectangle if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { // 'grow' the Rect by c.Radius in each orthagonal - return Rect{ - Min: r.Min.Sub(V(c.Radius, c.Radius)), - Max: r.Max.Add(V(c.Radius, c.Radius)), - }.Contains(c.Center) - } - // The center is in the diagonal quadrants - return c.Center.To(r.Min).Len() <= c.Radius || c.Center.To(r.Max).Len() <= c.Radius || - c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius || c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius + grown := Rect{Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius))} + if !grown.Contains(c.Center) { + // c.Center not close enough to overlap, return zero-vector + return ZV + } + + // Get minimum distance to travel out of Rect + rToC := r.Center().To(c.Center) + h = c.Radius - math.Abs(rToC.X) + (r.W() / 2) + v = c.Radius - math.Abs(rToC.Y) + (r.H() / 2) + + if rToC.X < 0 { + h = -h + } + if rToC.Y < 0 { + v = -v + } + } else { + // The center is in the diagonal quadrants + if c.Center.To(r.Min).Len() <= c.Radius { + // Closest to bottom-left + cornerToCenter := r.Min.To(c.Center) + // Get the horizontal and vertical overlaps + h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)) + v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))) + } + if c.Center.To(r.Max).Len() <= c.Radius { + // Closest to top-right + cornerToCenter := r.Max.To(c.Center) + // Get the horizontal and vertical overlaps + h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)) + v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)) + } + if c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius { + // Closest to top-left + cornerToCenter := V(r.Min.X, r.Max.Y).To(c.Center) + // Get the horizontal and vertical overlaps + h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))) + v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)) + } + if c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius { + // Closest to bottom-right + cornerToCenter := V(r.Max.X, r.Min.Y).To(c.Center) + // Get the horizontal and vertical overlaps + h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))) + v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))) + } + } + + // No intersect + if h == 0 && v == 0 { + return ZV + } + + if math.Abs(h) > math.Abs(v) { + // Vertical distance shorter + return V(0, v) + } + return V(h, 0) } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such diff --git a/geometry_test.go b/geometry_test.go index 5425dff3..da321b24 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -553,55 +553,85 @@ func TestRect_IntersectsCircle(t *testing.T) { name string fields fields args args - want bool + want pixel.Vec }{ { name: "Rect.IntersectsCircle(): no overlap", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(50, 50), 1)}, - want: false, + want: pixel.ZV, }, { name: "Rect.IntersectsCircle(): circle contains rect", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 10)}, - want: true, + want: pixel.V(-15, 0), }, { name: "Rect.IntersectsCircle(): rect contains circle", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 1)}, - want: true, + want: pixel.V(-6, 0), }, { name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(-.5, -.5), 1)}, - want: true, + args: args{c: pixel.C(pixel.V(0, 0), 1)}, + want: pixel.V(1, 0), }, { name: "Rect.IntersectsCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(-.5, 10.5), 1)}, - want: true, + args: args{c: pixel.C(pixel.V(0, 10), 1)}, + want: pixel.V(1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps bottom-right corner", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(10, 0), 1)}, + want: pixel.V(-1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps top-right corner", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(10, 10), 1)}, + want: pixel.V(-1, 0), }, { name: "Rect.IntersectsCircle(): circle overlaps two corners", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 6)}, - want: true, + want: pixel.V(6, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps one edge", + name: "Rect.IntersectsCircle(): circle overlaps left edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 1)}, - want: true, + want: pixel.V(1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps bottom edge", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, 0), 1)}, + want: pixel.V(0, 1), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps right edge", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(10, 5), 1)}, + want: pixel.V(-1, 0), + }, + { + name: "Rect.IntersectsCircle(): circle overlaps top edge", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, 10), 1)}, + want: pixel.V(0, -1), }, { name: "Rect.IntersectsCircle(): edge is tangent", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(-1, 5), 1)}, - want: true, + want: pixel.ZV, }, } for _, tt := range tests { From f21c48599f003dd4580768810e7ffba752f009ab Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:12:05 +0000 Subject: [PATCH 067/156] Made naming consistent --- geometry.go | 10 +++++----- geometry_test.go | 32 ++++++++++++++++---------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/geometry.go b/geometry.go index 95ccbf92..8cd49e8d 100644 --- a/geometry.go +++ b/geometry.go @@ -318,15 +318,15 @@ func (r Rect) Intersect(s Rect) Rect { return t } -// IntersectsCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// IntersectCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -func (r Rect) IntersectsCircle(c Circle) Vec { - return c.IntersectsRect(r).Scaled(-1) +func (r Rect) IntersectCircle(c Circle) Vec { + return c.IntersectRect(r).Scaled(-1) } // Circle is a 2D circle. It is defined by two properties: @@ -476,14 +476,14 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectsRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// IntersectRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // // This function will return true if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully -func (c Circle) IntersectsRect(r Rect) Vec { +func (c Circle) IntersectRect(r Rect) Vec { // h and v will hold the minimum horizontal and vertical distances (respectively) to avoid overlapping var h, v float64 diff --git a/geometry_test.go b/geometry_test.go index da321b24..1ecc58e0 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -541,7 +541,7 @@ func TestCircle_Intersect(t *testing.T) { } } -func TestRect_IntersectsCircle(t *testing.T) { +func TestRect_IntersectCircle(t *testing.T) { type fields struct { Min pixel.Vec Max pixel.Vec @@ -556,79 +556,79 @@ func TestRect_IntersectsCircle(t *testing.T) { want pixel.Vec }{ { - name: "Rect.IntersectsCircle(): no overlap", + name: "Rect.IntersectCircle(): no overlap", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(50, 50), 1)}, want: pixel.ZV, }, { - name: "Rect.IntersectsCircle(): circle contains rect", + name: "Rect.IntersectCircle(): circle contains rect", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 10)}, want: pixel.V(-15, 0), }, { - name: "Rect.IntersectsCircle(): rect contains circle", + name: "Rect.IntersectCircle(): rect contains circle", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 5), 1)}, want: pixel.V(-6, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps bottom-left corner", + name: "Rect.IntersectCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 0), 1)}, want: pixel.V(1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps top-left corner", + name: "Rect.IntersectCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 10), 1)}, want: pixel.V(1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps bottom-right corner", + name: "Rect.IntersectCircle(): circle overlaps bottom-right corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(10, 0), 1)}, want: pixel.V(-1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps top-right corner", + name: "Rect.IntersectCircle(): circle overlaps top-right corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(10, 10), 1)}, want: pixel.V(-1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps two corners", + name: "Rect.IntersectCircle(): circle overlaps two corners", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 6)}, want: pixel.V(6, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps left edge", + name: "Rect.IntersectCircle(): circle overlaps left edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(0, 5), 1)}, want: pixel.V(1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps bottom edge", + name: "Rect.IntersectCircle(): circle overlaps bottom edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 0), 1)}, want: pixel.V(0, 1), }, { - name: "Rect.IntersectsCircle(): circle overlaps right edge", + name: "Rect.IntersectCircle(): circle overlaps right edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(10, 5), 1)}, want: pixel.V(-1, 0), }, { - name: "Rect.IntersectsCircle(): circle overlaps top edge", + name: "Rect.IntersectCircle(): circle overlaps top edge", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(5, 10), 1)}, want: pixel.V(0, -1), }, { - name: "Rect.IntersectsCircle(): edge is tangent", + name: "Rect.IntersectCircle(): edge is tangent", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(-1, 5), 1)}, want: pixel.ZV, @@ -640,8 +640,8 @@ func TestRect_IntersectsCircle(t *testing.T) { Min: tt.fields.Min, Max: tt.fields.Max, } - if got := r.IntersectsCircle(tt.args.c); got != tt.want { - t.Errorf("Rect.IntersectsCircle() = %v, want %v", got, tt.want) + if got := r.IntersectCircle(tt.args.c); got != tt.want { + t.Errorf("Rect.IntersectCircle() = %v, want %v", got, tt.want) } }) } From b61602cdf4278c13ab3d016efeb30f374044a511 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:12:54 +0000 Subject: [PATCH 068/156] fixed area tests --- geometry_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 1ecc58e0..cc68faed 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -288,7 +288,7 @@ func TestCircle_Area(t *testing.T) { { name: "Circle.Area(): positive radius", fields: fields{radius: 10, center: pixel.ZV}, - want: 20 * math.Pi, + want: 100 * math.Pi, }, { name: "Circle.Area(): zero radius", @@ -298,7 +298,7 @@ func TestCircle_Area(t *testing.T) { { name: "Circle.Area(): negative radius", fields: fields{radius: -5, center: pixel.ZV}, - want: -10 * math.Pi, + want: 25 * math.Pi, }, } for _, tt := range tests { From cc9f03d3937a4eba4cbc5187e0b6a22c63979bfc Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:15:26 +0000 Subject: [PATCH 069/156] corrected function preambles --- geometry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index 8cd49e8d..465cc2fa 100644 --- a/geometry.go +++ b/geometry.go @@ -322,7 +322,7 @@ func (r Rect) Intersect(s Rect) Rect { // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // -// This function will return true if: +// This function will return a non-zero vector if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully func (r Rect) IntersectCircle(c Circle) Vec { @@ -480,7 +480,7 @@ func (c Circle) Intersect(d Circle) Circle { // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // -// This function will return true if: +// This function will return a non-zero vector if: // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully func (c Circle) IntersectRect(r Rect) Vec { From 6f1e3bbbf817ea97809089eb4d80a802dcef8ab2 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 14 Feb 2019 14:18:14 +0000 Subject: [PATCH 070/156] More intersection tests --- geometry_test.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/geometry_test.go b/geometry_test.go index cc68faed..57d8b4d5 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -628,11 +628,41 @@ func TestRect_IntersectCircle(t *testing.T) { want: pixel.V(0, -1), }, { - name: "Rect.IntersectCircle(): edge is tangent", + name: "Rect.IntersectCircle(): edge is tangent of left side", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, args: args{c: pixel.C(pixel.V(-1, 5), 1)}, want: pixel.ZV, }, + { + name: "Rect.IntersectCircle(): edge is tangent of top side", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, -1), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle above rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, 12), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle below rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, -2), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle left of rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(-1, 5), 1)}, + want: pixel.ZV, + }, + { + name: "Rect.IntersectCircle(): circle right of rectangle", + fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(11, 5), 1)}, + want: pixel.ZV, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 680d16c17ba4f2d52d303b641fc5cbde63c06874 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 18 Feb 2019 12:03:54 +0000 Subject: [PATCH 071/156] Corrected returned Vectors for corner overlaps --- geometry.go | 65 +++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/geometry.go b/geometry.go index 465cc2fa..1e2922de 100644 --- a/geometry.go +++ b/geometry.go @@ -484,9 +484,6 @@ func (c Circle) Intersect(d Circle) Circle { // - The Rect contains the Circle, partially or fully // - The Circle contains the Rect, partially of fully func (c Circle) IntersectRect(r Rect) Vec { - // h and v will hold the minimum horizontal and vertical distances (respectively) to avoid overlapping - var h, v float64 - // Checks if the c.Center is not in the diagonal quadrants of the rectangle if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) { // 'grow' the Rect by c.Radius in each orthagonal @@ -498,8 +495,8 @@ func (c Circle) IntersectRect(r Rect) Vec { // Get minimum distance to travel out of Rect rToC := r.Center().To(c.Center) - h = c.Radius - math.Abs(rToC.X) + (r.W() / 2) - v = c.Radius - math.Abs(rToC.Y) + (r.H() / 2) + h := c.Radius - math.Abs(rToC.X) + (r.W() / 2) + v := c.Radius - math.Abs(rToC.Y) + (r.H() / 2) if rToC.X < 0 { h = -h @@ -507,48 +504,52 @@ func (c Circle) IntersectRect(r Rect) Vec { if rToC.Y < 0 { v = -v } + + // No intersect + if h == 0 && v == 0 { + return ZV + } + + if math.Abs(h) > math.Abs(v) { + // Vertical distance shorter + return V(0, v) + } + return V(h, 0) } else { // The center is in the diagonal quadrants + + // Helper points to make code below easy to read. + rectTopLeft := V(r.Min.X, r.Max.Y) + rectBottomRight := V(r.Max.X, r.Min.Y) + + // Check for overlap. + if !(c.Contains(r.Min) || c.Contains(r.Max) || c.Contains(rectTopLeft) || c.Contains(rectBottomRight)) { + // No overlap. + return ZV + } + + var centerToCorner Vec if c.Center.To(r.Min).Len() <= c.Radius { // Closest to bottom-left - cornerToCenter := r.Min.To(c.Center) - // Get the horizontal and vertical overlaps - h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)) - v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))) + centerToCorner = c.Center.To(r.Min) } if c.Center.To(r.Max).Len() <= c.Radius { // Closest to top-right - cornerToCenter := r.Max.To(c.Center) - // Get the horizontal and vertical overlaps - h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)) - v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)) + centerToCorner = c.Center.To(r.Max) } - if c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius { + if c.Center.To(rectTopLeft).Len() <= c.Radius { // Closest to top-left - cornerToCenter := V(r.Min.X, r.Max.Y).To(c.Center) - // Get the horizontal and vertical overlaps - h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))) - v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)) + centerToCorner = c.Center.To(rectTopLeft) } - if c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius { + if c.Center.To(rectBottomRight).Len() <= c.Radius { // Closest to bottom-right - cornerToCenter := V(r.Max.X, r.Min.Y).To(c.Center) - // Get the horizontal and vertical overlaps - h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))) - v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))) + centerToCorner = c.Center.To(rectBottomRight) } - } - // No intersect - if h == 0 && v == 0 { - return ZV - } + cornerToCircumferenceLen := c.Radius - centerToCorner.Len() - if math.Abs(h) > math.Abs(v) { - // Vertical distance shorter - return V(0, v) + return centerToCorner.Unit().Scaled(cornerToCircumferenceLen) } - return V(h, 0) } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such From b3246f6234b4c020ad188b09649c7c5e98731d97 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 18 Feb 2019 12:16:16 +0000 Subject: [PATCH 072/156] Updated tests --- geometry_test.go | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 57d8b4d5..dfa78cf0 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -542,6 +542,19 @@ func TestCircle_Intersect(t *testing.T) { } func TestRect_IntersectCircle(t *testing.T) { + // closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them. + // Effectively this compares two floats to a given decimal point. + // Example: + // closeEnough(100.125342432, 100.125, 2) == true + // closeEnough(math.Pi, 3.14, 2) == true + // closeEnough(0.1234, 0.1245, 3) == false + closeEnough := func(got, expected float64, decimalAccuracy int) bool { + gotShifted := got * math.Pow10(decimalAccuracy) + expectedShifted := expected * math.Pow10(decimalAccuracy) + + return math.Trunc(gotShifted) == math.Trunc(expectedShifted) + } + type fields struct { Min pixel.Vec Max pixel.Vec @@ -576,26 +589,26 @@ func TestRect_IntersectCircle(t *testing.T) { { name: "Rect.IntersectCircle(): circle overlaps bottom-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(0, 0), 1)}, - want: pixel.V(1, 0), + args: args{c: pixel.C(pixel.V(-0.5, -0.5), 1)}, + want: pixel.V(-0.2, -0.2), }, { name: "Rect.IntersectCircle(): circle overlaps top-left corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(0, 10), 1)}, - want: pixel.V(1, 0), + args: args{c: pixel.C(pixel.V(-0.5, 10.5), 1)}, + want: pixel.V(-0.2, 0.2), }, { name: "Rect.IntersectCircle(): circle overlaps bottom-right corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(10, 0), 1)}, - want: pixel.V(-1, 0), + args: args{c: pixel.C(pixel.V(10.5, -0.5), 1)}, + want: pixel.V(0.2, -0.2), }, { name: "Rect.IntersectCircle(): circle overlaps top-right corner", fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(10, 10), 1)}, - want: pixel.V(-1, 0), + args: args{c: pixel.C(pixel.V(10.5, 10.5), 1)}, + want: pixel.V(0.2, 0.2), }, { name: "Rect.IntersectCircle(): circle overlaps two corners", @@ -670,7 +683,8 @@ func TestRect_IntersectCircle(t *testing.T) { Min: tt.fields.Min, Max: tt.fields.Max, } - if got := r.IntersectCircle(tt.args.c); got != tt.want { + got := r.IntersectCircle(tt.args.c) + if !closeEnough(got.X, tt.want.X, 2) || !closeEnough(got.Y, tt.want.Y, 2) { t.Errorf("Rect.IntersectCircle() = %v, want %v", got, tt.want) } }) From 489b03138f19d9b126c66fb22813ff71b1d17b5a Mon Sep 17 00:00:00 2001 From: CodeLingo Bot Date: Mon, 11 Mar 2019 00:45:01 +0000 Subject: [PATCH 073/156] Fix function comments based on best practices from Effective Go Signed-off-by: CodeLingo Bot --- pixelgl/input.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixelgl/input.go b/pixelgl/input.go index 5cf2d9aa..aa16b645 100644 --- a/pixelgl/input.go +++ b/pixelgl/input.go @@ -54,7 +54,7 @@ func (w *Window) SetMousePosition(v pixel.Vec) { }) } -// MouseEntered returns true if the mouse position is within the Window's Bounds. +// MouseInsideWindow returns true if the mouse position is within the Window's Bounds. func (w *Window) MouseInsideWindow() bool { return w.cursorInsideWindow } From 09b50d22963e0d9bc4223c1db49f8bb0ea246ba5 Mon Sep 17 00:00:00 2001 From: faiface Date: Mon, 18 Mar 2019 22:55:57 +0100 Subject: [PATCH 074/156] add Gizmo to examples part of README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27cfe9d5..eedb406d 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ Here are some screenshots from the examples! | --- | --- | | ![Smoke](https://github.com/faiface/pixel-examples/blob/master/smoke/screenshot.png) | ![Typewriter](https://github.com/faiface/pixel-examples/blob/master/typewriter/screenshot.png) | -| [Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster) | [gonutz' No-Brain Jogging](https://github.com/gonutz/no-brain-jogging) | +| [Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster) | [Gizmo](https://github.com/Lallassu/gizmo) | | --- | --- | -| ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![NoBrainJogging](https://raw.githubusercontent.com/gonutz/no-brain-jogging/master/screenshots/screenshot.png) | +| ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![Gizmo](https://github.com/Lallassu/gizmo/blob/master/preview.png) | ## Features From 3d2f083f70de1aa2469d2c9e98e2c77e61095638 Mon Sep 17 00:00:00 2001 From: tobaloidee <36028424+Tobaloidee@users.noreply.github.com> Date: Wed, 20 Mar 2019 11:09:54 +0800 Subject: [PATCH 075/156] Create logo --- logo/logo | 1 + 1 file changed, 1 insertion(+) create mode 100644 logo/logo diff --git a/logo/logo b/logo/logo new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/logo/logo @@ -0,0 +1 @@ + From c7836cb3ad23dce8c5387ae35816d9111f062723 Mon Sep 17 00:00:00 2001 From: tobaloidee <36028424+Tobaloidee@users.noreply.github.com> Date: Wed, 20 Mar 2019 11:10:30 +0800 Subject: [PATCH 076/156] logo upload --- logo/LOGOTYPE-HORIZONTAL-BLACK.png | Bin 0 -> 12674 bytes logo/LOGOTYPE-HORIZONTAL-BLUE.png | Bin 0 -> 19747 bytes logo/LOGOTYPE-HORIZONTAL-MAROON.png | Bin 0 -> 18259 bytes logo/LOGOTYPE-HORIZONTAL-PURPLE.png | Bin 0 -> 20094 bytes logo/LOGOTYPE-HORIZONTAL-RED.png | Bin 0 -> 17148 bytes logo/LOGOTYPE-HORIZONTAL-WHITE.png | Bin 0 -> 12371 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 logo/LOGOTYPE-HORIZONTAL-BLACK.png create mode 100644 logo/LOGOTYPE-HORIZONTAL-BLUE.png create mode 100644 logo/LOGOTYPE-HORIZONTAL-MAROON.png create mode 100644 logo/LOGOTYPE-HORIZONTAL-PURPLE.png create mode 100644 logo/LOGOTYPE-HORIZONTAL-RED.png create mode 100644 logo/LOGOTYPE-HORIZONTAL-WHITE.png diff --git a/logo/LOGOTYPE-HORIZONTAL-BLACK.png b/logo/LOGOTYPE-HORIZONTAL-BLACK.png new file mode 100644 index 0000000000000000000000000000000000000000..2122d1bbbd357ee7a91c6817baed2a2d6cc07927 GIT binary patch literal 12674 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 z zuK{qau2-r-;kAmuB9SbG|6B`V6l=$i`3Ys2|MKrhfHa%YCJxiRc3$2F=Yj@6`fn$! z&OXDGe53=I@c*3>dXMQIcg{Ei=|wB1-k?4Cv@y@or2B=b1v=FPZ6xqIy=t#PINWDi z;}{c3UO%xy9q%&=Fc8kYHA#K^S%L;+!B%leTnI*7eU%|Rd8-5f6ZBfkxix`dvg930 z?e!^IaEKNx3=Sjz=~ar!YLt3|Zu$qI>&%lHb@}A-k}Msxf`>3o)|<>jt}2jL8>}YC zLU2#A?O0Re%eZ*8fC8Lznvnnjh7qKI1~5#}{<8vL#F}?vmBUfUYk#su%QfLia%o3$ zw2YlATKx1e+V3y(Ykev?WKu$jhk{i3ATGM^%Zq93j_yJ4g_{J+RgVV|PJOx!xllzi zO;Mnl!*S6sP%d*g!6kXI){re(NH8B-dBpqVC7oWK4Rf%ynvu@s5}(K^6V;Si8RLsmex5j#};S?ZQIoW69cWgXqyA+tzH}lFprTbRZQe(c=EYFkeTB z>c*TGSL4pI_s#fVmH1PdSa_@;xc3SWWFDNAa0D=vwd-Y> zPwPcM-oPHK>dAmw*?Cax>f|qh_&$Rle)emqprbe8cPc%k2GcdtSz=*iw>d@@gwA(u z0XHglz z(*GV!$zAt=nqp5|EFYy$KXr^gD@cC+;5 z4nKBKcnUS~F|5=z8|k0Q7kw-e7quZL;Bm#?Ap?sl2Tu%UZ`vbnX$&}*ULBFNSI>^MCJ-<~Mjdhew&?%XiK#w=l#i`Rm zeQNgnF|C0(EpLRHb<^u=<(Gk^BK$e%7Tiy(bc>K4WlY18cVl2X@R1&V}}|#G0T4 zXv*Kd8xzJxe=?+Pec7Xn7A<#oP`^NTFk7pY+0WR92>+kzFbah4eWR+F^o!_F8O=>z zLS)y)U&!h7>lmoLg=UetUz^yq?)tzC`Z3P0&Tqi#dY{vi)L8)4kMqs;Z(u9VnbgpT z>*WtWq>T1XZLYBerdP#*u#BgAr7rs>g`#ECPpL`+71zRyDh;1$lIdPKdy67Oz3H0N zwlPq3O!z?RiW7)wx0g|YHiQ||;d-utN6;B!^UT_jlh@&~&$)RZTezqLFx&En*P;UA zq@I_I@LoX(EjWhj$%ASKM^iW~JO}Bx+_&w?(eJ}&q%usL*X4`ZL&IQt&}7%q1NrdP z$tTAGTxYv1vF+ouiq4sUa`DZJKw_DCNp3;I=WYe#{X^%mI?5$9>F@sB%$~VZ0Acj4 z0t9I*UF~x5)P~vAOZ?n_Jx^EMVpwh?a^3W_^G1oJ@l7YEguP4f_8{apLBc~dQE^gX zxWfnE&-KiJ&vlrc#3|Y`ihCg{l?cnNu{T6PjbIs$MGdPvfz$iZno><{>GH16gJoT` zyG}1|n3ukgs8B7o4uv=mqFuVKDMD#ZOzUM=984U_Ji~@Bi%8prc7WL7D*>#|O;9On zv_M$Qrj$=L`!-8Sav2>oh+~_GoR867d1DC;j?m*^iCp1`r25t8WA5|L)7f(IF{=we z2H~yuG2hDdHbrs7s0QH1=nk%J&M*N+%IstT;YNXUjw!qw5+ji(SP2*~1&lsw;DAFs8j`DQ(j`*w!CPa0Dxrfi*R zS(VROQHRhol#!fkVFpWRK}UyS9mk~Rvs3FMzA4PhKJ2uK048Bdd#%9j`&{=lG`rSv z{J-mEnCrYR*qz*pKk#~!SHA4Wycxib2yhYFdAO2T6`}_dEz#UY&n*8NNr z%~J*z>+w#yF#WliGRmsB|rhDgT`t;6}uB zYk{L6PM&OlX>z++^MtAw%*8BWVdNh4h>AQ%Q9uDWmW9su=FGJL&Z)$LG} z6sjBVgVsz{5G`83&eo3IXeN#FDe~U|mj68w^?#Tc`rnKG|IbtXkCXkMo8|g{O=JB_ e)<^zY0bp=YX6T(N_<|X712pgJsg^6-h5tX}8X2|# literal 0 HcmV?d00001 diff --git a/logo/LOGOTYPE-HORIZONTAL-BLUE.png b/logo/LOGOTYPE-HORIZONTAL-BLUE.png new file mode 100644 index 0000000000000000000000000000000000000000..0a8d1e12ea1036a4ff5c5a47bc1e5c0173b2d228 GIT binary patch literal 19747 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 Q;I3r`Pgg&ebxsLQ06LH#ZvX%Q literal 0 HcmV?d00001 diff --git a/logo/LOGOTYPE-HORIZONTAL-PURPLE.png b/logo/LOGOTYPE-HORIZONTAL-PURPLE.png new file mode 100644 index 0000000000000000000000000000000000000000..d22d0cbca23e79379c6a8639aa35218747d4114a GIT binary patch literal 20094 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 Ge*OcnLo~4f literal 0 HcmV?d00001 diff --git a/logo/LOGOTYPE-HORIZONTAL-RED.png b/logo/LOGOTYPE-HORIZONTAL-RED.png new file mode 100644 index 0000000000000000000000000000000000000000..eb7a8bd231e6d068eec48fff623af38581b45269 GIT binary patch literal 17148 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�wWYcLH$Rbx7r=5{HOyHqctl< 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 ZV7jKXM(inWAPe`WrLLz|qhc5HKLD|e4T%5% literal 0 HcmV?d00001 From c9328c9260a4c65b1402c8a6f1d0414947138fe5 Mon Sep 17 00:00:00 2001 From: tobaloidee <36028424+Tobaloidee@users.noreply.github.com> Date: Wed, 20 Mar 2019 11:12:07 +0800 Subject: [PATCH 077/156] Delete logo --- logo/logo | 1 - 1 file changed, 1 deletion(-) delete mode 100644 logo/logo diff --git a/logo/logo b/logo/logo deleted file mode 100644 index 8b137891..00000000 --- a/logo/logo +++ /dev/null @@ -1 +0,0 @@ - From 0475b1ddb4247ccbee3433b89f07aa4ee5c46653 Mon Sep 17 00:00:00 2001 From: tobaloidee <36028424+Tobaloidee@users.noreply.github.com> Date: Wed, 20 Mar 2019 11:16:49 +0800 Subject: [PATCH 078/156] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index eedb406d..eaad6132 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +

+ # Pixel [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From 86de5123c0ccf30b214ac352cec460cfffca08e2 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 26 Mar 2019 15:25:41 +0000 Subject: [PATCH 079/156] Added related libraries --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index eaad6132..eda768f5 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,12 @@ Here's the list of the main features in Pixel. Although Pixel is still under hea arbitrarily crazy stuff (e.g. graphical effects) - Small codebase, ~5K lines of code, including the backend [glhf](https://github.com/faiface/glhf) package + +## Related repositories + +Here are some packages which use Pixel: + - [TilePix](https://github.com/bcvery1/tilepix) Makes handling TMX files built with [Tiled](https://www.mapeditor.org/) + trivially easy to work with using Pixel. ## Missing features From 50ef216e791784461a98afe1af4aa878ed066830 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 26 Mar 2019 15:41:32 +0000 Subject: [PATCH 080/156] Removing wip files --- batch_test.go | 154 ----------- compose_test.go | 101 ------- data_test.go | 325 ---------------------- go.mod | 12 - go.sum | 16 -- rect_test.go | 609 ----------------------------------------- sprite_test.go | 122 --------- vec_test.go | 707 ------------------------------------------------ 8 files changed, 2046 deletions(-) delete mode 100644 batch_test.go delete mode 100644 compose_test.go delete mode 100644 data_test.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 rect_test.go delete mode 100644 sprite_test.go delete mode 100644 vec_test.go diff --git a/batch_test.go b/batch_test.go deleted file mode 100644 index a8df400d..00000000 --- a/batch_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package pixel_test - -import ( - "image/color" - "reflect" - "testing" - - "github.com/faiface/pixel" -) - -func TestNewBatch(t *testing.T) { - type args struct { - container pixel.Triangles - pic pixel.Picture - } - tests := []struct { - name string - args args - want *pixel.Batch - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.NewBatch(tt.args.container, tt.args.pic); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewBatch() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestBatch_Dirty(t *testing.T) { - tests := []struct { - name string - b *pixel.Batch - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.b.Dirty() - }) - } -} - -func TestBatch_Clear(t *testing.T) { - tests := []struct { - name string - b *pixel.Batch - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.b.Clear() - }) - } -} - -func TestBatch_Draw(t *testing.T) { - type args struct { - t pixel.Target - } - tests := []struct { - name string - b *pixel.Batch - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.b.Draw(tt.args.t) - }) - } -} - -func TestBatch_SetMatrix(t *testing.T) { - type args struct { - m pixel.Matrix - } - tests := []struct { - name string - b *pixel.Batch - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.b.SetMatrix(tt.args.m) - }) - } -} - -func TestBatch_SetColorMask(t *testing.T) { - type args struct { - c color.Color - } - tests := []struct { - name string - b *pixel.Batch - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.b.SetColorMask(tt.args.c) - }) - } -} - -func TestBatch_MakeTriangles(t *testing.T) { - type args struct { - t pixel.Triangles - } - tests := []struct { - name string - b *pixel.Batch - args args - want pixel.TargetTriangles - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.b.MakeTriangles(tt.args.t); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Batch.MakeTriangles() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestBatch_MakePicture(t *testing.T) { - type args struct { - p pixel.Picture - } - tests := []struct { - name string - b *pixel.Batch - args args - want pixel.TargetPicture - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.b.MakePicture(tt.args.p); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Batch.MakePicture() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/compose_test.go b/compose_test.go deleted file mode 100644 index 2bddf0f1..00000000 --- a/compose_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package pixel_test - -import ( - "reflect" - "testing" - - "github.com/faiface/pixel" -) - -func TestComposeMethod_Compose(t *testing.T) { - type args struct { - a pixel.RGBA - b pixel.RGBA - } - - a := pixel.RGBA{R: 200, G: 200, B: 200, A: .8} - b := pixel.RGBA{R: 100, G: 100, B: 100, A: .5} - c := pixel.RGBA{R: 200, G: 200, B: 200, A: .5} - - tests := []struct { - name string - cm pixel.ComposeMethod - args args - want pixel.RGBA - }{ - { - name: "ComposeMethod.Compose: ComposeOver", - cm: pixel.ComposeOver, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 220, G: 220, B: 220, A: .9}, - }, - { - name: "ComposeMethod.Compose: ComposeIn", - cm: pixel.ComposeIn, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 100, G: 100, B: 100, A: .4}, - }, - { - name: "ComposeMethod.Compose: ComposeOut", - cm: pixel.ComposeOut, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 100, G: 100, B: 100, A: .4}, - }, - { - name: "ComposeMethod.Compose: ComposeAtop", - cm: pixel.ComposeAtop, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 120, G: 120, B: 120, A: .5}, - }, - { - name: "ComposeMethod.Compose: ComposeRover", - cm: pixel.ComposeRover, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 200, G: 200, B: 200, A: .9}, - }, - { - name: "ComposeMethod.Compose: ComposeRin", - cm: pixel.ComposeRin, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 80, G: 80, B: 80, A: .4}, - }, - { - name: "ComposeMethod.Compose: ComposeRout", - cm: pixel.ComposeRout, - // Using 'c' here to make the "want"ed RGBA rational - args: args{a: c, b: b}, - want: pixel.RGBA{R: 50, G: 50, B: 50, A: .25}, - }, - { - name: "ComposeMethod.Compose: ComposeRatop", - cm: pixel.ComposeRatop, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 180, G: 180, B: 180, A: .8}, - }, - { - name: "ComposeMethod.Compose: ComposeXor", - cm: pixel.ComposeXor, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 120, G: 120, B: 120, A: .5}, - }, - { - name: "ComposeMethod.Compose: ComposePlus", - cm: pixel.ComposePlus, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 300, G: 300, B: 300, A: 1.3}, - }, - { - name: "ComposeMethod.Compose: ComposeCopy", - cm: pixel.ComposeCopy, - args: args{a: a, b: b}, - want: pixel.RGBA{R: 200, G: 200, B: 200, A: .8}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.cm.Compose(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { - t.Errorf("ComposeMethod.Compose() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/data_test.go b/data_test.go deleted file mode 100644 index 8c621bbd..00000000 --- a/data_test.go +++ /dev/null @@ -1,325 +0,0 @@ -package pixel_test - -import ( - "image" - "reflect" - "testing" - - "github.com/faiface/pixel" -) - -func TestMakeTrianglesData(t *testing.T) { - type args struct { - len int - } - tests := []struct { - name string - args args - want *pixel.TrianglesData - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.MakeTrianglesData(tt.args.len); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MakeTrianglesData() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTrianglesData_Len(t *testing.T) { - tests := []struct { - name string - td *pixel.TrianglesData - want int - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.td.Len(); got != tt.want { - t.Errorf("TrianglesData.Len() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTrianglesData_SetLen(t *testing.T) { - type args struct { - len int - } - tests := []struct { - name string - td *pixel.TrianglesData - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.td.SetLen(tt.args.len) - }) - } -} - -func TestTrianglesData_Slice(t *testing.T) { - type args struct { - i int - j int - } - tests := []struct { - name string - td *pixel.TrianglesData - args args - want pixel.Triangles - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.td.Slice(tt.args.i, tt.args.j); !reflect.DeepEqual(got, tt.want) { - t.Errorf("TrianglesData.Slice() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTrianglesData_Update(t *testing.T) { - type args struct { - t pixel.Triangles - } - tests := []struct { - name string - td *pixel.TrianglesData - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.td.Update(tt.args.t) - }) - } -} - -func TestTrianglesData_Copy(t *testing.T) { - tests := []struct { - name string - td *pixel.TrianglesData - want pixel.Triangles - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.td.Copy(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("TrianglesData.Copy() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTrianglesData_Position(t *testing.T) { - type args struct { - i int - } - tests := []struct { - name string - td *pixel.TrianglesData - args args - want pixel.Vec - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.td.Position(tt.args.i); !reflect.DeepEqual(got, tt.want) { - t.Errorf("TrianglesData.Position() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTrianglesData_Color(t *testing.T) { - type args struct { - i int - } - tests := []struct { - name string - td *pixel.TrianglesData - args args - want pixel.RGBA - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.td.Color(tt.args.i); !reflect.DeepEqual(got, tt.want) { - t.Errorf("TrianglesData.Color() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTrianglesData_Picture(t *testing.T) { - type args struct { - i int - } - tests := []struct { - name string - td *pixel.TrianglesData - args args - wantPic pixel.Vec - wantIntensity float64 - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotPic, gotIntensity := tt.td.Picture(tt.args.i) - if !reflect.DeepEqual(gotPic, tt.wantPic) { - t.Errorf("TrianglesData.Picture() gotPic = %v, want %v", gotPic, tt.wantPic) - } - if gotIntensity != tt.wantIntensity { - t.Errorf("TrianglesData.Picture() gotIntensity = %v, want %v", gotIntensity, tt.wantIntensity) - } - }) - } -} - -func TestMakePictureData(t *testing.T) { - type args struct { - rect pixel.Rect - } - tests := []struct { - name string - args args - want *pixel.PictureData - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.MakePictureData(tt.args.rect); !reflect.DeepEqual(got, tt.want) { - t.Errorf("MakePictureData() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestPictureDataFromImage(t *testing.T) { - type args struct { - img image.Image - } - tests := []struct { - name string - args args - want *pixel.PictureData - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.PictureDataFromImage(tt.args.img); !reflect.DeepEqual(got, tt.want) { - t.Errorf("PictureDataFromImage() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestPictureDataFromPicture(t *testing.T) { - type args struct { - pic pixel.Picture - } - tests := []struct { - name string - args args - want *pixel.PictureData - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.PictureDataFromPicture(tt.args.pic); !reflect.DeepEqual(got, tt.want) { - t.Errorf("PictureDataFromPicture() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestPictureData_Image(t *testing.T) { - tests := []struct { - name string - pd *pixel.PictureData - want *image.RGBA - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.pd.Image(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("PictureData.Image() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestPictureData_Index(t *testing.T) { - type args struct { - at pixel.Vec - } - tests := []struct { - name string - pd *pixel.PictureData - args args - want int - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.pd.Index(tt.args.at); got != tt.want { - t.Errorf("PictureData.Index() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestPictureData_Bounds(t *testing.T) { - tests := []struct { - name string - pd *pixel.PictureData - want pixel.Rect - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.pd.Bounds(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("PictureData.Bounds() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestPictureData_Color(t *testing.T) { - type args struct { - at pixel.Vec - } - tests := []struct { - name string - pd *pixel.PictureData - args args - want pixel.RGBA - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.pd.Color(tt.args.at); !reflect.DeepEqual(got, tt.want) { - t.Errorf("PictureData.Color() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 96ab6ebd..00000000 --- a/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -module github.com/faiface/pixel - -require ( - github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 - github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 - github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 // indirect - github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f - github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/pkg/errors v0.8.1 - golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 0860a70e..00000000 --- a/go.sum +++ /dev/null @@ -1,16 +0,0 @@ -github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= -github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= -github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= -github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= -github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk= -github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= -github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM= -github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a h1:2n5w2v3knlspzjJWyQPC0j88Mwvq0SZV0Jdws34GJwc= -github.com/go-gl/mathgl v0.0.0-20180804195959-cdf14b6b8f8a/go.mod h1:dvrdneKbyWbK2skTda0nM4B9zSlS2GZSnjX7itr/skQ= -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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM= -golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= diff --git a/rect_test.go b/rect_test.go deleted file mode 100644 index 87250536..00000000 --- a/rect_test.go +++ /dev/null @@ -1,609 +0,0 @@ -package pixel_test - -import ( - "fmt" - "reflect" - "testing" - - "github.com/faiface/pixel" -) - -var ( - squareAroundOrigin = pixel.R(-10, -10, 10, 10) - squareAround2020 = pixel.R(10, 10, 30, 30) - rectangleAroundOrigin = pixel.R(-20, -10, 20, 10) - rectangleAround2020 = pixel.R(0, 10, 40, 30) -) - -func TestR(t *testing.T) { - type args struct { - minX float64 - minY float64 - maxX float64 - maxY float64 - } - tests := []struct { - name string - args args - want pixel.Rect - }{ - { - name: "R(): square around origin", - args: args{minX: -10, minY: -10, maxX: 10, maxY: 10}, - want: squareAroundOrigin, - }, - { - name: "R(): square around 20 20", - args: args{minX: 10, minY: 10, maxX: 30, maxY: 30}, - want: squareAround2020, - }, - { - name: "R(): rectangle around origin", - args: args{minX: -20, minY: -10, maxX: 20, maxY: 10}, - want: rectangleAroundOrigin, - }, - { - name: "R(): square around 20 20", - args: args{minX: 0, minY: 10, maxX: 40, maxY: 30}, - want: rectangleAround2020, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.R(tt.args.minX, tt.args.minY, tt.args.maxX, tt.args.maxY); !reflect.DeepEqual(got, tt.want) { - t.Errorf("R() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_String(t *testing.T) { - tests := []struct { - name string - r pixel.Rect - want string - }{ - { - name: "Rect.String(): square around origin", - r: squareAroundOrigin, - want: "Rect(-10, -10, 10, 10)", - }, - { - name: "Rect.String(): square around 20 20", - r: squareAround2020, - want: "Rect(10, 10, 30, 30)", - }, - { - name: "Rect.String(): rectangle around origin", - r: rectangleAroundOrigin, - want: "Rect(-20, -10, 20, 10)", - }, - { - name: "Rect.String(): square around 20 20", - r: rectangleAround2020, - want: "Rect(0, 10, 40, 30)", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.String(); got != tt.want { - t.Errorf("Rect.String() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Norm(t *testing.T) { - tests := []struct { - name string - r pixel.Rect - want pixel.Rect - }{ - { - name: "Rect.Norm(): square around origin", - r: squareAroundOrigin, - want: squareAroundOrigin, - }, - { - name: "Rect.Norm(): square around 20 20", - r: squareAround2020, - want: squareAround2020, - }, - { - name: "Rect.Norm(): rectangle around origin", - r: rectangleAroundOrigin, - want: rectangleAroundOrigin, - }, - { - name: "Rect.Norm(): square around 20 20", - r: rectangleAround2020, - want: rectangleAround2020, - }, - { - name: "Rect.Norm(): square around origin unnormalized", - r: pixel.Rect{Min: squareAroundOrigin.Max, Max: squareAroundOrigin.Min}, - want: squareAroundOrigin, - }, - { - name: "Rect.Norm(): square around 20 20 unnormalized", - r: pixel.Rect{Min: squareAround2020.Max, Max: squareAround2020.Min}, - want: squareAround2020, - }, - { - name: "Rect.Norm(): rectangle around origin unnormalized", - r: pixel.Rect{Min: rectangleAroundOrigin.Max, Max: rectangleAroundOrigin.Min}, - want: rectangleAroundOrigin, - }, - { - name: "Rect.Norm(): square around 20 20 unnormalized", - r: pixel.Rect{Min: rectangleAround2020.Max, Max: rectangleAround2020.Min}, - want: rectangleAround2020, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Norm(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.Norm() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_W(t *testing.T) { - tests := []struct { - name string - r pixel.Rect - want float64 - }{ - { - name: "Rect.W(): square around origin", - r: squareAroundOrigin, - want: 20, - }, - { - name: "Rect.W(): square around 20 20", - r: squareAround2020, - want: 20, - }, - { - name: "Rect.W(): rectangle around origin", - r: rectangleAroundOrigin, - want: 40, - }, - { - name: "Rect.W(): square around 20 20", - r: rectangleAround2020, - want: 40, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.W(); got != tt.want { - t.Errorf("Rect.W() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_H(t *testing.T) { - tests := []struct { - name string - r pixel.Rect - want float64 - }{ - { - name: "Rect.H(): square around origin", - r: squareAroundOrigin, - want: 20, - }, - { - name: "Rect.H(): square around 20 20", - r: squareAround2020, - want: 20, - }, - { - name: "Rect.H(): rectangle around origin", - r: rectangleAroundOrigin, - want: 20, - }, - { - name: "Rect.H(): square around 20 20", - r: rectangleAround2020, - want: 20, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.H(); got != tt.want { - t.Errorf("Rect.H() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Size(t *testing.T) { - tests := []struct { - name string - r pixel.Rect - want pixel.Vec - }{ - { - name: "Rect.Size(): square around origin", - r: squareAroundOrigin, - want: pixel.V(20, 20), - }, - { - name: "Rect.Size(): square around 20 20", - r: squareAround2020, - want: pixel.V(20, 20), - }, - { - name: "Rect.Size(): rectangle around origin", - r: rectangleAroundOrigin, - want: pixel.V(40, 20), - }, - { - name: "Rect.Size(): square around 20 20", - r: rectangleAround2020, - want: pixel.V(40, 20), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Size(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.Size() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Area(t *testing.T) { - tests := []struct { - name string - r pixel.Rect - want float64 - }{ - { - name: "Rect.Area(): square around origin", - r: squareAroundOrigin, - want: 400, - }, - { - name: "Rect.Area(): square around 20 20", - r: squareAround2020, - want: 400, - }, - { - name: "Rect.Area(): rectangle around origin", - r: rectangleAroundOrigin, - want: 800, - }, - { - name: "Rect.Area(): square around 20 20", - r: rectangleAround2020, - want: 800, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Area(); got != tt.want { - t.Errorf("Rect.Area() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Center(t *testing.T) { - tests := []struct { - name string - r pixel.Rect - want pixel.Vec - }{ - { - name: "Rect.Center(): square around origin", - r: squareAroundOrigin, - want: pixel.V(0, 0), - }, - { - name: "Rect.Center(): square around 20 20", - r: squareAround2020, - want: pixel.V(20, 20), - }, - { - name: "Rect.Center(): rectangle around origin", - r: rectangleAroundOrigin, - want: pixel.V(0, 0), - }, - { - name: "Rect.Center(): square around 20 20", - r: rectangleAround2020, - want: pixel.V(20, 20), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Center(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.Center() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Moved(t *testing.T) { - positiveShift := pixel.V(10, 10) - negativeShift := pixel.V(-10, -10) - - type args struct { - delta pixel.Vec - } - tests := []struct { - name string - r pixel.Rect - args args - want pixel.Rect - }{ - { - name: "Rect.Moved(): positive shift", - r: squareAroundOrigin, - args: args{delta: positiveShift}, - want: pixel.R(0, 0, 20, 20), - }, - { - name: "Rect.Moved(): negative shift", - r: squareAroundOrigin, - args: args{delta: negativeShift}, - want: pixel.R(-20, -20, 0, 0), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.Moved() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Resized(t *testing.T) { - // resize transformations - resizeByHalfAroundCenter := rectTestTransform{"by half around center", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(rect.Center(), rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundMin := rectTestTransform{"by half around Min", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(rect.Min, rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundMax := rectTestTransform{"by half around Max", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(rect.Max, rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundMiddleOfLeftSide := rectTestTransform{"by half around middle of left side", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(pixel.V(rect.Min.X, rect.Center().Y), rect.Size().Scaled(0.5)) - }} - resizeByHalfAroundOrigin := rectTestTransform{"by half around the origin", func(rect pixel.Rect) pixel.Rect { - return rect.Resized(pixel.ZV, rect.Size().Scaled(0.5)) - }} - - testCases := []struct { - input pixel.Rect - transform rectTestTransform - answer pixel.Rect - }{ - {squareAroundOrigin, resizeByHalfAroundCenter, pixel.R(-5, -5, 5, 5)}, - {squareAround2020, resizeByHalfAroundCenter, pixel.R(15, 15, 25, 25)}, - {rectangleAroundOrigin, resizeByHalfAroundCenter, pixel.R(-10, -5, 10, 5)}, - {rectangleAround2020, resizeByHalfAroundCenter, pixel.R(10, 15, 30, 25)}, - - {squareAroundOrigin, resizeByHalfAroundMin, pixel.R(-10, -10, 0, 0)}, - {squareAround2020, resizeByHalfAroundMin, pixel.R(10, 10, 20, 20)}, - {rectangleAroundOrigin, resizeByHalfAroundMin, pixel.R(-20, -10, 0, 0)}, - {rectangleAround2020, resizeByHalfAroundMin, pixel.R(0, 10, 20, 20)}, - - {squareAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 10, 10)}, - {squareAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 30, 30)}, - {rectangleAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 20, 10)}, - {rectangleAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 40, 30)}, - - {squareAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-10, -5, 0, 5)}, - {squareAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(10, 15, 20, 25)}, - {rectangleAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-20, -5, 0, 5)}, - {rectangleAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(0, 15, 20, 25)}, - - {squareAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-5, -5, 5, 5)}, - {squareAround2020, resizeByHalfAroundOrigin, pixel.R(5, 5, 15, 15)}, - {rectangleAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-10, -5, 10, 5)}, - {rectangleAround2020, resizeByHalfAroundOrigin, pixel.R(0, 5, 20, 15)}, - } - - for _, testCase := range testCases { - t.Run(fmt.Sprintf("Resize %v %s", testCase.input, testCase.transform.name), func(t *testing.T) { - testResult := testCase.transform.f(testCase.input) - if testResult != testCase.answer { - t.Errorf("Got: %v, wanted: %v\n", testResult, testCase.answer) - } - }) - } -} - -func TestRect_ResizedMin(t *testing.T) { - grow := pixel.V(5, 5) - shrink := pixel.V(-5, -5) - - type args struct { - size pixel.Vec - } - tests := []struct { - name string - r pixel.Rect - args args - want pixel.Rect - }{ - { - name: "Rect.ResizedMin(): square around origin - growing", - r: squareAroundOrigin, - args: args{size: grow}, - want: pixel.R(-10, -10, -5, -5), - }, - { - name: "Rect.ResizedMin(): square around 20 20 - growing", - r: squareAround2020, - args: args{size: grow}, - want: pixel.R(10, 10, 15, 15), - }, - { - name: "Rect.ResizedMin(): rectangle around origin - growing", - r: rectangleAroundOrigin, - args: args{size: grow}, - want: pixel.R(-20, -10, -15, -5), - }, - { - name: "Rect.ResizedMin(): square around 20 20 - growing", - r: rectangleAround2020, - args: args{size: grow}, - want: pixel.R(0, 10, 5, 15), - }, - { - name: "Rect.ResizedMin(): square around origin - growing", - r: squareAroundOrigin, - args: args{size: shrink}, - want: pixel.R(-10, -10, -15, -15), - }, - { - name: "Rect.ResizedMin(): square around 20 20 - growing", - r: squareAround2020, - args: args{size: shrink}, - want: pixel.R(10, 10, 5, 5), - }, - { - name: "Rect.ResizedMin(): rectangle around origin - growing", - r: rectangleAroundOrigin, - args: args{size: shrink}, - want: pixel.R(-20, -10, -25, -15), - }, - { - name: "Rect.ResizedMin(): square around 20 20 - growing", - r: rectangleAround2020, - args: args{size: shrink}, - want: pixel.R(0, 10, -5, 5), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.ResizedMin(tt.args.size); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.ResizedMin() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Contains(t *testing.T) { - type args struct { - u pixel.Vec - } - tests := []struct { - name string - r pixel.Rect - args args - want bool - }{ - { - name: "Rect.Contains(): square and contained vector", - r: squareAroundOrigin, - args: args{u: pixel.V(-5, 5)}, - want: true, - }, - { - name: "Rect.Contains(): square and seperate vector", - r: squareAroundOrigin, - args: args{u: pixel.V(50, 55)}, - want: false, - }, - { - name: "Rect.Contains(): square and overlapping vector", - r: squareAroundOrigin, - args: args{u: pixel.V(0, 50)}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Contains(tt.args.u); got != tt.want { - t.Errorf("Rect.Contains() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Union(t *testing.T) { - type args struct { - s pixel.Rect - } - tests := []struct { - name string - r pixel.Rect - args args - want pixel.Rect - }{ - { - name: "Rect.Union(): seperate squares", - r: squareAroundOrigin, - args: args{s: squareAround2020}, - want: pixel.R(-10, -10, 30, 30), - }, - { - name: "Rect.Union(): overlapping squares", - r: squareAroundOrigin, - args: args{s: pixel.R(0, 0, 20, 20)}, - want: pixel.R(-10, -10, 20, 20), - }, - { - name: "Rect.Union(): square within a square", - r: squareAroundOrigin, - args: args{s: pixel.R(-5, -5, 5, 5)}, - want: squareAroundOrigin, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Union(tt.args.s); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.Union() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRect_Intersect(t *testing.T) { - type args struct { - s pixel.Rect - } - tests := []struct { - name string - r pixel.Rect - args args - want pixel.Rect - }{ - { - name: "Rect.Union(): seperate squares", - r: squareAroundOrigin, - args: args{s: squareAround2020}, - want: pixel.R(0, 0, 0, 0), - }, - { - name: "Rect.Union(): overlapping squares", - r: squareAroundOrigin, - args: args{s: pixel.R(0, 0, 20, 20)}, - want: pixel.R(0, 0, 10, 10), - }, - { - name: "Rect.Union(): square within a square", - r: squareAroundOrigin, - args: args{s: pixel.R(-5, -5, 5, 5)}, - want: pixel.R(-5, -5, 5, 5), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.r.Intersect(tt.args.s); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.Intersect() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/sprite_test.go b/sprite_test.go deleted file mode 100644 index 9f8b9e9b..00000000 --- a/sprite_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package pixel_test - -import ( - "image/color" - "reflect" - "testing" - - "github.com/faiface/pixel" -) - -func TestNewSprite(t *testing.T) { - type args struct { - pic pixel.Picture - frame pixel.Rect - } - tests := []struct { - name string - args args - want *pixel.Sprite - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.NewSprite(tt.args.pic, tt.args.frame); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewSprite() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSprite_Set(t *testing.T) { - type args struct { - pic pixel.Picture - frame pixel.Rect - } - tests := []struct { - name string - s *pixel.Sprite - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.s.Set(tt.args.pic, tt.args.frame) - }) - } -} - -func TestSprite_Picture(t *testing.T) { - tests := []struct { - name string - s *pixel.Sprite - want pixel.Picture - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.s.Picture(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Sprite.Picture() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSprite_Frame(t *testing.T) { - tests := []struct { - name string - s *pixel.Sprite - want pixel.Rect - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.s.Frame(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Sprite.Frame() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSprite_Draw(t *testing.T) { - type args struct { - t pixel.Target - matrix pixel.Matrix - } - tests := []struct { - name string - s *pixel.Sprite - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.s.Draw(tt.args.t, tt.args.matrix) - }) - } -} - -func TestSprite_DrawColorMask(t *testing.T) { - type args struct { - t pixel.Target - matrix pixel.Matrix - mask color.Color - } - tests := []struct { - name string - s *pixel.Sprite - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.s.DrawColorMask(tt.args.t, tt.args.matrix, tt.args.mask) - }) - } -} diff --git a/vec_test.go b/vec_test.go deleted file mode 100644 index 67275ccb..00000000 --- a/vec_test.go +++ /dev/null @@ -1,707 +0,0 @@ -package pixel_test - -import ( - "math" - "reflect" - "testing" - - "github.com/faiface/pixel" -) - -// closeEnough is a test helper function to establish if vectors are "close enough". This is to resolve floating point -// errors, specifically when dealing with `math.Pi` -func closeEnough(u, v pixel.Vec) bool { - uX, uY := math.Round(u.X), math.Round(u.Y) - vX, vY := math.Round(v.X), math.Round(v.Y) - return uX == vX && uY == vY -} - -func TestV(t *testing.T) { - type args struct { - x float64 - y float64 - } - tests := []struct { - name string - args args - want pixel.Vec - }{ - { - name: "V(): both 0", - args: args{x: 0, y: 0}, - want: pixel.ZV, - }, - { - name: "V(): x < y", - args: args{x: 0, y: 10}, - want: pixel.Vec{X: 0, Y: 10}, - }, - { - name: "V(): x > y", - args: args{x: 10, y: 0}, - want: pixel.Vec{X: 10, Y: 0}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.V(tt.args.x, tt.args.y); !reflect.DeepEqual(got, tt.want) { - t.Errorf("V() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestUnit(t *testing.T) { - type args struct { - angle float64 - } - tests := []struct { - name string - args args - want pixel.Vec - }{ - { - name: "Unit(): 0 radians", - args: args{angle: 0}, - want: pixel.V(1, 0), - }, - { - name: "Unit(): pi radians", - args: args{angle: math.Pi}, - want: pixel.V(-1, 0), - }, - { - name: "Unit(): 10 * pi radians", - args: args{angle: 10 * math.Pi}, - want: pixel.V(1, 0), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.Unit(tt.args.angle); !closeEnough(got, tt.want) { - t.Errorf("Unit() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_String(t *testing.T) { - tests := []struct { - name string - u pixel.Vec - want string - }{ - { - name: "Vec.String(): both 0", - u: pixel.Vec{X: 0, Y: 0}, - want: "Vec(0, 0)", - }, - { - name: "Vec.String(): x < y", - u: pixel.Vec{X: 0, Y: 10}, - want: "Vec(0, 10)", - }, - { - name: "Vec.String(): x > y", - u: pixel.Vec{X: 10, Y: 0}, - want: "Vec(10, 0)", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.String(); got != tt.want { - t.Errorf("Vec.String() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_XY(t *testing.T) { - tests := []struct { - name string - u pixel.Vec - wantX float64 - wantY float64 - }{ - { - name: "Vec.XY(): both 0", - u: pixel.Vec{X: 0, Y: 0}, - wantX: 0, - wantY: 0, - }, - { - name: "Vec.XY(): x < y", - u: pixel.Vec{X: 0, Y: 10}, - wantX: 0, - wantY: 10, - }, - { - name: "Vec.XY(): x > y", - u: pixel.Vec{X: 10, Y: 0}, - wantX: 10, - wantY: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotX, gotY := tt.u.XY() - if gotX != tt.wantX { - t.Errorf("Vec.XY() gotX = %v, want %v", gotX, tt.wantX) - } - if gotY != tt.wantY { - t.Errorf("Vec.XY() gotY = %v, want %v", gotY, tt.wantY) - } - }) - } -} - -func TestVec_Add(t *testing.T) { - type args struct { - v pixel.Vec - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.Add(): positive vector", - u: pixel.V(0, 10), - args: args{v: pixel.V(10, 10)}, - want: pixel.V(10, 20), - }, - { - name: "Vec.Add(): zero vector", - u: pixel.V(0, 10), - args: args{v: pixel.ZV}, - want: pixel.V(0, 10), - }, - { - name: "Vec.Add(): negative vector", - u: pixel.V(0, 10), - args: args{v: pixel.V(-20, -30)}, - want: pixel.V(-20, -20), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Add(tt.args.v); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.Add() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Sub(t *testing.T) { - type args struct { - v pixel.Vec - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.Sub(): positive vector", - u: pixel.V(0, 10), - args: args{v: pixel.V(10, 10)}, - want: pixel.V(-10, 0), - }, - { - name: "Vec.Sub(): zero vector", - u: pixel.V(0, 10), - args: args{v: pixel.ZV}, - want: pixel.V(0, 10), - }, - { - name: "Vec.Sub(): negative vector", - u: pixel.V(0, 10), - args: args{v: pixel.V(-20, -30)}, - want: pixel.V(20, 40), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Sub(tt.args.v); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.Sub() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_To(t *testing.T) { - type args struct { - v pixel.Vec - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.To(): positive vector", - u: pixel.V(0, 10), - args: args{v: pixel.V(10, 10)}, - want: pixel.V(10, 0), - }, - { - name: "Vec.To(): zero vector", - u: pixel.V(0, 10), - args: args{v: pixel.ZV}, - want: pixel.V(0, -10), - }, - { - name: "Vec.To(): negative vector", - u: pixel.V(0, 10), - args: args{v: pixel.V(-20, -30)}, - want: pixel.V(-20, -40), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.To(tt.args.v); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.To() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Scaled(t *testing.T) { - type args struct { - c float64 - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.Scaled(): positive scale", - u: pixel.V(0, 10), - args: args{c: 10}, - want: pixel.V(0, 100), - }, - { - name: "Vec.Scaled(): zero scale", - u: pixel.V(0, 10), - args: args{c: 0}, - want: pixel.ZV, - }, - { - name: "Vec.Scaled(): identity scale", - u: pixel.V(0, 10), - args: args{c: 1}, - want: pixel.V(0, 10), - }, - { - name: "Vec.Scaled(): negative scale", - u: pixel.V(0, 10), - args: args{c: -10}, - want: pixel.V(0, -100), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Scaled(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.Scaled() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_ScaledXY(t *testing.T) { - type args struct { - v pixel.Vec - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.ScaledXY(): positive scale", - u: pixel.V(0, 10), - args: args{v: pixel.V(10, 20)}, - want: pixel.V(0, 200), - }, - { - name: "Vec.ScaledXY(): zero scale", - u: pixel.V(0, 10), - args: args{v: pixel.ZV}, - want: pixel.ZV, - }, - { - name: "Vec.ScaledXY(): identity scale", - u: pixel.V(0, 10), - args: args{v: pixel.V(1, 1)}, - want: pixel.V(0, 10), - }, - { - name: "Vec.ScaledXY(): negative scale", - u: pixel.V(0, 10), - args: args{v: pixel.V(-5, -10)}, - want: pixel.V(0, -100), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.ScaledXY(tt.args.v); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.ScaledXY() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Len(t *testing.T) { - tests := []struct { - name string - u pixel.Vec - want float64 - }{ - { - name: "Vec.Len(): positive vector", - u: pixel.V(40, 30), - want: 50, - }, - { - name: "Vec.Len(): zero vector", - u: pixel.ZV, - want: 0, - }, - { - name: "Vec.Len(): negative vector", - u: pixel.V(-5, -12), - want: 13, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Len(); got != tt.want { - t.Errorf("Vec.Len() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Angle(t *testing.T) { - tests := []struct { - name string - u pixel.Vec - want float64 - }{ - { - name: "Vec.Angle(): positive vector", - u: pixel.V(0, 30), - want: math.Pi / 2, - }, - { - name: "Vec.Angle(): zero vector", - u: pixel.ZV, - want: 0, - }, - { - name: "Vec.Angle(): negative vector", - u: pixel.V(-5, -0), - want: math.Pi, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Angle(); got != tt.want { - t.Errorf("Vec.Angle() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Unit(t *testing.T) { - tests := []struct { - name string - u pixel.Vec - want pixel.Vec - }{ - { - name: "Vec.Unit(): positive vector", - u: pixel.V(0, 30), - want: pixel.V(0, 1), - }, - { - name: "Vec.Unit(): zero vector", - u: pixel.ZV, - want: pixel.V(1, 0), - }, - { - name: "Vec.Unit(): negative vector", - u: pixel.V(-5, 0), - want: pixel.V(-1, 0), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Unit(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.Unit() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Rotated(t *testing.T) { - type args struct { - angle float64 - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.Rotated(): partial rotation", - u: pixel.V(0, 1), - args: args{angle: math.Pi / 2}, - want: pixel.V(-1, 0), - }, - { - name: "Vec.Rotated(): full rotation", - u: pixel.V(0, 1), - args: args{angle: 2 * math.Pi}, - want: pixel.V(0, 1), - }, - { - name: "Vec.Rotated(): zero rotation", - u: pixel.V(0, 1), - args: args{angle: 0}, - want: pixel.V(0, 1), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Rotated(tt.args.angle); !closeEnough(got, tt.want) { - t.Errorf("Vec.Rotated() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Normal(t *testing.T) { - tests := []struct { - name string - u pixel.Vec - want pixel.Vec - }{ - { - name: "Vec.Normal(): positive vector", - u: pixel.V(0, 30), - want: pixel.V(-30, 0), - }, - { - name: "Vec.Normal(): zero vector", - u: pixel.ZV, - want: pixel.ZV, - }, - { - name: "Vec.Normal(): negative vector", - u: pixel.V(-5, 0), - want: pixel.V(0, -5), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Normal(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.Normal() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Dot(t *testing.T) { - type args struct { - v pixel.Vec - } - tests := []struct { - name string - u pixel.Vec - args args - want float64 - }{ - { - name: "Vec.Dot(): positive vector", - u: pixel.V(0, 30), - args: args{v: pixel.V(10, 10)}, - want: 300, - }, - { - name: "Vec.Dot(): zero vector", - u: pixel.ZV, - args: args{v: pixel.V(10, 10)}, - want: 0, - }, - { - name: "Vec.Dot(): negative vector", - u: pixel.V(-5, 1), - args: args{v: pixel.V(10, 10)}, - want: -40, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Dot(tt.args.v); got != tt.want { - t.Errorf("Vec.Dot() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Cross(t *testing.T) { - type args struct { - v pixel.Vec - } - tests := []struct { - name string - u pixel.Vec - args args - want float64 - }{ - { - name: "Vec.Cross(): positive vector", - u: pixel.V(0, 30), - args: args{v: pixel.V(10, 10)}, - want: -300, - }, - { - name: "Vec.Cross(): zero vector", - u: pixel.ZV, - args: args{v: pixel.V(10, 10)}, - want: 0, - }, - { - name: "Vec.Cross(): negative vector", - u: pixel.V(-5, 1), - args: args{v: pixel.V(10, 10)}, - want: -60, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Cross(tt.args.v); got != tt.want { - t.Errorf("Vec.Cross() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Project(t *testing.T) { - type args struct { - v pixel.Vec - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.Project(): positive vector", - u: pixel.V(0, 30), - args: args{v: pixel.V(10, 10)}, - want: pixel.V(15, 15), - }, - { - name: "Vec.Project(): zero vector", - u: pixel.ZV, - args: args{v: pixel.V(10, 10)}, - want: pixel.ZV, - }, - { - name: "Vec.Project(): negative vector", - u: pixel.V(-30, 0), - args: args{v: pixel.V(10, 10)}, - want: pixel.V(-15, -15), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Project(tt.args.v); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.Project() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestVec_Map(t *testing.T) { - type args struct { - f func(float64) float64 - } - tests := []struct { - name string - u pixel.Vec - args args - want pixel.Vec - }{ - { - name: "Vec.Map(): positive vector", - u: pixel.V(0, 25), - args: args{f: math.Sqrt}, - want: pixel.V(0, 5), - }, - { - name: "Vec.Map(): zero vector", - u: pixel.ZV, - args: args{f: math.Sqrt}, - want: pixel.ZV, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.u.Map(tt.args.f); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Vec.Map() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestLerp(t *testing.T) { - type args struct { - a pixel.Vec - b pixel.Vec - t float64 - } - tests := []struct { - name string - args args - want pixel.Vec - }{ - { - name: "Lerp(): t = 0", - args: args{a: pixel.V(10, 10), b: pixel.ZV, t: 0}, - want: pixel.V(10, 10), - }, - { - name: "Lerp(): t = 1/4", - args: args{a: pixel.V(10, 10), b: pixel.ZV, t: .25}, - want: pixel.V(7.5, 7.5), - }, - { - name: "Lerp(): t = 1/2", - args: args{a: pixel.V(10, 10), b: pixel.ZV, t: .5}, - want: pixel.V(5, 5), - }, - { - name: "Lerp(): t = 1", - args: args{a: pixel.V(10, 10), b: pixel.ZV, t: 1}, - want: pixel.ZV, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.Lerp(tt.args.a, tt.args.b, tt.args.t); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Lerp() = %v, want %v", got, tt.want) - } - }) - } -} From 95148365d04eea969b0a8f40dd41bb2c38f8409c Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 26 Mar 2019 15:42:12 +0000 Subject: [PATCH 081/156] Reverting back to origin --- README.md | 6 - color_test.go | 361 ------------------------------------------------- drawer_test.go | 118 ---------------- matrix_test.go | 168 ----------------------- 4 files changed, 653 deletions(-) diff --git a/README.md b/README.md index eda768f5..eaad6132 100644 --- a/README.md +++ b/README.md @@ -95,12 +95,6 @@ Here's the list of the main features in Pixel. Although Pixel is still under hea arbitrarily crazy stuff (e.g. graphical effects) - Small codebase, ~5K lines of code, including the backend [glhf](https://github.com/faiface/glhf) package - -## Related repositories - -Here are some packages which use Pixel: - - [TilePix](https://github.com/bcvery1/tilepix) Makes handling TMX files built with [Tiled](https://www.mapeditor.org/) - trivially easy to work with using Pixel. ## Missing features diff --git a/color_test.go b/color_test.go index bdc689ae..31514d7e 100644 --- a/color_test.go +++ b/color_test.go @@ -3,7 +3,6 @@ package pixel_test import ( "fmt" "image/color" - "reflect" "testing" "github.com/faiface/pixel" @@ -23,363 +22,3 @@ func BenchmarkColorToRGBA(b *testing.B) { }) } } - -func TestRGB(t *testing.T) { - type args struct { - r float64 - g float64 - b float64 - } - tests := []struct { - name string - args args - want pixel.RGBA - }{ - { - name: "RBG: create black", - args: args{r: 0, g: 0, b: 0}, - want: pixel.RGBA{R: 0, G: 0, B: 0, A: 1}, - }, - { - name: "RBG: create white", - args: args{r: 1, g: 1, b: 1}, - want: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}, - }, - { - name: "RBG: create nonsense", - args: args{r: 500, g: 500, b: 500}, - want: pixel.RGBA{R: 500, G: 500, B: 500, A: 1}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.RGB(tt.args.r, tt.args.g, tt.args.b); !reflect.DeepEqual(got, tt.want) { - t.Errorf("RGB() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestAlpha(t *testing.T) { - type args struct { - a float64 - } - tests := []struct { - name string - args args - want pixel.RGBA - }{ - { - name: "Alpha: transparent", - args: args{a: 0}, - want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, - }, - { - name: "Alpha: obaque", - args: args{a: 1}, - want: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}, - }, - { - name: "Alpha: nonsense", - args: args{a: 1024}, - want: pixel.RGBA{R: 1024, G: 1024, B: 1024, A: 1024}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.Alpha(tt.args.a); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Alpha() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRGBA_Add(t *testing.T) { - type fields struct { - R float64 - G float64 - B float64 - A float64 - } - type args struct { - d pixel.RGBA - } - tests := []struct { - name string - fields fields - args args - want pixel.RGBA - }{ - { - name: "RGBA.Add: add to black", - fields: fields{R: 0, G: 0, B: 0, A: 1}, - args: args{d: pixel.RGBA{R: 50, G: 50, B: 50, A: 0}}, - want: pixel.RGBA{R: 50, G: 50, B: 50, A: 1}, - }, - { - name: "RGBA.Add: add to white", - fields: fields{R: 1, G: 1, B: 1, A: 1}, - args: args{d: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}}, - want: pixel.RGBA{R: 2, G: 2, B: 2, A: 2}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := pixel.RGBA{ - R: tt.fields.R, - G: tt.fields.G, - B: tt.fields.B, - A: tt.fields.A, - } - if got := c.Add(tt.args.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("RGBA.Add() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRGBA_Sub(t *testing.T) { - type fields struct { - R float64 - G float64 - B float64 - A float64 - } - type args struct { - d pixel.RGBA - } - tests := []struct { - name string - fields fields - args args - want pixel.RGBA - }{ - { - name: "RGBA.Sub: sub from white", - fields: fields{R: 1, G: 1, B: 1, A: 1}, - args: args{d: pixel.RGBA{R: .5, G: .5, B: .5, A: 0}}, - want: pixel.RGBA{R: .5, G: .5, B: .5, A: 1}, - }, - { - name: "RGBA.Sub: sub from black", - fields: fields{R: 0, G: 0, B: 0, A: 0}, - args: args{d: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}}, - want: pixel.RGBA{R: -1, G: -1, B: -1, A: -1}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := pixel.RGBA{ - R: tt.fields.R, - G: tt.fields.G, - B: tt.fields.B, - A: tt.fields.A, - } - if got := c.Sub(tt.args.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("RGBA.Sub() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRGBA_Mul(t *testing.T) { - type fields struct { - R float64 - G float64 - B float64 - A float64 - } - type args struct { - d pixel.RGBA - } - - greaterThanOne := args{d: pixel.RGBA{R: 2, G: 3, B: 4, A: 5}} - lessThanOne := args{d: pixel.RGBA{R: .2, G: .3, B: .4, A: .5}} - - tests := []struct { - name string - fields fields - args args - want pixel.RGBA - }{ - { - name: "RGBA.Mul: multiply black by >1", - fields: fields{R: 0, G: 0, B: 0, A: 0}, - args: greaterThanOne, - want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, - }, - { - name: "RGBA.Mul: multiply white by >1", - fields: fields{R: 1, G: 1, B: 1, A: 1}, - args: greaterThanOne, - want: pixel.RGBA{R: 2, G: 3, B: 4, A: 5}, - }, - { - name: "RGBA.Mul: multiply black by <1", - fields: fields{R: 0, G: 0, B: 0, A: 0}, - args: lessThanOne, - want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, - }, - { - name: "RGBA.Mul: multiply white by <1", - fields: fields{R: 1, G: 1, B: 1, A: 1}, - args: lessThanOne, - want: pixel.RGBA{R: .2, G: .3, B: .4, A: .5}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := pixel.RGBA{ - R: tt.fields.R, - G: tt.fields.G, - B: tt.fields.B, - A: tt.fields.A, - } - if got := c.Mul(tt.args.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("RGBA.Mul() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRGBA_Scaled(t *testing.T) { - type fields struct { - R float64 - G float64 - B float64 - A float64 - } - type args struct { - scale float64 - } - tests := []struct { - name string - fields fields - args args - want pixel.RGBA - }{ - { - name: "RBGA.Scaled: black <1", - fields: fields{R: 0, G: 0, B: 0, A: 0}, - args: args{scale: 0.5}, - want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, - }, - { - name: "RBGA.Scaled: black <1", - fields: fields{R: 1, G: 1, B: 1, A: 1}, - args: args{scale: 0.5}, - want: pixel.RGBA{R: .5, G: .5, B: .5, A: .5}, - }, - { - name: "RBGA.Scaled: black >1", - fields: fields{R: 0, G: 0, B: 0, A: 0}, - args: args{scale: 2}, - want: pixel.RGBA{R: 0, G: 0, B: 0, A: 0}, - }, - { - name: "RBGA.Scaled: black >1", - fields: fields{R: 1, G: 1, B: 1, A: 1}, - args: args{scale: 2}, - want: pixel.RGBA{R: 2, G: 2, B: 2, A: 2}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := pixel.RGBA{ - R: tt.fields.R, - G: tt.fields.G, - B: tt.fields.B, - A: tt.fields.A, - } - if got := c.Scaled(tt.args.scale); !reflect.DeepEqual(got, tt.want) { - t.Errorf("RGBA.Scaled() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestRGBA_RGBA(t *testing.T) { - type fields struct { - R float64 - G float64 - B float64 - A float64 - } - tests := []struct { - name string - fields fields - wantR uint32 - wantG uint32 - wantB uint32 - wantA uint32 - }{ - { - name: "RGBA.RGBA: black", - fields: fields{R: 0, G: 0, B: 0, A: 0}, - wantR: 0, - wantG: 0, - wantB: 0, - wantA: 0, - }, - { - name: "RGBA.RGBA: white", - fields: fields{R: 1, G: 1, B: 1, A: 1}, - wantR: 65535, - wantG: 65535, - wantB: 65535, - wantA: 65535, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := pixel.RGBA{ - R: tt.fields.R, - G: tt.fields.G, - B: tt.fields.B, - A: tt.fields.A, - } - gotR, gotG, gotB, gotA := c.RGBA() - if gotR != tt.wantR { - t.Errorf("RGBA.RGBA() gotR = %v, want %v", gotR, tt.wantR) - } - if gotG != tt.wantG { - t.Errorf("RGBA.RGBA() gotG = %v, want %v", gotG, tt.wantG) - } - if gotB != tt.wantB { - t.Errorf("RGBA.RGBA() gotB = %v, want %v", gotB, tt.wantB) - } - if gotA != tt.wantA { - t.Errorf("RGBA.RGBA() gotA = %v, want %v", gotA, tt.wantA) - } - }) - } -} - -func TestToRGBA(t *testing.T) { - type args struct { - c color.Color - } - tests := []struct { - name string - args args - want pixel.RGBA - }{ - { - name: "ToRGBA: black", - args: args{c: color.Black}, - want: pixel.RGBA{R: 0, G: 0, B: 0, A: 1}, - }, - { - name: "ToRGBA: white", - args: args{c: color.White}, - want: pixel.RGBA{R: 1, G: 1, B: 1, A: 1}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := pixel.ToRGBA(tt.args.c); !reflect.DeepEqual(got, tt.want) { - t.Errorf("ToRGBA() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/drawer_test.go b/drawer_test.go index 09a684de..233f510e 100644 --- a/drawer_test.go +++ b/drawer_test.go @@ -16,121 +16,3 @@ func BenchmarkSpriteDrawBatch(b *testing.B) { sprite.Draw(batch, pixel.IM) } } - -func TestDrawer_Dirty(t *testing.T) { - tests := []struct { - name string - d *pixel.Drawer - }{ - { - name: "Drawer.Dirty", - d: &pixel.Drawer{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.d.Dirty() - }) - } -} - -type targetMock struct { - makeTrianglesCount int - makePictureCount int -} - -func (t *targetMock) MakePicture(_ pixel.Picture) pixel.TargetPicture { - t.makePictureCount++ - return targetPictureMock{} -} - -func (t *targetMock) MakeTriangles(_ pixel.Triangles) pixel.TargetTriangles { - t.makeTrianglesCount++ - return targetTrianglesMock{} -} - -type targetTrianglesMock struct{} - -func (targetTrianglesMock) Len() int { - return 0 -} - -func (targetTrianglesMock) SetLen(_ int) { - -} - -func (targetTrianglesMock) Slice(_, _ int) pixel.Triangles { - return nil -} - -func (targetTrianglesMock) Update(_ pixel.Triangles) { -} - -func (targetTrianglesMock) Copy() pixel.Triangles { - return nil -} - -func (targetTrianglesMock) Draw() { -} - -type targetPictureMock struct{} - -func (targetPictureMock) Bounds() pixel.Rect { - return pixel.R(0, 0, 0, 0) -} - -func (targetPictureMock) Draw(_ pixel.TargetTriangles) { - -} - -func TestDrawer_Draw(t *testing.T) { - type args struct { - t pixel.Target - } - tests := []struct { - name string - d *pixel.Drawer - args args - wantPictureCount int - wantTriangleCount int - }{ - { - name: "Drawer.Draw: nil triangles", - d: &pixel.Drawer{}, - args: args{t: &targetMock{}}, - wantPictureCount: 0, - wantTriangleCount: 0, - }, - { - name: "Drawer.Draw: non-nil triangles", - d: &pixel.Drawer{Triangles: pixel.MakeTrianglesData(1)}, - args: args{t: &targetMock{}}, - wantPictureCount: 0, - wantTriangleCount: 1, - }, - { - name: "Drawer.Draw: non-nil picture", - d: &pixel.Drawer{ - Triangles: pixel.MakeTrianglesData(1), - Picture: pixel.MakePictureData(pixel.R(0, 0, 0, 0)), - }, - args: args{t: &targetMock{}}, - wantPictureCount: 1, - wantTriangleCount: 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.d.Draw(tt.args.t) - - target := tt.args.t.(*targetMock) - - if tt.wantPictureCount != target.makePictureCount { - t.Fatalf("MakePicture not called. Expected %d, got: %d", tt.wantPictureCount, target.makePictureCount) - } - if tt.wantTriangleCount != target.makeTrianglesCount { - t.Fatalf("MakeTriangles not called. Expected %d, got: %d", tt.wantTriangleCount, target.makeTrianglesCount) - } - }) - } -} diff --git a/matrix_test.go b/matrix_test.go index 08db8479..e8d0ec91 100644 --- a/matrix_test.go +++ b/matrix_test.go @@ -2,7 +2,6 @@ package pixel_test import ( "math/rand" - "reflect" "testing" "github.com/faiface/pixel" @@ -62,170 +61,3 @@ func BenchmarkMatrix(b *testing.B) { } }) } - -func TestMatrix_String(t *testing.T) { - tests := []struct { - name string - m pixel.Matrix - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.String(); got != tt.want { - t.Errorf("Matrix.String() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMatrix_Moved(t *testing.T) { - type args struct { - delta pixel.Vec - } - tests := []struct { - name string - m pixel.Matrix - args args - want pixel.Matrix - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Matrix.Moved() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMatrix_ScaledXY(t *testing.T) { - type args struct { - around pixel.Vec - scale pixel.Vec - } - tests := []struct { - name string - m pixel.Matrix - args args - want pixel.Matrix - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.ScaledXY(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Matrix.ScaledXY() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMatrix_Scaled(t *testing.T) { - type args struct { - around pixel.Vec - scale float64 - } - tests := []struct { - name string - m pixel.Matrix - args args - want pixel.Matrix - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.Scaled(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Matrix.Scaled() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMatrix_Rotated(t *testing.T) { - type args struct { - around pixel.Vec - angle float64 - } - tests := []struct { - name string - m pixel.Matrix - args args - want pixel.Matrix - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.Rotated(tt.args.around, tt.args.angle); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Matrix.Rotated() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMatrix_Chained(t *testing.T) { - type args struct { - next pixel.Matrix - } - tests := []struct { - name string - m pixel.Matrix - args args - want pixel.Matrix - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.Chained(tt.args.next); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Matrix.Chained() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMatrix_Project(t *testing.T) { - type args struct { - u pixel.Vec - } - tests := []struct { - name string - m pixel.Matrix - args args - want pixel.Vec - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.Project(tt.args.u); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Matrix.Project() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestMatrix_Unproject(t *testing.T) { - type args struct { - u pixel.Vec - } - tests := []struct { - name string - m pixel.Matrix - args args - want pixel.Vec - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.m.Unproject(tt.args.u); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Matrix.Unproject() = %v, want %v", got, tt.want) - } - }) - } -} From b2c8bff6ebb28b4903288d1ba43100ce894c3592 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 26 Mar 2019 15:42:59 +0000 Subject: [PATCH 082/156] Adding related packages --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index eaad6132..0e5b038e 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,12 @@ Here's the list of the main features in Pixel. Although Pixel is still under hea - Small codebase, ~5K lines of code, including the backend [glhf](https://github.com/faiface/glhf) package + ## Related repositories + + Here are some packages which use Pixel: + - [TilePix](https://github.com/bcvery1/tilepix) Makes handling TMX files built with [Tiled](https://www.mapeditor.org/) + trivially easy to work with using Pixel. + ## Missing features Pixel is in development and still missing few critical features. Here're the most critical ones. From d3912a6486ed2806a6743b5f2203ffdfce3a8b38 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 1 Apr 2019 15:31:20 +0100 Subject: [PATCH 083/156] Added Line struct and methods --- geometry.go | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/geometry.go b/geometry.go index 1e2922de..7e10a692 100644 --- a/geometry.go +++ b/geometry.go @@ -181,6 +181,173 @@ func Lerp(a, b Vec, t float64) Vec { return a.Scaled(1 - t).Add(b.Scaled(t)) } +// Line is a 2D line segment, between points `A` and `B`. +type Line struct { + A, B Vec +} + +// Bounds returns the lines bounding box. This is in the form of a normalized `Rect`. +func (l Line) Bounds() Rect { + return R(l.A.X, l.A.Y, l.B.X, l.B.Y).Norm() +} + +// Center will return the point at center of the line; that is, the point equidistant from either end. +func (l Line) Center() Vec { + return l.A.Add(l.A.To(l.B).Scaled(0.5)) +} + +// Closest will return the point on the line which is closest to the `Vec` provided. +func (l Line) Closest(v Vec) Vec { + lenSquared := math.Pow(l.Len(), 2) + + if lenSquared == 0 { + return l.A + } + + t := math.Max(0, math.Min(1, l.A.Sub(v).Dot(l.B.Sub(v))/lenSquared)) + projection := l.A.Add(l.B.Sub(v).Scaled(t)) + return v.To(projection) +} + +// Contains returns whether the provided `Vec` lies on the line +func (l Line) Contains(v Vec) bool { + return l.Closest(v) == v +} + +// Formula will return the values that represent the line in the formula: y = mx + b +func (l Line) Formula() (m, b float64) { + m = (l.B.Y - l.A.Y) / (l.B.X - l.A.X) + b = l.A.Y - (m * l.A.X) + + return m, b +} + +// Intersect will return the point of intersection for the two line segments. If the line segments do not intersect, +// this function will return the zero-vector and `false`. +func (l Line) Intersect(k Line) (Vec, bool) { + // Check if the lines are parallel + lDir := l.A.To(l.B) + kDir := k.A.To(k.B) + if math.Abs(lDir.X) == math.Abs(kDir.X) && math.Abs(lDir.Y) == math.Abs(kDir.Y) { + return ZV, false + } + + // The lines intersect - but potentially not within the line segments. + // Get the intersection point for the lines if they were infinitely long, check if the point exists on both of the + // segments + lm, lb := l.Formula() + km, kb := l.Formula() + + // Coordinates of intersect + x := (kb - lb) / (lm - km) + y := lm*x + lb + + if l.Contains(V(x, y)) && k.Contains(V(x, y)) { + // The intersect point is on both line segments, they intersect. + return V(x, y), true + } + + return ZV, false +} + +// IntersectCircle will return the shortest `Vec` such that the Line and Circle no longer intesect. If they do not +// intersect at all, this function will return a zero-vector. +func (l Line) IntersectCircle(c Circle) Vec { + // Get the point on the line closest to the center of the circle. + closest := l.Closest(c.Center) + cirToClosest := c.Center.To(closest) + + if cirToClosest.Len() >= c.Radius { + return ZV + } + + return cirToClosest.Scaled(cirToClosest.Len() - c.Radius) +} + +// IntersectRect will return the shortest `Vec` such that the Line and Rect no longer intesect. If they do not +// intersect at all, this function will return a zero-vector. +func (l Line) IntersectRect(r Rect) Vec { + // Check if either end of the line segment are within the rectangle + if r.Contains(l.A) || r.Contains(l.B) { + // Use the `Rect.Intersect` to get minimal return value + rIntersect := l.Bounds().Intersect(r) + if rIntersect.H() > rIntersect.W() { + // Go vertical + return V(0, rIntersect.H()) + } + return V(rIntersect.W(), 0) + } + + // Check if any of the rectangles' edges intersect with this line. + for _, edge := range r.Edges() { + if _, ok := l.Intersect(edge); ok { + // Get the closest points on the line to each corner, where: + // - the point is contained by the rectangle + // - the point is not the corner itself + corners := r.Vertices() + closest := ZV + closestCorner := corners[0] + for _, c := range corners { + cc := l.Closest(c) + if closest != ZV || (closest.Len() > cc.Len() && r.Contains(cc)) { + closest = cc + closestCorner = c + } + } + + return closest.To(closestCorner) + } + } + + // No intersect + return ZV +} + +// Len returns the length of the line segment. +func (l Line) Len() float64 { + return l.A.Sub(l.B).Len() +} + +// Moved will return a line moved by the delta `Vec` provided. +func (l Line) Moved(delta Vec) Line { + return Line{ + A: l.A.Add(delta), + B: l.B.Add(delta), + } +} + +// Rotated will rotate the line around the provided `Vec`. +func (l Line) Rotated(around Vec, angle float64) Line { + // Move the line so we can use `Vec.Rotated` + lineShifted := l.Moved(around.Scaled(-1)) + lineRotated := Line{ + A: lineShifted.A.Rotated(angle), + B: lineShifted.B.Rotated(angle), + } + + return lineRotated.Moved(around) +} + +// Scaled will return the line scaled around the center point. +func (l Line) Scaled(scale float64) Line { + return l.ScaledXY(l.Center(), scale) +} + +// ScaledXY will return the line scaled around the `Vec` provided. +func (l Line) ScaledXY(around Vec, scale float64) Line { + toA := around.To(l.A).Scaled(scale) + toB := around.To(l.B).Scaled(scale) + + return Line{ + A: around.Add(toA), + B: around.Add(toB), + } +} + +func (l Line) String() string { + return fmt.Sprintf("Line(%v, %v)", l.A, l.B) +} + // Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two // points, Min and Max. // @@ -243,6 +410,18 @@ func (r Rect) Area() float64 { return r.W() * r.H() } +// Edges will return the four lines which make up the edges of the rectangle. +func (r Rect) Edges() [4]Line { + corners := r.Vertices() + + return [4]Line{ + {A: corners[0], B: corners[1]}, + {A: corners[1], B: corners[2]}, + {A: corners[2], B: corners[3]}, + {A: corners[3], B: corners[0]}, + } +} + // Center returns the position of the center of the Rect. func (r Rect) Center() Vec { return Lerp(r.Min, r.Max, 0.5) @@ -329,6 +508,22 @@ func (r Rect) IntersectCircle(c Circle) Vec { return c.IntersectRect(r).Scaled(-1) } +// IntersectLine will return the shortest `Vec` such that if the Rect is moved by the Vec returned, the Line and Rect no +// longer intersect. +func (r Rect) IntersectLine(l Line) Vec { + return l.IntersectRect(r).Scaled(-1) +} + +// Vertices returns a slice of the four corners which make up the rectangle. +func (r Rect) Vertices() [4]Vec { + return [4]Vec{ + r.Min, + r.Max, + V(r.Min.X, r.Max.Y), + V(r.Max.X, r.Min.Y), + } +} + // Circle is a 2D circle. It is defined by two properties: // - Center vector // - Radius float64 @@ -476,6 +671,12 @@ func (c Circle) Intersect(d Circle) Circle { } } +// IntersectLine will return the shortest `Vec` such that if the Rect is moved by the Vec returned, the Line and Rect no +// longer intersect. +func (c Circle) IntersectLine(l Line) Vec { + return l.IntersectCircle(c).Scaled(-1) +} + // IntersectRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. From a5ea71811ea1670166b63c1ed74e415ef54eac8b Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 1 Apr 2019 15:31:34 +0100 Subject: [PATCH 084/156] Added test templates --- geometry_test.go | 369 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) diff --git a/geometry_test.go b/geometry_test.go index dfa78cf0..cd957952 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -690,3 +690,372 @@ func TestRect_IntersectCircle(t *testing.T) { }) } } + +func TestLine_Bounds(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + tests := []struct { + name string + fields fields + want pixel.Rect + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.Bounds(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.Bounds() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_Center(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + tests := []struct { + name string + fields fields + want pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.Center(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.Center() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_Closest(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + v pixel.Vec + } + tests := []struct { + name string + fields fields + args args + want pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.Closest(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.Closest() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_Contains(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + v pixel.Vec + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.Contains(tt.args.v); got != tt.want { + t.Errorf("Line.Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_Formula(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + tests := []struct { + name string + fields fields + wantM float64 + wantB float64 + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + gotM, gotB := l.Formula() + if gotM != tt.wantM { + t.Errorf("Line.Formula() gotM = %v, want %v", gotM, tt.wantM) + } + if gotB != tt.wantB { + t.Errorf("Line.Formula() gotB = %v, want %v", gotB, tt.wantB) + } + }) + } +} + +func TestLine_Intersect(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + k pixel.Line + } + tests := []struct { + name string + fields fields + args args + want pixel.Vec + want1 bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + got, got1 := l.Intersect(tt.args.k) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.Intersect() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Line.Intersect() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestLine_IntersectCircle(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + c pixel.Circle + } + tests := []struct { + name string + fields fields + args args + want pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.IntersectCircle(tt.args.c); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.IntersectCircle() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_IntersectRect(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + r pixel.Rect + } + tests := []struct { + name string + fields fields + args args + want pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.IntersectRect(tt.args.r); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.IntersectRect() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_Len(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + tests := []struct { + name string + fields fields + want float64 + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.Len(); got != tt.want { + t.Errorf("Line.Len() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_Rotated(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + around pixel.Vec + angle float64 + } + tests := []struct { + name string + fields fields + args args + want pixel.Line + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.Rotated(tt.args.around, tt.args.angle); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.Rotated() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_Scaled(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + scale float64 + } + tests := []struct { + name string + fields fields + args args + want pixel.Line + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.Scaled(tt.args.scale); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.Scaled() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_ScaledXY(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + type args struct { + around pixel.Vec + scale float64 + } + tests := []struct { + name string + fields fields + args args + want pixel.Line + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.ScaledXY(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Line.ScaledXY() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLine_String(t *testing.T) { + type fields struct { + A pixel.Vec + B pixel.Vec + } + tests := []struct { + name string + fields fields + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := pixel.Line{ + A: tt.fields.A, + B: tt.fields.B, + } + if got := l.String(); got != tt.want { + t.Errorf("Line.String() = %v, want %v", got, tt.want) + } + }) + } +} From 1eac5d8dc2671556f2d8d03c85c9425b284ea555 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 1 Apr 2019 15:33:31 +0100 Subject: [PATCH 085/156] Added test templates for new rect tests --- geometry_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/geometry_test.go b/geometry_test.go index cd957952..8b13f5e8 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -10,6 +10,31 @@ import ( "github.com/stretchr/testify/assert" ) +func TestRect_Edges(t *testing.T) { + type fields struct { + Min pixel.Vec + Max pixel.Vec + } + tests := []struct { + name string + fields fields + want [4]pixel.Line + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := pixel.Rect{ + Min: tt.fields.Min, + Max: tt.fields.Max, + } + if got := r.Edges(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Edges() = %v, want %v", got, tt.want) + } + }) + } +} + func TestRect_Resize(t *testing.T) { type rectTestTransform struct { name string @@ -80,6 +105,31 @@ func TestRect_Resize(t *testing.T) { } } +func TestRect_Vertices(t *testing.T) { + type fields struct { + Min pixel.Vec + Max pixel.Vec + } + tests := []struct { + name string + fields fields + want [4]pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := pixel.Rect{ + Min: tt.fields.Min, + Max: tt.fields.Max, + } + if got := r.Vertices(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.Vertices() = %v, want %v", got, tt.want) + } + }) + } +} + func TestMatrix_Unproject(t *testing.T) { const delta = 1e-15 t.Run("for rotated matrix", func(t *testing.T) { From 128ec4d4c0b000fdabb925bc7bd09bb7b8bb95bd Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 1 Apr 2019 15:37:23 +0100 Subject: [PATCH 086/156] Added function to create a Line --- geometry.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/geometry.go b/geometry.go index 7e10a692..a474ca7a 100644 --- a/geometry.go +++ b/geometry.go @@ -186,6 +186,14 @@ type Line struct { A, B Vec } +// L creates and returns a new Line object. +func L(from, to Vec) Line { + return Line{ + A: from, + B: to, + } +} + // Bounds returns the lines bounding box. This is in the form of a normalized `Rect`. func (l Line) Bounds() Rect { return R(l.A.X, l.A.Y, l.B.X, l.B.Y).Norm() From 9d07d69429a037ec3bcd4be97589156bc383414b Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 1 Apr 2019 15:42:20 +0100 Subject: [PATCH 087/156] Fixed so corners are provided in anticlockwise pattern --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index a474ca7a..053a016d 100644 --- a/geometry.go +++ b/geometry.go @@ -526,8 +526,8 @@ func (r Rect) IntersectLine(l Line) Vec { func (r Rect) Vertices() [4]Vec { return [4]Vec{ r.Min, - r.Max, V(r.Min.X, r.Max.Y), + r.Max, V(r.Max.X, r.Min.Y), } } From 1a275b59295cc4b1c8e3c276777392643042fd07 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 1 Apr 2019 15:44:14 +0100 Subject: [PATCH 088/156] Filled rect tests --- geometry_test.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 8b13f5e8..ca2b2861 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -20,7 +20,16 @@ func TestRect_Edges(t *testing.T) { fields fields want [4]pixel.Line }{ - // TODO: Add test cases. + { + name: "Get edges", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + want: [4]pixel.Line{ + pixel.L(pixel.V(0, 0), pixel.V(0, 10)), + pixel.L(pixel.V(0, 10), pixel.V(10, 10)), + pixel.L(pixel.V(10, 10), pixel.V(10, 0)), + pixel.L(pixel.V(10, 0), pixel.V(0, 0)), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -115,7 +124,16 @@ func TestRect_Vertices(t *testing.T) { fields fields want [4]pixel.Vec }{ - // TODO: Add test cases. + { + name: "Get corners", + fields: fields{Min: pixel.V(0, 0), Max: pixel.V(10, 10)}, + want: [4]pixel.Vec{ + pixel.V(0, 0), + pixel.V(0, 10), + pixel.V(10, 10), + pixel.V(10, 0), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From cca37c750e0ea5421e3ce6f3c544f5913fd9788d Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 1 Apr 2019 16:01:08 +0100 Subject: [PATCH 089/156] wip adding line tests --- geometry_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index ca2b2861..32dac1e7 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -769,7 +769,16 @@ func TestLine_Bounds(t *testing.T) { fields fields want pixel.Rect }{ - // TODO: Add test cases. + { + name: "Positive slope", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + want: pixel.R(0, 0, 10, 10), + }, + { + name: "Negative slope", + fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)}, + want: pixel.R(0, 0, 10, 10), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -794,7 +803,16 @@ func TestLine_Center(t *testing.T) { fields fields want pixel.Vec }{ - // TODO: Add test cases. + { + name: "Positive slope", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + want: pixel.V(5, 5), + }, + { + name: "Negative slope", + fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)}, + want: pixel.V(5, 5), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -823,7 +841,24 @@ func TestLine_Closest(t *testing.T) { args args want pixel.Vec }{ - // TODO: Add test cases. + { + name: "Point on line", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{v: pixel.V(5, 5)}, + want: pixel.V(5, 5), + }, + { + name: "Point on next to line", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{v: pixel.V(0, 10)}, + want: pixel.V(5, 5), + }, + { + name: "Point on inline with line", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{v: pixel.V(20, 20)}, + want: pixel.V(10, 10), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -852,7 +887,18 @@ func TestLine_Contains(t *testing.T) { args args want bool }{ - // TODO: Add test cases. + { + name: "Point on line", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{v: pixel.V(5, 5)}, + want: true, + }, + { + name: "Point not on line", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{v: pixel.V(0, 10)}, + want: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -878,7 +924,24 @@ func TestLine_Formula(t *testing.T) { wantM float64 wantB float64 }{ - // TODO: Add test cases. + { + name: "Getting formula - 45 degs", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + wantM: 1, + wantB: 0, + }, + { + name: "Getting formula - 90 degs", + fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)}, + wantM: math.Inf(1), + wantB: math.NaN(), + }, + { + name: "Getting formula - 0 degs", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 0)}, + wantM: 0, + wantB: 0, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 2478da5d12f9235d2406e75891236fe60dadb336 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 2 Apr 2019 08:49:45 +0100 Subject: [PATCH 090/156] wip adding line tests --- geometry_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/geometry_test.go b/geometry_test.go index 32dac1e7..1493a5a1 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -975,7 +975,27 @@ func TestLine_Intersect(t *testing.T) { want pixel.Vec want1 bool }{ - // TODO: Add test cases. + { + name: "Lines intersect", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{k: pixel.L(pixel.V(0, 10), pixel.V(10, 0))}, + want: pixel.V(5, 5), + want1: true, + }, + { + name: "Lines don't intersect", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{k: pixel.L(pixel.V(0, 10), pixel.V(1, 20))}, + want: pixel.ZV, + want1: false, + }, + { + name: "Lines parallel", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{k: pixel.L(pixel.V(0, 1), pixel.V(10, 11))}, + want: pixel.ZV, + want1: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 55b87ca5b12561bd1a30e31fc43b7b3944ba6f47 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 2 Apr 2019 13:33:41 +0100 Subject: [PATCH 091/156] WIP line tests --- geometry_test.go | 95 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 1493a5a1..51c2ea99 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1028,7 +1028,18 @@ func TestLine_IntersectCircle(t *testing.T) { args args want pixel.Vec }{ - // TODO: Add test cases. + { + name: "Cirle intersects", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(5, 5), 1)}, + want: pixel.V(1, -1), + }, + { + name: "Cirle doesn't intersects", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{c: pixel.C(pixel.V(0, 5), 1)}, + want: pixel.ZV, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1057,7 +1068,36 @@ func TestLine_IntersectRect(t *testing.T) { args args want pixel.Vec }{ - // TODO: Add test cases. + { + name: "Line through rect vertically", + fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)}, + args: args{r: pixel.R(-1, 1, 5, 5)}, + want: pixel.V(-1, 0), + }, + { + name: "Line through rect horizontally", + fields: fields{A: pixel.V(-5, 0), B: pixel.V(5, 0)}, + args: args{r: pixel.R(-2, -5, 2, 1)}, + want: pixel.V(0, 1), + }, + { + name: "Line through rect diagonally bottom and left edges", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{r: pixel.R(0, 2, 3, 3)}, + want: pixel.V(1, -1), + }, + { + name: "Line through rect diagonally top and right edges", + fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)}, + args: args{r: pixel.R(5, 0, 8, 3)}, + want: pixel.V(-1, -1), + }, + { + name: "Line with not rect intersect", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{r: pixel.R(20, 20, 21, 21)}, + want: pixel.ZV, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1082,7 +1122,31 @@ func TestLine_Len(t *testing.T) { fields fields want float64 }{ - // TODO: Add test cases. + { + name: "End right-up of start", + fields: fields{A: pixel.V(0, 0), B: pixel.V(3, 4)}, + want: 5, + }, + { + name: "End left-up of start", + fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, 4)}, + want: 5, + }, + { + name: "End right-down of start", + fields: fields{A: pixel.V(0, 0), B: pixel.V(3, -4)}, + want: 5, + }, + { + name: "End left-down of start", + fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, -4)}, + want: 5, + }, + { + name: "End same as start", + fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 0)}, + want: 0, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1112,7 +1176,24 @@ func TestLine_Rotated(t *testing.T) { args args want pixel.Line }{ - // TODO: Add test cases. + { + name: "Rotating around line center", + fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, + args: args{around: pixel.V(2, 2), angle: 2 * math.Pi}, + want: pixel.L(pixel.V(1, 1), pixel.V(3, 3)), + }, + { + name: "Rotating around x-y origin", + fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, + args: args{around: pixel.V(0, 0), angle: 2 * math.Pi}, + want: pixel.L(pixel.V(-1, -1), pixel.V(-3, -3)), + }, + { + name: "Rotating around line end", + fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, + args: args{around: pixel.V(1, 1), angle: 2 * math.Pi}, + want: pixel.L(pixel.V(1, 1), pixel.V(-2, -2)), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1196,7 +1277,11 @@ func TestLine_String(t *testing.T) { fields fields want string }{ - // TODO: Add test cases. + { + name: "Getting string", + fields: fields{A: pixel.V(0, 0), B: pixel.V(1, 1)}, + want: "Line(Vec(0, 0), Vec(1, 1))", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From c7eac064993cd6b40c45c8c530d088614743faa2 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 2 Apr 2019 13:46:50 +0100 Subject: [PATCH 092/156] WIP line tests --- geometry_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 51c2ea99..2b46b449 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1222,7 +1222,30 @@ func TestLine_Scaled(t *testing.T) { args args want pixel.Line }{ - // TODO: Add test cases. + { + name: "Scaling by 1", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{scale: 1}, + want: pixel.L(pixel.V(0, 0), pixel.V(10, 10)), + }, + { + name: "Scaling by >1", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{scale: 2}, + want: pixel.L(pixel.V(-5, -5), pixel.V(15, 15)), + }, + { + name: "Scaling by <1", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{scale: 0.5}, + want: pixel.L(pixel.V(2.5, 2.5), pixel.V(7.5, 7.5)), + }, + { + name: "Scaling by -1", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{scale: -1}, + want: pixel.L(pixel.V(10, 10), pixel.V(0, 0)), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1252,7 +1275,30 @@ func TestLine_ScaledXY(t *testing.T) { args args want pixel.Line }{ - // TODO: Add test cases. + { + name: "Scaling by 1 around origin", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{around: pixel.ZV, scale: 1}, + want: pixel.L(pixel.V(0, 0), pixel.V(10, 10)), + }, + { + name: "Scaling by >1 around origin", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{around: pixel.ZV, scale: 2}, + want: pixel.L(pixel.V(0, 0), pixel.V(20, 20)), + }, + { + name: "Scaling by <1 around origin", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{around: pixel.ZV, scale: 0.5}, + want: pixel.L(pixel.V(0, 0), pixel.V(5, 5)), + }, + { + name: "Scaling by -1 around origin", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{around: pixel.ZV, scale: -1}, + want: pixel.L(pixel.V(0, 0), pixel.V(-10, -10)), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 237d77596ff9eb33b73cf71dc5b68a07aba87c10 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 2 Apr 2019 13:48:43 +0100 Subject: [PATCH 093/156] WIP line tests --- geometry_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/geometry_test.go b/geometry_test.go index 2b46b449..e2414b85 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -954,7 +954,9 @@ func TestLine_Formula(t *testing.T) { t.Errorf("Line.Formula() gotM = %v, want %v", gotM, tt.wantM) } if gotB != tt.wantB { - t.Errorf("Line.Formula() gotB = %v, want %v", gotB, tt.wantB) + if math.IsNaN(tt.wantB) && !math.IsNaN(gotB) { + t.Errorf("Line.Formula() gotB = %v, want %v", gotB, tt.wantB) + } } }) } From 04c3ef72a301b80d687e53f7e5c737394f520e5a Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 2 Apr 2019 16:27:54 +0100 Subject: [PATCH 094/156] WIP line tests --- geometry.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/geometry.go b/geometry.go index 053a016d..5126aaad 100644 --- a/geometry.go +++ b/geometry.go @@ -206,15 +206,28 @@ func (l Line) Center() Vec { // Closest will return the point on the line which is closest to the `Vec` provided. func (l Line) Closest(v Vec) Vec { - lenSquared := math.Pow(l.Len(), 2) - - if lenSquared == 0 { - return l.A + // Check if the point is within the lines' bounding box, if not, one of the endpoints will be the closest point + if !l.Bounds().Contains(v) { + // Not within bounding box + toStart := v.To(l.A) + toEnd := v.To(l.B) + + if toStart.Len() < toEnd.Len() { + return l.A + } + return l.B } - t := math.Max(0, math.Min(1, l.A.Sub(v).Dot(l.B.Sub(v))/lenSquared)) - projection := l.A.Add(l.B.Sub(v).Scaled(t)) - return v.To(projection) + // Closest point will be on a line, perpendicular to this line + m, b := l.Formula() + perpendicularM := -1 / m + perpendicularB := v.Y - (perpendicularM * v.X) + + // Coordinates of intersect (of infinite lines) + x := (perpendicularB - b) / (m - perpendicularM) + y := m*x - b + + return V(x, y) } // Contains returns whether the provided `Vec` lies on the line From 997f23dfb5b670e282d243b1133b67ad07d203af Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 2 Apr 2019 17:01:18 +0100 Subject: [PATCH 095/156] WIP line tests --- geometry.go | 17 +++++++++++++++-- geometry_test.go | 6 ++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index 5126aaad..d71cbd8e 100644 --- a/geometry.go +++ b/geometry.go @@ -222,11 +222,18 @@ func (l Line) Closest(v Vec) Vec { m, b := l.Formula() perpendicularM := -1 / m perpendicularB := v.Y - (perpendicularM * v.X) + fmt.Println(m) + fmt.Println(b) + fmt.Println(perpendicularM) + fmt.Println(perpendicularB) // Coordinates of intersect (of infinite lines) x := (perpendicularB - b) / (m - perpendicularM) y := m*x - b + fmt.Println(x) + fmt.Println(y) + return V(x, y) } @@ -246,10 +253,13 @@ func (l Line) Formula() (m, b float64) { // Intersect will return the point of intersection for the two line segments. If the line segments do not intersect, // this function will return the zero-vector and `false`. func (l Line) Intersect(k Line) (Vec, bool) { + fmt.Printf("%v, %v\n", l, k) // Check if the lines are parallel lDir := l.A.To(l.B) kDir := k.A.To(k.B) - if math.Abs(lDir.X) == math.Abs(kDir.X) && math.Abs(lDir.Y) == math.Abs(kDir.Y) { + fmt.Printf("%v, %v\n", lDir, kDir) + if lDir.X == kDir.X && lDir.Y == kDir.Y { + fmt.Println("p") return ZV, false } @@ -257,11 +267,14 @@ func (l Line) Intersect(k Line) (Vec, bool) { // Get the intersection point for the lines if they were infinitely long, check if the point exists on both of the // segments lm, lb := l.Formula() - km, kb := l.Formula() + km, kb := k.Formula() + fmt.Printf("%.2f, %.2f -- %.2f, %.2f\n", lm, lb, km, kb) // Coordinates of intersect x := (kb - lb) / (lm - km) y := lm*x + lb + fmt.Printf("(%.2f, %.2f)\n", x, y) + fmt.Printf("%t %t\n", l.Contains(V(x, y)), k.Contains(V(x, y))) if l.Contains(V(x, y)) && k.Contains(V(x, y)) { // The intersect point is on both line segments, they intersect. diff --git a/geometry_test.go b/geometry_test.go index e2414b85..ce31f9ad 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -893,6 +893,12 @@ func TestLine_Contains(t *testing.T) { args: args{v: pixel.V(5, 5)}, want: true, }, + { + name: "Point on negative sloped line", + fields: fields{A: pixel.V(0, 10), B: pixel.V(10, 0)}, + args: args{v: pixel.V(5, 5)}, + want: true, + }, { name: "Point not on line", fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, From 8cd352b7a32ea4b9d6dfd443d52a3b953e60b236 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 08:13:33 +0100 Subject: [PATCH 096/156] WIP line tests --- geometry.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/geometry.go b/geometry.go index d71cbd8e..5854914d 100644 --- a/geometry.go +++ b/geometry.go @@ -222,17 +222,10 @@ func (l Line) Closest(v Vec) Vec { m, b := l.Formula() perpendicularM := -1 / m perpendicularB := v.Y - (perpendicularM * v.X) - fmt.Println(m) - fmt.Println(b) - fmt.Println(perpendicularM) - fmt.Println(perpendicularB) // Coordinates of intersect (of infinite lines) x := (perpendicularB - b) / (m - perpendicularM) - y := m*x - b - - fmt.Println(x) - fmt.Println(y) + y := m*x + b return V(x, y) } @@ -253,11 +246,9 @@ func (l Line) Formula() (m, b float64) { // Intersect will return the point of intersection for the two line segments. If the line segments do not intersect, // this function will return the zero-vector and `false`. func (l Line) Intersect(k Line) (Vec, bool) { - fmt.Printf("%v, %v\n", l, k) // Check if the lines are parallel lDir := l.A.To(l.B) kDir := k.A.To(k.B) - fmt.Printf("%v, %v\n", lDir, kDir) if lDir.X == kDir.X && lDir.Y == kDir.Y { fmt.Println("p") return ZV, false @@ -268,13 +259,10 @@ func (l Line) Intersect(k Line) (Vec, bool) { // segments lm, lb := l.Formula() km, kb := k.Formula() - fmt.Printf("%.2f, %.2f -- %.2f, %.2f\n", lm, lb, km, kb) // Coordinates of intersect x := (kb - lb) / (lm - km) y := lm*x + lb - fmt.Printf("(%.2f, %.2f)\n", x, y) - fmt.Printf("%t %t\n", l.Contains(V(x, y)), k.Contains(V(x, y))) if l.Contains(V(x, y)) && k.Contains(V(x, y)) { // The intersect point is on both line segments, they intersect. From 11e2012ef5f1d8010b4c38e8ef36a34c90f7bbf2 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 08:23:34 +0100 Subject: [PATCH 097/156] WIP line tests --- geometry.go | 1 - geometry_test.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/geometry.go b/geometry.go index 5854914d..9f2fe92e 100644 --- a/geometry.go +++ b/geometry.go @@ -250,7 +250,6 @@ func (l Line) Intersect(k Line) (Vec, bool) { lDir := l.A.To(l.B) kDir := k.A.To(k.B) if lDir.X == kDir.X && lDir.Y == kDir.Y { - fmt.Println("p") return ZV, false } diff --git a/geometry_test.go b/geometry_test.go index ce31f9ad..2690054a 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1039,8 +1039,8 @@ func TestLine_IntersectCircle(t *testing.T) { { name: "Cirle intersects", fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, - args: args{c: pixel.C(pixel.V(5, 5), 1)}, - want: pixel.V(1, -1), + args: args{c: pixel.C(pixel.V(6, 4), 2)}, + want: pixel.V(0.5857864376269049, -0.5857864376269049), }, { name: "Cirle doesn't intersects", From eb3d7e0787411875f3490fcbe1a580e8d2c02d57 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 11:09:14 +0100 Subject: [PATCH 098/156] wip line tests --- geometry.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++-- geometry_test.go | 74 +++++++++++++++++++++++++++++++--------------- 2 files changed, 125 insertions(+), 26 deletions(-) diff --git a/geometry.go b/geometry.go index 9f2fe92e..d6f97092 100644 --- a/geometry.go +++ b/geometry.go @@ -206,8 +206,30 @@ func (l Line) Center() Vec { // Closest will return the point on the line which is closest to the `Vec` provided. func (l Line) Closest(v Vec) Vec { + // Closest point will be on a line, perpendicular to this line + m, b := l.Formula() + + // Account for horizontal lines + if m == 0 { + fmt.Println("h", l) + x := v.X + y := l.A.Y + fmt.Println(x, y) + return V(x, y) + } + + // Account for vertical lines + if math.IsInf(math.Abs(m), 1) { + fmt.Println("v", l) + x := l.A.X + y := v.Y + fmt.Println(x, y) + return V(x, y) + } + // Check if the point is within the lines' bounding box, if not, one of the endpoints will be the closest point if !l.Bounds().Contains(v) { + fmt.Println("out") // Not within bounding box toStart := v.To(l.A) toEnd := v.To(l.B) @@ -218,14 +240,14 @@ func (l Line) Closest(v Vec) Vec { return l.B } - // Closest point will be on a line, perpendicular to this line - m, b := l.Formula() perpendicularM := -1 / m perpendicularB := v.Y - (perpendicularM * v.X) + fmt.Println(m, b, perpendicularM, perpendicularB) // Coordinates of intersect (of infinite lines) x := (perpendicularB - b) / (m - perpendicularM) y := m*x + b + fmt.Println(x, y) return V(x, y) } @@ -237,6 +259,11 @@ func (l Line) Contains(v Vec) bool { // Formula will return the values that represent the line in the formula: y = mx + b func (l Line) Formula() (m, b float64) { + // Account for horizontal lines + if l.B.Y == l.A.Y { + return 0, l.A.Y + } + m = (l.B.Y - l.A.Y) / (l.B.X - l.A.X) b = l.A.Y - (m * l.A.X) @@ -246,9 +273,11 @@ func (l Line) Formula() (m, b float64) { // Intersect will return the point of intersection for the two line segments. If the line segments do not intersect, // this function will return the zero-vector and `false`. func (l Line) Intersect(k Line) (Vec, bool) { + fmt.Println(l, k) // Check if the lines are parallel lDir := l.A.To(l.B) kDir := k.A.To(k.B) + fmt.Println(lDir, kDir) if lDir.X == kDir.X && lDir.Y == kDir.Y { return ZV, false } @@ -258,10 +287,47 @@ func (l Line) Intersect(k Line) (Vec, bool) { // segments lm, lb := l.Formula() km, kb := k.Formula() + fmt.Println(lm, lb, km, kb) + + // Account for vertical lines + if math.IsInf(math.Abs(lm), 1) && math.IsInf(math.Abs(km), 1) { + // Both vertical, therefore parallel + return ZV, false + } + + if math.IsInf(math.Abs(lm), 1) || math.IsInf(math.Abs(km), 1) { + // One line is vertical + intersectM := lm + intersectB := lb + verticalLine := k + + if math.IsInf(math.Abs(lm), 1) { + intersectM = km + intersectB = kb + verticalLine = l + } + + maxVerticalY := verticalLine.A.Y + minVerticalY := verticalLine.B.Y + if verticalLine.B.Y > maxVerticalY { + maxVerticalY = verticalLine.B.Y + minVerticalY = verticalLine.A.Y + } + + y := intersectM*l.A.X + intersectB + if y > maxVerticalY || y < minVerticalY { + // Point is not on the horizontal line + return ZV, false + } + + return V(l.A.X, y), true + } // Coordinates of intersect x := (kb - lb) / (lm - km) y := lm*x + lb + fmt.Println(x, y) + fmt.Println(l.Contains(V(x, y)), k.Contains(V(x, y))) if l.Contains(V(x, y)) && k.Contains(V(x, y)) { // The intersect point is on both line segments, they intersect. @@ -290,18 +356,22 @@ func (l Line) IntersectCircle(c Circle) Vec { func (l Line) IntersectRect(r Rect) Vec { // Check if either end of the line segment are within the rectangle if r.Contains(l.A) || r.Contains(l.B) { + fmt.Println("yes1") // Use the `Rect.Intersect` to get minimal return value rIntersect := l.Bounds().Intersect(r) if rIntersect.H() > rIntersect.W() { + fmt.Println("yes2") // Go vertical return V(0, rIntersect.H()) } return V(rIntersect.W(), 0) } + fmt.Println("No") // Check if any of the rectangles' edges intersect with this line. for _, edge := range r.Edges() { if _, ok := l.Intersect(edge); ok { + fmt.Println(edge) // Get the closest points on the line to each corner, where: // - the point is contained by the rectangle // - the point is not the corner itself @@ -316,6 +386,9 @@ func (l Line) IntersectRect(r Rect) Vec { } } + fmt.Println(closest) + fmt.Println(closestCorner) + return closest.To(closestCorner) } } diff --git a/geometry_test.go b/geometry_test.go index 2690054a..dfcbf329 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -853,6 +853,18 @@ func TestLine_Closest(t *testing.T) { args: args{v: pixel.V(0, 10)}, want: pixel.V(5, 5), }, + { + name: "Point on next to vertical line", + fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)}, + args: args{v: pixel.V(6, 5)}, + want: pixel.V(5, 5), + }, + { + name: "Point on next to horizontal line", + fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)}, + args: args{v: pixel.V(5, 6)}, + want: pixel.V(5, 5), + }, { name: "Point on inline with line", fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, @@ -990,6 +1002,20 @@ func TestLine_Intersect(t *testing.T) { want: pixel.V(5, 5), want1: true, }, + { + name: "Line intersect with vertical", + fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)}, + args: args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))}, + want: pixel.V(5, 5), + want1: true, + }, + { + name: "Line intersect with horizontal", + fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)}, + args: args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))}, + want: pixel.V(5, 5), + want1: true, + }, { name: "Lines don't intersect", fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, @@ -1082,30 +1108,30 @@ func TestLine_IntersectRect(t *testing.T) { args: args{r: pixel.R(-1, 1, 5, 5)}, want: pixel.V(-1, 0), }, - { - name: "Line through rect horizontally", - fields: fields{A: pixel.V(-5, 0), B: pixel.V(5, 0)}, - args: args{r: pixel.R(-2, -5, 2, 1)}, - want: pixel.V(0, 1), - }, - { - name: "Line through rect diagonally bottom and left edges", - fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, - args: args{r: pixel.R(0, 2, 3, 3)}, - want: pixel.V(1, -1), - }, - { - name: "Line through rect diagonally top and right edges", - fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)}, - args: args{r: pixel.R(5, 0, 8, 3)}, - want: pixel.V(-1, -1), - }, - { - name: "Line with not rect intersect", - fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, - args: args{r: pixel.R(20, 20, 21, 21)}, - want: pixel.ZV, - }, + // { + // name: "Line through rect horizontally", + // fields: fields{A: pixel.V(-5, 0), B: pixel.V(5, 0)}, + // args: args{r: pixel.R(-2, -5, 2, 1)}, + // want: pixel.V(0, 1), + // }, + // { + // name: "Line through rect diagonally bottom and left edges", + // fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + // args: args{r: pixel.R(0, 2, 3, 3)}, + // want: pixel.V(1, -1), + // }, + // { + // name: "Line through rect diagonally top and right edges", + // fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)}, + // args: args{r: pixel.R(5, 0, 8, 3)}, + // want: pixel.V(-1, -1), + // }, + // { + // name: "Line with not rect intersect", + // fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + // args: args{r: pixel.R(20, 20, 21, 21)}, + // want: pixel.ZV, + // }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From aa7ef59ecd79828d3b5e2c35fdcac758fceb00b4 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 11:47:36 +0100 Subject: [PATCH 099/156] wip line tests --- geometry.go | 46 +++++++++++++++-------------------------- geometry_test.go | 54 +++++++++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/geometry.go b/geometry.go index d6f97092..9872b74d 100644 --- a/geometry.go +++ b/geometry.go @@ -211,25 +211,34 @@ func (l Line) Closest(v Vec) Vec { // Account for horizontal lines if m == 0 { - fmt.Println("h", l) x := v.X y := l.A.Y - fmt.Println(x, y) return V(x, y) } // Account for vertical lines if math.IsInf(math.Abs(m), 1) { - fmt.Println("v", l) x := l.A.X y := v.Y - fmt.Println(x, y) return V(x, y) } - // Check if the point is within the lines' bounding box, if not, one of the endpoints will be the closest point - if !l.Bounds().Contains(v) { - fmt.Println("out") + perpendicularM := -1 / m + perpendicularB := v.Y - (perpendicularM * v.X) + + // Coordinates of intersect (of infinite lines) + x := (perpendicularB - b) / (m - perpendicularM) + y := m*x + b + + // between is a helper function which determines whether 'x' is greater than min(a, b) and less than max(a, b) + between := func(a, b, x float64) bool { + min := math.Min(a, b) + max := math.Max(a, b) + return min < x && x < max + } + + // Check if the point lies between the x and y bounds of the segment + if !between(l.A.X, l.B.X, x) && !between(l.A.Y, l.B.Y, y) { // Not within bounding box toStart := v.To(l.A) toEnd := v.To(l.B) @@ -240,15 +249,6 @@ func (l Line) Closest(v Vec) Vec { return l.B } - perpendicularM := -1 / m - perpendicularB := v.Y - (perpendicularM * v.X) - fmt.Println(m, b, perpendicularM, perpendicularB) - - // Coordinates of intersect (of infinite lines) - x := (perpendicularB - b) / (m - perpendicularM) - y := m*x + b - fmt.Println(x, y) - return V(x, y) } @@ -273,11 +273,9 @@ func (l Line) Formula() (m, b float64) { // Intersect will return the point of intersection for the two line segments. If the line segments do not intersect, // this function will return the zero-vector and `false`. func (l Line) Intersect(k Line) (Vec, bool) { - fmt.Println(l, k) // Check if the lines are parallel lDir := l.A.To(l.B) kDir := k.A.To(k.B) - fmt.Println(lDir, kDir) if lDir.X == kDir.X && lDir.Y == kDir.Y { return ZV, false } @@ -287,7 +285,6 @@ func (l Line) Intersect(k Line) (Vec, bool) { // segments lm, lb := l.Formula() km, kb := k.Formula() - fmt.Println(lm, lb, km, kb) // Account for vertical lines if math.IsInf(math.Abs(lm), 1) && math.IsInf(math.Abs(km), 1) { @@ -326,8 +323,6 @@ func (l Line) Intersect(k Line) (Vec, bool) { // Coordinates of intersect x := (kb - lb) / (lm - km) y := lm*x + lb - fmt.Println(x, y) - fmt.Println(l.Contains(V(x, y)), k.Contains(V(x, y))) if l.Contains(V(x, y)) && k.Contains(V(x, y)) { // The intersect point is on both line segments, they intersect. @@ -356,22 +351,18 @@ func (l Line) IntersectCircle(c Circle) Vec { func (l Line) IntersectRect(r Rect) Vec { // Check if either end of the line segment are within the rectangle if r.Contains(l.A) || r.Contains(l.B) { - fmt.Println("yes1") // Use the `Rect.Intersect` to get minimal return value rIntersect := l.Bounds().Intersect(r) if rIntersect.H() > rIntersect.W() { - fmt.Println("yes2") // Go vertical return V(0, rIntersect.H()) } return V(rIntersect.W(), 0) } - fmt.Println("No") // Check if any of the rectangles' edges intersect with this line. for _, edge := range r.Edges() { if _, ok := l.Intersect(edge); ok { - fmt.Println(edge) // Get the closest points on the line to each corner, where: // - the point is contained by the rectangle // - the point is not the corner itself @@ -380,15 +371,12 @@ func (l Line) IntersectRect(r Rect) Vec { closestCorner := corners[0] for _, c := range corners { cc := l.Closest(c) - if closest != ZV || (closest.Len() > cc.Len() && r.Contains(cc)) { + if closest == ZV || (closest.Len() > cc.Len() && r.Contains(cc)) { closest = cc closestCorner = c } } - fmt.Println(closest) - fmt.Println(closestCorner) - return closest.To(closestCorner) } } diff --git a/geometry_test.go b/geometry_test.go index dfcbf329..86f3a452 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -865,6 +865,12 @@ func TestLine_Closest(t *testing.T) { args: args{v: pixel.V(5, 6)}, want: pixel.V(5, 5), }, + { + name: "Point far from line", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{v: pixel.V(80, -70)}, + want: pixel.V(5, 5), + }, { name: "Point on inline with line", fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, @@ -1108,30 +1114,30 @@ func TestLine_IntersectRect(t *testing.T) { args: args{r: pixel.R(-1, 1, 5, 5)}, want: pixel.V(-1, 0), }, - // { - // name: "Line through rect horizontally", - // fields: fields{A: pixel.V(-5, 0), B: pixel.V(5, 0)}, - // args: args{r: pixel.R(-2, -5, 2, 1)}, - // want: pixel.V(0, 1), - // }, - // { - // name: "Line through rect diagonally bottom and left edges", - // fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, - // args: args{r: pixel.R(0, 2, 3, 3)}, - // want: pixel.V(1, -1), - // }, - // { - // name: "Line through rect diagonally top and right edges", - // fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)}, - // args: args{r: pixel.R(5, 0, 8, 3)}, - // want: pixel.V(-1, -1), - // }, - // { - // name: "Line with not rect intersect", - // fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, - // args: args{r: pixel.R(20, 20, 21, 21)}, - // want: pixel.ZV, - // }, + { + name: "Line through rect horizontally", + fields: fields{A: pixel.V(0, 1), B: pixel.V(10, 1)}, + args: args{r: pixel.R(1, 0, 5, 5)}, + want: pixel.V(0, -1), + }, + { + name: "Line through rect diagonally bottom and left edges", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{r: pixel.R(0, 2, 3, 3)}, + want: pixel.V(-1, 1), + }, + { + name: "Line through rect diagonally top and right edges", + fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)}, + args: args{r: pixel.R(5, 0, 8, 3)}, + want: pixel.V(-2.5, -2.5), + }, + { + name: "Line with not rect intersect", + fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, + args: args{r: pixel.R(20, 20, 21, 21)}, + want: pixel.ZV, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From e99ac56daa9ecf5446670c8ae31198268d6cfbef Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 11:52:42 +0100 Subject: [PATCH 100/156] wip line tests --- geometry.go | 6 ++++++ geometry_test.go | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/geometry.go b/geometry.go index 9872b74d..41d2b4c4 100644 --- a/geometry.go +++ b/geometry.go @@ -402,6 +402,12 @@ func (l Line) Moved(delta Vec) Line { func (l Line) Rotated(around Vec, angle float64) Line { // Move the line so we can use `Vec.Rotated` lineShifted := l.Moved(around.Scaled(-1)) + + fmt.Println(around.Scaled(-1)) + fmt.Println(lineShifted) + fmt.Println(lineShifted.A.Rotated(angle)) + fmt.Println(lineShifted.B.Rotated(angle)) + lineRotated := Line{ A: lineShifted.A.Rotated(angle), B: lineShifted.B.Rotated(angle), diff --git a/geometry_test.go b/geometry_test.go index 86f3a452..2a08e5c9 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1216,24 +1216,24 @@ func TestLine_Rotated(t *testing.T) { args args want pixel.Line }{ - { - name: "Rotating around line center", - fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, - args: args{around: pixel.V(2, 2), angle: 2 * math.Pi}, - want: pixel.L(pixel.V(1, 1), pixel.V(3, 3)), - }, + // { + // name: "Rotating around line center", + // fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, + // args: args{around: pixel.V(2, 2), angle: 2 * math.Pi}, + // want: pixel.L(pixel.V(1, 1), pixel.V(3, 3)), + // }, { name: "Rotating around x-y origin", fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, args: args{around: pixel.V(0, 0), angle: 2 * math.Pi}, want: pixel.L(pixel.V(-1, -1), pixel.V(-3, -3)), }, - { - name: "Rotating around line end", - fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, - args: args{around: pixel.V(1, 1), angle: 2 * math.Pi}, - want: pixel.L(pixel.V(1, 1), pixel.V(-2, -2)), - }, + // { + // name: "Rotating around line end", + // fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, + // args: args{around: pixel.V(1, 1), angle: 2 * math.Pi}, + // want: pixel.L(pixel.V(1, 1), pixel.V(-2, -2)), + // }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From faf6558294545215844fadaa3b4b68044513e904 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 12:03:07 +0100 Subject: [PATCH 101/156] wip line tests --- geometry_test.go | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 2a08e5c9..ef00f336 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1216,24 +1216,24 @@ func TestLine_Rotated(t *testing.T) { args args want pixel.Line }{ - // { - // name: "Rotating around line center", - // fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, - // args: args{around: pixel.V(2, 2), angle: 2 * math.Pi}, - // want: pixel.L(pixel.V(1, 1), pixel.V(3, 3)), - // }, + { + name: "Rotating around line center", + fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, + args: args{around: pixel.V(2, 2), angle: math.Pi}, + want: pixel.L(pixel.V(3, 3), pixel.V(1, 1)), + }, { name: "Rotating around x-y origin", fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, - args: args{around: pixel.V(0, 0), angle: 2 * math.Pi}, + args: args{around: pixel.V(0, 0), angle: math.Pi}, want: pixel.L(pixel.V(-1, -1), pixel.V(-3, -3)), }, - // { - // name: "Rotating around line end", - // fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, - // args: args{around: pixel.V(1, 1), angle: 2 * math.Pi}, - // want: pixel.L(pixel.V(1, 1), pixel.V(-2, -2)), - // }, + { + name: "Rotating around line end", + fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)}, + args: args{around: pixel.V(1, 1), angle: math.Pi}, + want: pixel.L(pixel.V(1, 1), pixel.V(-1, -1)), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1241,7 +1241,13 @@ func TestLine_Rotated(t *testing.T) { A: tt.fields.A, B: tt.fields.B, } - if got := l.Rotated(tt.args.around, tt.args.angle); !reflect.DeepEqual(got, tt.want) { + // Have to round the results, due to floating-point in accuracies. Results are correct to approximately + // 10 decimal places. + got := l.Rotated(tt.args.around, tt.args.angle) + if math.Round(got.A.X) != tt.want.A.X || + math.Round(got.B.X) != tt.want.B.X || + math.Round(got.A.Y) != tt.want.A.Y || + math.Round(got.B.Y) != tt.want.B.Y { t.Errorf("Line.Rotated() = %v, want %v", got, tt.want) } }) From e5ff236d71ae06b01465645ee20d0420f927b124 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 12:03:58 +0100 Subject: [PATCH 102/156] Removed debug lines --- geometry.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/geometry.go b/geometry.go index 41d2b4c4..4f4c1e46 100644 --- a/geometry.go +++ b/geometry.go @@ -403,11 +403,6 @@ func (l Line) Rotated(around Vec, angle float64) Line { // Move the line so we can use `Vec.Rotated` lineShifted := l.Moved(around.Scaled(-1)) - fmt.Println(around.Scaled(-1)) - fmt.Println(lineShifted) - fmt.Println(lineShifted.A.Rotated(angle)) - fmt.Println(lineShifted.B.Rotated(angle)) - lineRotated := Line{ A: lineShifted.A.Rotated(angle), B: lineShifted.B.Rotated(angle), From 98d5b9b417d585a2d0baf4a28c7b2dd3fe7b4b35 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 12:12:27 +0100 Subject: [PATCH 103/156] Adding vertical/horizontal edge cases --- geometry.go | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/geometry.go b/geometry.go index 4f4c1e46..73f4854b 100644 --- a/geometry.go +++ b/geometry.go @@ -206,20 +206,48 @@ func (l Line) Center() Vec { // Closest will return the point on the line which is closest to the `Vec` provided. func (l Line) Closest(v Vec) Vec { - // Closest point will be on a line, perpendicular to this line + // between is a helper function which determines whether 'x' is greater than min(a, b) and less than max(a, b) + between := func(a, b, x float64) bool { + min := math.Min(a, b) + max := math.Max(a, b) + return min < x && x < max + } + + // Closest point will be on a line which perpendicular to this line. + // If and only if the infinite perpendicular line intersects the segment. m, b := l.Formula() // Account for horizontal lines if m == 0 { x := v.X y := l.A.Y - return V(x, y) + + // check if the X coordinate of v is on the line + if between(l.A.X, l.B.X, v.X) { + return V(x, y) + } + + // Otherwise get the closest endpoint + if l.A.To(v).Len() < l.B.To(v).Len() { + return l.A + } + return l.B } // Account for vertical lines if math.IsInf(math.Abs(m), 1) { x := l.A.X y := v.Y + + // check if the Y coordinate of v is on the line + if between(l.A.Y, l.B.Y, v.Y) { + return V(x, y) + } + + // Otherwise get the closest endpoint + if l.A.To(v).Len() < l.B.To(v).Len() { + return l.A + } return V(x, y) } @@ -230,13 +258,6 @@ func (l Line) Closest(v Vec) Vec { x := (perpendicularB - b) / (m - perpendicularM) y := m*x + b - // between is a helper function which determines whether 'x' is greater than min(a, b) and less than max(a, b) - between := func(a, b, x float64) bool { - min := math.Min(a, b) - max := math.Max(a, b) - return min < x && x < max - } - // Check if the point lies between the x and y bounds of the segment if !between(l.A.X, l.B.X, x) && !between(l.A.Y, l.B.Y, y) { // Not within bounding box From a1d36f8c7e7635a9f6bbbd8bf224a9c9d17ebc2e Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 12:16:27 +0100 Subject: [PATCH 104/156] Clarified comment --- geometry.go | 1 + 1 file changed, 1 insertion(+) diff --git a/geometry.go b/geometry.go index 73f4854b..da8fb7e5 100644 --- a/geometry.go +++ b/geometry.go @@ -279,6 +279,7 @@ func (l Line) Contains(v Vec) bool { } // Formula will return the values that represent the line in the formula: y = mx + b +// This function will return `math.Inf+, math.Inf-` for a vertical line. func (l Line) Formula() (m, b float64) { // Account for horizontal lines if l.B.Y == l.A.Y { From 3592de858cea46b5c0cf234aeeed7a397556071a Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 12:19:30 +0100 Subject: [PATCH 105/156] Clarified comment --- geometry.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/geometry.go b/geometry.go index da8fb7e5..e28cf2c2 100644 --- a/geometry.go +++ b/geometry.go @@ -354,8 +354,8 @@ func (l Line) Intersect(k Line) (Vec, bool) { return ZV, false } -// IntersectCircle will return the shortest `Vec` such that the Line and Circle no longer intesect. If they do not -// intersect at all, this function will return a zero-vector. +// IntersectCircle will return the shortest `Vec` such that moving the Line by that Vec will cause the Line and Circle +// to no longer intesect. If they do not intersect at all, this function will return a zero-vector. func (l Line) IntersectCircle(c Circle) Vec { // Get the point on the line closest to the center of the circle. closest := l.Closest(c.Center) @@ -368,8 +368,8 @@ func (l Line) IntersectCircle(c Circle) Vec { return cirToClosest.Scaled(cirToClosest.Len() - c.Radius) } -// IntersectRect will return the shortest `Vec` such that the Line and Rect no longer intesect. If they do not -// intersect at all, this function will return a zero-vector. +// IntersectRect will return the shortest `Vec` such that moving the Line by that Vec will cause the Line and Rect to +// no longer intesect. If they do not intersect at all, this function will return a zero-vector. func (l Line) IntersectRect(r Rect) Vec { // Check if either end of the line segment are within the rectangle if r.Contains(l.A) || r.Contains(l.B) { From e6392a228dedb4cef08d0549cad11e37e9f637f1 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 12:22:21 +0100 Subject: [PATCH 106/156] Making len more sensible --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index e28cf2c2..486eecd5 100644 --- a/geometry.go +++ b/geometry.go @@ -409,7 +409,7 @@ func (l Line) IntersectRect(r Rect) Vec { // Len returns the length of the line segment. func (l Line) Len() float64 { - return l.A.Sub(l.B).Len() + return l.A.To(l.B).Len() } // Moved will return a line moved by the delta `Vec` provided. From 352785e1b8558579028e4728ad05af54f5690389 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 12:37:33 +0100 Subject: [PATCH 107/156] Supporting pre go1.10 --- geometry_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index ef00f336..488a5a34 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1202,6 +1202,15 @@ func TestLine_Len(t *testing.T) { } func TestLine_Rotated(t *testing.T) { + // round returns the nearest integer, rounding ties away from zero. + // This is required because `math.Round` wasn't introduced until Go1.10 + round := func(x float64) float64 { + t := math.Trunc(x) + if math.Abs(x-t) >= 0.5 { + return t + math.Copysign(1, x) + } + return t + } type fields struct { A pixel.Vec B pixel.Vec @@ -1244,10 +1253,10 @@ func TestLine_Rotated(t *testing.T) { // Have to round the results, due to floating-point in accuracies. Results are correct to approximately // 10 decimal places. got := l.Rotated(tt.args.around, tt.args.angle) - if math.Round(got.A.X) != tt.want.A.X || - math.Round(got.B.X) != tt.want.B.X || - math.Round(got.A.Y) != tt.want.A.Y || - math.Round(got.B.Y) != tt.want.B.Y { + if round(got.A.X) != tt.want.A.X || + round(got.B.X) != tt.want.B.X || + round(got.A.Y) != tt.want.A.Y || + round(got.B.Y) != tt.want.B.Y { t.Errorf("Line.Rotated() = %v, want %v", got, tt.want) } }) From c3e69c4f3570570687e431d7547eefe8bb878d2d Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 15:26:38 +0100 Subject: [PATCH 108/156] Tidying up function comments --- geometry.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/geometry.go b/geometry.go index 486eecd5..4b45afb5 100644 --- a/geometry.go +++ b/geometry.go @@ -186,7 +186,7 @@ type Line struct { A, B Vec } -// L creates and returns a new Line object. +// L creates and returns a new Line. func L(from, to Vec) Line { return Line{ A: from, @@ -194,7 +194,7 @@ func L(from, to Vec) Line { } } -// Bounds returns the lines bounding box. This is in the form of a normalized `Rect`. +// Bounds returns the lines bounding box. This is in the form of a normalized Rect. func (l Line) Bounds() Rect { return R(l.A.X, l.A.Y, l.B.X, l.B.Y).Norm() } @@ -204,9 +204,9 @@ func (l Line) Center() Vec { return l.A.Add(l.A.To(l.B).Scaled(0.5)) } -// Closest will return the point on the line which is closest to the `Vec` provided. +// Closest will return the point on the line which is closest to the Vec provided. func (l Line) Closest(v Vec) Vec { - // between is a helper function which determines whether 'x' is greater than min(a, b) and less than max(a, b) + // between is a helper function which determines whether x is greater than min(a, b) and less than max(a, b) between := func(a, b, x float64) bool { min := math.Min(a, b) max := math.Max(a, b) @@ -273,13 +273,13 @@ func (l Line) Closest(v Vec) Vec { return V(x, y) } -// Contains returns whether the provided `Vec` lies on the line +// Contains returns whether the provided Vec lies on the line func (l Line) Contains(v Vec) bool { return l.Closest(v) == v } // Formula will return the values that represent the line in the formula: y = mx + b -// This function will return `math.Inf+, math.Inf-` for a vertical line. +// This function will return math.Inf+, math.Inf- for a vertical line. func (l Line) Formula() (m, b float64) { // Account for horizontal lines if l.B.Y == l.A.Y { @@ -293,7 +293,7 @@ func (l Line) Formula() (m, b float64) { } // Intersect will return the point of intersection for the two line segments. If the line segments do not intersect, -// this function will return the zero-vector and `false`. +// this function will return the zero-vector and false. func (l Line) Intersect(k Line) (Vec, bool) { // Check if the lines are parallel lDir := l.A.To(l.B) @@ -354,7 +354,7 @@ func (l Line) Intersect(k Line) (Vec, bool) { return ZV, false } -// IntersectCircle will return the shortest `Vec` such that moving the Line by that Vec will cause the Line and Circle +// IntersectCircle will return the shortest Vec such that moving the Line by that Vec will cause the Line and Circle // to no longer intesect. If they do not intersect at all, this function will return a zero-vector. func (l Line) IntersectCircle(c Circle) Vec { // Get the point on the line closest to the center of the circle. @@ -368,12 +368,12 @@ func (l Line) IntersectCircle(c Circle) Vec { return cirToClosest.Scaled(cirToClosest.Len() - c.Radius) } -// IntersectRect will return the shortest `Vec` such that moving the Line by that Vec will cause the Line and Rect to +// IntersectRect will return the shortest Vec such that moving the Line by that Vec will cause the Line and Rect to // no longer intesect. If they do not intersect at all, this function will return a zero-vector. func (l Line) IntersectRect(r Rect) Vec { // Check if either end of the line segment are within the rectangle if r.Contains(l.A) || r.Contains(l.B) { - // Use the `Rect.Intersect` to get minimal return value + // Use the Rect.Intersect to get minimal return value rIntersect := l.Bounds().Intersect(r) if rIntersect.H() > rIntersect.W() { // Go vertical @@ -412,7 +412,7 @@ func (l Line) Len() float64 { return l.A.To(l.B).Len() } -// Moved will return a line moved by the delta `Vec` provided. +// Moved will return a line moved by the delta Vec provided. func (l Line) Moved(delta Vec) Line { return Line{ A: l.A.Add(delta), @@ -420,7 +420,7 @@ func (l Line) Moved(delta Vec) Line { } } -// Rotated will rotate the line around the provided `Vec`. +// Rotated will rotate the line around the provided Vec. func (l Line) Rotated(around Vec, angle float64) Line { // Move the line so we can use `Vec.Rotated` lineShifted := l.Moved(around.Scaled(-1)) @@ -438,7 +438,7 @@ func (l Line) Scaled(scale float64) Line { return l.ScaledXY(l.Center(), scale) } -// ScaledXY will return the line scaled around the `Vec` provided. +// ScaledXY will return the line scaled around the Vec provided. func (l Line) ScaledXY(around Vec, scale float64) Line { toA := around.To(l.A).Scaled(scale) toB := around.To(l.B).Scaled(scale) @@ -613,7 +613,7 @@ func (r Rect) IntersectCircle(c Circle) Vec { return c.IntersectRect(r).Scaled(-1) } -// IntersectLine will return the shortest `Vec` such that if the Rect is moved by the Vec returned, the Line and Rect no +// IntersectLine will return the shortest Vec such that if the Rect is moved by the Vec returned, the Line and Rect no // longer intersect. func (r Rect) IntersectLine(l Line) Vec { return l.IntersectRect(r).Scaled(-1) @@ -776,7 +776,7 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectLine will return the shortest `Vec` such that if the Rect is moved by the Vec returned, the Line and Rect no +// IntersectLine will return the shortest Vec such that if the Rect is moved by the Vec returned, the Line and Rect no // longer intersect. func (c Circle) IntersectLine(l Line) Vec { return l.IntersectCircle(c).Scaled(-1) From 83c62a031375c1597c4341884fcc674a011f7dab Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 16:21:57 +0100 Subject: [PATCH 109/156] fixing line intersect function --- geometry.go | 56 +++++++++++++++++++++------------ geometry_test.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 20 deletions(-) diff --git a/geometry.go b/geometry.go index 4b45afb5..f2c00177 100644 --- a/geometry.go +++ b/geometry.go @@ -314,38 +314,26 @@ func (l Line) Intersect(k Line) (Vec, bool) { return ZV, false } + var x, y float64 + if math.IsInf(math.Abs(lm), 1) || math.IsInf(math.Abs(km), 1) { // One line is vertical intersectM := lm intersectB := lb - verticalLine := k if math.IsInf(math.Abs(lm), 1) { intersectM = km intersectB = kb - verticalLine = l - } - - maxVerticalY := verticalLine.A.Y - minVerticalY := verticalLine.B.Y - if verticalLine.B.Y > maxVerticalY { - maxVerticalY = verticalLine.B.Y - minVerticalY = verticalLine.A.Y - } - - y := intersectM*l.A.X + intersectB - if y > maxVerticalY || y < minVerticalY { - // Point is not on the horizontal line - return ZV, false } - return V(l.A.X, y), true + y = intersectM*l.A.X + intersectB + x = l.A.X + } else { + // Coordinates of intersect + x = (kb - lb) / (lm - km) + y = lm*x + lb } - // Coordinates of intersect - x := (kb - lb) / (lm - km) - y := lm*x + lb - if l.Contains(V(x, y)) && k.Contains(V(x, y)) { // The intersect point is on both line segments, they intersect. return V(x, y), true @@ -619,6 +607,28 @@ func (r Rect) IntersectLine(l Line) Vec { return l.IntersectRect(r).Scaled(-1) } +// IntersectionPoints returns all the points where the Rect intersects with the line provided. This can be zero, one or +// two points, depending on the location of the shapes. +func (r Rect) IntersectionPoints(l Line) []Vec { + // Use map keys to ensure unique points + pointMap := make(map[Vec]struct{}) + + for _, edge := range r.Edges() { + if intersect, ok := edge.Intersect(l); ok { + fmt.Println(edge) + fmt.Println(l) + fmt.Println(intersect) + pointMap[intersect] = struct{}{} + } + } + + points := make([]Vec, 0, len(pointMap)) + for point := range pointMap { + points = append(points, point) + } + return points +} + // Vertices returns a slice of the four corners which make up the rectangle. func (r Rect) Vertices() [4]Vec { return [4]Vec{ @@ -858,6 +868,12 @@ func (c Circle) IntersectRect(r Rect) Vec { } } +// IntersectionPoints returns all the points where the Circle intersects with the line provided. This can be zero, one or +// two points, depending on the location of the shapes. +func (c Circle) IntersectionPoints(l Line) []Vec { + return []Vec{} +} + // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such // as movement, scaling and rotations. // diff --git a/geometry_test.go b/geometry_test.go index 488a5a34..4f8f8df9 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -609,6 +609,35 @@ func TestCircle_Intersect(t *testing.T) { } } +func TestCircle_IntersectPoints(t *testing.T) { + type fields struct { + Center pixel.Vec + Radius float64 + } + type args struct { + l pixel.Line + } + tests := []struct { + name string + fields fields + args args + want []pixel.Vec + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := pixel.Circle{ + Center: tt.fields.Center, + Radius: tt.fields.Radius, + } + if got := c.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Circle.IntersectPoints() = %v, want %v", got, tt.want) + } + }) + } +} + func TestRect_IntersectCircle(t *testing.T) { // closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them. // Effectively this compares two floats to a given decimal point. @@ -759,6 +788,52 @@ func TestRect_IntersectCircle(t *testing.T) { } } +func TestRect_IntersectionPoints(t *testing.T) { + type fields struct { + Min pixel.Vec + Max pixel.Vec + } + type args struct { + l pixel.Line + } + tests := []struct { + name string + fields fields + args args + want []pixel.Vec + }{ + { + name: "No intersection points", + fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)}, + args: args{l: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))}, + want: []pixel.Vec{}, + }, + // { + // name: "One intersection point", + // fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)}, + // args: args{l: pixel.L(pixel.V(2, 0), pixel.V(2, 2))}, + // want: []pixel.Vec{pixel.V(2, 1)}, + // }, + // { + // name: "Two intersection points", + // fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)}, + // args: args{l: pixel.L(pixel.V(0, 2), pixel.V(6, 2))}, + // want: []pixel.Vec{pixel.V(1, 2), pixel.V(5, 2)}, + // }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := pixel.Rect{ + Min: tt.fields.Min, + Max: tt.fields.Max, + } + if got := r.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.IntersectPoints() = %v, want %v", got, tt.want) + } + }) + } +} + func TestLine_Bounds(t *testing.T) { type fields struct { A pixel.Vec @@ -1008,6 +1083,13 @@ func TestLine_Intersect(t *testing.T) { want: pixel.V(5, 5), want1: true, }, + { + name: "Lines intersect 2", + fields: fields{A: pixel.V(1, 1), B: pixel.V(1, 5)}, + args: args{k: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))}, + want: pixel.ZV, + want1: false, + }, { name: "Line intersect with vertical", fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)}, From 4795a92b41da2c1be64300226ac43228ca7d43ce Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 3 Apr 2019 16:58:30 +0100 Subject: [PATCH 110/156] fixed line intersect and added rect intersection points --- geometry.go | 13 ++++++------- geometry_test.go | 46 ++++++++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/geometry.go b/geometry.go index f2c00177..4f1ae022 100644 --- a/geometry.go +++ b/geometry.go @@ -248,7 +248,7 @@ func (l Line) Closest(v Vec) Vec { if l.A.To(v).Len() < l.B.To(v).Len() { return l.A } - return V(x, y) + return l.B } perpendicularM := -1 / m @@ -320,14 +320,16 @@ func (l Line) Intersect(k Line) (Vec, bool) { // One line is vertical intersectM := lm intersectB := lb + verticalLine := k if math.IsInf(math.Abs(lm), 1) { intersectM = km intersectB = kb + verticalLine = l } - y = intersectM*l.A.X + intersectB - x = l.A.X + y = intersectM*verticalLine.A.X + intersectB + x = verticalLine.A.X } else { // Coordinates of intersect x = (kb - lb) / (lm - km) @@ -614,10 +616,7 @@ func (r Rect) IntersectionPoints(l Line) []Vec { pointMap := make(map[Vec]struct{}) for _, edge := range r.Edges() { - if intersect, ok := edge.Intersect(l); ok { - fmt.Println(edge) - fmt.Println(l) - fmt.Println(intersect) + if intersect, ok := l.Intersect(edge); ok { pointMap[intersect] = struct{}{} } } diff --git a/geometry_test.go b/geometry_test.go index 4f8f8df9..a311159f 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -808,18 +808,18 @@ func TestRect_IntersectionPoints(t *testing.T) { args: args{l: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))}, want: []pixel.Vec{}, }, - // { - // name: "One intersection point", - // fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)}, - // args: args{l: pixel.L(pixel.V(2, 0), pixel.V(2, 2))}, - // want: []pixel.Vec{pixel.V(2, 1)}, - // }, - // { - // name: "Two intersection points", - // fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)}, - // args: args{l: pixel.L(pixel.V(0, 2), pixel.V(6, 2))}, - // want: []pixel.Vec{pixel.V(1, 2), pixel.V(5, 2)}, - // }, + { + name: "One intersection point", + fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)}, + args: args{l: pixel.L(pixel.V(2, 0), pixel.V(2, 3))}, + want: []pixel.Vec{pixel.V(2, 1)}, + }, + { + name: "Two intersection points", + fields: fields{Min: pixel.V(1, 1), Max: pixel.V(5, 5)}, + args: args{l: pixel.L(pixel.V(0, 2), pixel.V(6, 2))}, + want: []pixel.Vec{pixel.V(1, 2), pixel.V(5, 2)}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1085,10 +1085,10 @@ func TestLine_Intersect(t *testing.T) { }, { name: "Lines intersect 2", - fields: fields{A: pixel.V(1, 1), B: pixel.V(1, 5)}, - args: args{k: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))}, - want: pixel.ZV, - want1: false, + fields: fields{A: pixel.V(5, 1), B: pixel.V(1, 1)}, + args: args{k: pixel.L(pixel.V(2, 0), pixel.V(2, 3))}, + want: pixel.V(2, 1), + want1: true, }, { name: "Line intersect with vertical", @@ -1111,6 +1111,20 @@ func TestLine_Intersect(t *testing.T) { want: pixel.ZV, want1: false, }, + { + name: "Lines don't intersect 2", + fields: fields{A: pixel.V(1, 1), B: pixel.V(1, 5)}, + args: args{k: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))}, + want: pixel.ZV, + want1: false, + }, + { + name: "Lines don't intersect 3", + fields: fields{A: pixel.V(2, 0), B: pixel.V(2, 3)}, + args: args{k: pixel.L(pixel.V(1, 5), pixel.V(5, 5))}, + want: pixel.ZV, + want1: false, + }, { name: "Lines parallel", fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)}, From f3377bb16fc5d0be5429aa8f14e62ae0fb4e630d Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 11:53:48 +0100 Subject: [PATCH 111/156] Added circle intersection points --- geometry.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++- geometry_test.go | 82 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 156 insertions(+), 17 deletions(-) diff --git a/geometry.go b/geometry.go index 4f1ae022..32b2467d 100644 --- a/geometry.go +++ b/geometry.go @@ -707,6 +707,12 @@ func (c Circle) Contains(u Vec) bool { return c.Radius >= toCenter.Len() } +// Formula returns the values of h and k, for the equation of the circle: (x-h)^2 + (y-k)^2 = r^2 +// where r is the radius of the circle. +func (c Circle) Formula() (h, k float64) { + return c.Center.X, c.Center.Y +} + // maxCircle will return the larger circle based on the radius. func maxCircle(c, d Circle) Circle { if c.Radius < d.Radius { @@ -870,7 +876,90 @@ func (c Circle) IntersectRect(r Rect) Vec { // IntersectionPoints returns all the points where the Circle intersects with the line provided. This can be zero, one or // two points, depending on the location of the shapes. func (c Circle) IntersectionPoints(l Line) []Vec { - return []Vec{} + cContainsA := c.Contains(l.A) + cContainsB := c.Contains(l.B) + + // Special case for both endpoint being contained within the circle + if cContainsA && cContainsB { + return []Vec{} + } + + // Get closest point on the line to this circles' center + closestToCenter := l.Closest(c.Center) + + // If the distance to the closest point is greater than the radius, there are no points of intersection + if closestToCenter.To(c.Center).Len() > c.Radius { + return []Vec{} + } + + // If the distance to the closest point is equal to the radius, the line is tangent and the closest point is the + // point at which it touches the circle. + if closestToCenter.To(c.Center).Len() == c.Radius { + return []Vec{closestToCenter} + } + + // Special case for endpoint being on the circles' center + if c.Center == l.A || c.Center == l.B { + otherEnd := l.B + if c.Center == l.B { + otherEnd = l.A + } + intersect := c.Center.Add(c.Center.To(otherEnd).Unit().Scaled(c.Radius)) + return []Vec{intersect} + } + + // This means the distance to the closest point is less than the radius, so there is at least one intersection, + // possibly two. + + // If one of the end points exists within the circle, there is only one intersection + if cContainsA || cContainsB { + containedPoint := l.A + otherEnd := l.B + if cContainsB { + containedPoint = l.B + otherEnd = l.A + } + + // Use trigonometry to get the length of the line between the contained point and the intersection point. + // The following is used to describe the triangle formed: + // - a is the side between contained point and circle center + // - b is the side between the center and the intersection point (radius) + // - c is the side between the contained point and the intersection point + // The captials of these letters are used as the angles opposite the respective sides. + // a and b are known + a := containedPoint.To(c.Center).Len() + b := c.Radius + // B can be calculated by subtracting the angle of b (to the x-axis) from the angle of c (to the x-axis) + B := containedPoint.To(c.Center).Angle() - containedPoint.To(otherEnd).Angle() + // Using the Sin rule we can get A + A := math.Asin((a * math.Sin(B)) / b) + // Using the rule that there are 180 degrees (or Pi radians) in a triangle, we can now get C + C := math.Pi - A + B + // If C is zero, the line segment is in-line with the center-intersect line. + var c float64 + if C == 0 { + c = b - a + } else { + // Using the Sine rule again, we can now get c + c = (a * math.Sin(C)) / math.Sin(A) + } + // Travelling from the contained point to the other end by length of a will provide the intersection point. + return []Vec{ + containedPoint.Add(containedPoint.To(otherEnd).Unit().Scaled(c)), + } + } + + // Otherwise the endpoints exist outside of the circle, and the line segment intersects in two locations. + // The vector formed by going from the closest point to the center of the circle will be perpendicular to the line; + // this forms a right-angled triangle with the intersection points, with the radius as the hypotenuse. + // Calculate the other triangles' sides' length. + a := math.Sqrt(math.Pow(c.Radius, 2) - math.Pow(closestToCenter.To(c.Center).Len(), 2)) + + // Travelling in both directions from the closest point by length of a will provide the two intersection points. + first := closestToCenter.Add(closestToCenter.To(l.A).Unit().Scaled(a)) + second := closestToCenter.Add(closestToCenter.To(l.B).Unit().Scaled(a)) + + return []Vec{first, second} } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such diff --git a/geometry_test.go b/geometry_test.go index a311159f..2f214a7b 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -10,6 +10,19 @@ import ( "github.com/stretchr/testify/assert" ) +// closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them. +// Effectively this compares two floats to a given decimal point. +// Example: +// closeEnough(100.125342432, 100.125, 2) == true +// closeEnough(math.Pi, 3.14, 2) == true +// closeEnough(0.1234, 0.1245, 3) == false +func closeEnough(got, expected float64, decimalAccuracy int) bool { + gotShifted := got * math.Pow10(decimalAccuracy) + expectedShifted := expected * math.Pow10(decimalAccuracy) + + return math.Trunc(gotShifted) == math.Trunc(expectedShifted) +} + func TestRect_Edges(t *testing.T) { type fields struct { Min pixel.Vec @@ -623,7 +636,54 @@ func TestCircle_IntersectPoints(t *testing.T) { args args want []pixel.Vec }{ - // TODO: Add test cases. + { + name: "Line intersects circle at two points", + fields: fields{Center: pixel.V(2, 2), Radius: 1}, + args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))}, + want: []pixel.Vec{pixel.V(1.292, 1.292), pixel.V(2.707, 2.707)}, + }, + { + name: "Line intersects circle at one point", + fields: fields{Center: pixel.V(-0.5, -0.5), Radius: 1}, + args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))}, + want: []pixel.Vec{pixel.V(0.207, 0.207)}, + }, + { + name: "Line endpoint is circle center", + fields: fields{Center: pixel.V(0, 0), Radius: 1}, + args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))}, + want: []pixel.Vec{pixel.V(0.707, 0.707)}, + }, + { + name: "Both line endpoints within circle", + fields: fields{Center: pixel.V(0, 0), Radius: 1}, + args: args{pixel.L(pixel.V(0.2, 0.2), pixel.V(0.5, 0.5))}, + want: []pixel.Vec{}, + }, + { + name: "Line does not intersect circle", + fields: fields{Center: pixel.V(10, 0), Radius: 1}, + args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))}, + want: []pixel.Vec{}, + }, + { + name: "Horizontal line intersects circle at two points", + fields: fields{Center: pixel.V(5, 5), Radius: 1}, + args: args{pixel.L(pixel.V(0, 5), pixel.V(10, 5))}, + want: []pixel.Vec{pixel.V(4, 5), pixel.V(6, 5)}, + }, + { + name: "Vertical line intersects circle at two points", + fields: fields{Center: pixel.V(5, 5), Radius: 1}, + args: args{pixel.L(pixel.V(5, 0), pixel.V(5, 10))}, + want: []pixel.Vec{pixel.V(5, 4), pixel.V(5, 6)}, + }, + { + name: "Left and down line intersects circle at two points", + fields: fields{Center: pixel.V(5, 5), Radius: 1}, + args: args{pixel.L(pixel.V(10, 10), pixel.V(0, 0))}, + want: []pixel.Vec{pixel.V(5.707, 5.707), pixel.V(4.292, 4.292)}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -631,27 +691,17 @@ func TestCircle_IntersectPoints(t *testing.T) { Center: tt.fields.Center, Radius: tt.fields.Radius, } - if got := c.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Circle.IntersectPoints() = %v, want %v", got, tt.want) + got := c.IntersectionPoints(tt.args.l) + for i, v := range got { + if !closeEnough(v.X, tt.want[i].X, 2) || !closeEnough(v.Y, tt.want[i].Y, 2) { + t.Errorf("Circle.IntersectPoints() = %v, want %v", v, tt.want[i]) + } } }) } } func TestRect_IntersectCircle(t *testing.T) { - // closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them. - // Effectively this compares two floats to a given decimal point. - // Example: - // closeEnough(100.125342432, 100.125, 2) == true - // closeEnough(math.Pi, 3.14, 2) == true - // closeEnough(0.1234, 0.1245, 3) == false - closeEnough := func(got, expected float64, decimalAccuracy int) bool { - gotShifted := got * math.Pow10(decimalAccuracy) - expectedShifted := expected * math.Pow10(decimalAccuracy) - - return math.Trunc(gotShifted) == math.Trunc(expectedShifted) - } - type fields struct { Min pixel.Vec Max pixel.Vec From 966150a8568f2028348a67e7bc3447d9e1f6e8a4 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 15:13:24 +0100 Subject: [PATCH 112/156] Removing backticks --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 32b2467d..5a60a1b5 100644 --- a/geometry.go +++ b/geometry.go @@ -181,7 +181,7 @@ func Lerp(a, b Vec, t float64) Vec { return a.Scaled(1 - t).Add(b.Scaled(t)) } -// Line is a 2D line segment, between points `A` and `B`. +// Line is a 2D line segment, between points A and B. type Line struct { A, B Vec } From bcda85acd2a06bb4a23f59ae00d32ed23f5ba3c2 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 15:13:47 +0100 Subject: [PATCH 113/156] Adding fullstop at end of func comment --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 5a60a1b5..b332f25c 100644 --- a/geometry.go +++ b/geometry.go @@ -273,7 +273,7 @@ func (l Line) Closest(v Vec) Vec { return V(x, y) } -// Contains returns whether the provided Vec lies on the line +// Contains returns whether the provided Vec lies on the line. func (l Line) Contains(v Vec) bool { return l.Closest(v) == v } From 364a7a84ae0ebd83a14ed38839821836bcbaaf31 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 15:31:34 +0100 Subject: [PATCH 114/156] Prevented test results order mattering --- geometry_test.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/geometry_test.go b/geometry_test.go index 2f214a7b..ebabfa03 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -839,6 +839,16 @@ func TestRect_IntersectCircle(t *testing.T) { } func TestRect_IntersectionPoints(t *testing.T) { + in := func(v pixel.Vec, vs []pixel.Vec) bool { + for _, vec := range vs { + if vec == v { + return true + } + } + + return false + } + type fields struct { Min pixel.Vec Max pixel.Vec @@ -877,8 +887,14 @@ func TestRect_IntersectionPoints(t *testing.T) { Min: tt.fields.Min, Max: tt.fields.Max, } - if got := r.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Rect.IntersectPoints() = %v, want %v", got, tt.want) + got := r.IntersectionPoints(tt.args.l) + if len(got) != len(tt.want) { + t.Errorf("Rect.IntersectPoints() has incorrect length. Expected %d, got %d", len(tt.want), len(got)) + } + for _, v := range got { + if !in(v, tt.want) { + t.Errorf("Rect.IntersectPoints(): got unexpected result = %v", v) + } } }) } From 29b1220ec33da3573eb0dc6492cafc417b2ebf39 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 15:48:27 +0100 Subject: [PATCH 115/156] Setting order of returned points on Rect --- geometry.go | 8 +++++++- geometry_test.go | 20 ++------------------ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/geometry.go b/geometry.go index b332f25c..9b232bff 100644 --- a/geometry.go +++ b/geometry.go @@ -3,6 +3,7 @@ package pixel import ( "fmt" "math" + "sort" ) // Clamp returns x clamped to the interval [min, max]. @@ -610,7 +611,8 @@ func (r Rect) IntersectLine(l Line) Vec { } // IntersectionPoints returns all the points where the Rect intersects with the line provided. This can be zero, one or -// two points, depending on the location of the shapes. +// two points, depending on the location of the shapes. The points of intersection will be returned in order of +// closest-to-l.A to closest-to-l.B. func (r Rect) IntersectionPoints(l Line) []Vec { // Use map keys to ensure unique points pointMap := make(map[Vec]struct{}) @@ -625,6 +627,10 @@ func (r Rect) IntersectionPoints(l Line) []Vec { for point := range pointMap { points = append(points, point) } + + // Order the points + sort.Slice(points, func(i, j int) bool { return points[i].To(l.A).Len() < points[j].To(l.A).Len() }) + return points } diff --git a/geometry_test.go b/geometry_test.go index ebabfa03..2f214a7b 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -839,16 +839,6 @@ func TestRect_IntersectCircle(t *testing.T) { } func TestRect_IntersectionPoints(t *testing.T) { - in := func(v pixel.Vec, vs []pixel.Vec) bool { - for _, vec := range vs { - if vec == v { - return true - } - } - - return false - } - type fields struct { Min pixel.Vec Max pixel.Vec @@ -887,14 +877,8 @@ func TestRect_IntersectionPoints(t *testing.T) { Min: tt.fields.Min, Max: tt.fields.Max, } - got := r.IntersectionPoints(tt.args.l) - if len(got) != len(tt.want) { - t.Errorf("Rect.IntersectPoints() has incorrect length. Expected %d, got %d", len(tt.want), len(got)) - } - for _, v := range got { - if !in(v, tt.want) { - t.Errorf("Rect.IntersectPoints(): got unexpected result = %v", v) - } + if got := r.IntersectionPoints(tt.args.l); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Rect.IntersectPoints() = %v, want %v", got, tt.want) } }) } From b8bb00a1617e47b904589582f1d27aaeb2fae715 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 15:49:40 +0100 Subject: [PATCH 116/156] Setting order of returned points on Circle --- geometry.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index 9b232bff..83c06383 100644 --- a/geometry.go +++ b/geometry.go @@ -880,7 +880,8 @@ func (c Circle) IntersectRect(r Rect) Vec { } // IntersectionPoints returns all the points where the Circle intersects with the line provided. This can be zero, one or -// two points, depending on the location of the shapes. +// two points, depending on the location of the shapes. The points of intersection will be returned in order of +// closest-to-l.A to closest-to-l.B. func (c Circle) IntersectionPoints(l Line) []Vec { cContainsA := c.Contains(l.A) cContainsB := c.Contains(l.B) @@ -965,7 +966,10 @@ func (c Circle) IntersectionPoints(l Line) []Vec { first := closestToCenter.Add(closestToCenter.To(l.A).Unit().Scaled(a)) second := closestToCenter.Add(closestToCenter.To(l.B).Unit().Scaled(a)) - return []Vec{first, second} + points := []Vec{first, second} + sort.Slice(points, func(i, j int) bool { return points[i].To(l.A).Len() < points[j].To(l.A).Len() }) + + return points } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such From 0092d6a577c62a196bf82c2faaf14e0018ced58c Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 16:09:25 +0100 Subject: [PATCH 117/156] implementing pre 1.8 sortslice solution --- geometry.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/geometry.go b/geometry.go index 83c06383..e6652cd9 100644 --- a/geometry.go +++ b/geometry.go @@ -3,7 +3,6 @@ package pixel import ( "fmt" "math" - "sort" ) // Clamp returns x clamped to the interval [min, max]. @@ -629,7 +628,11 @@ func (r Rect) IntersectionPoints(l Line) []Vec { } // Order the points - sort.Slice(points, func(i, j int) bool { return points[i].To(l.A).Len() < points[j].To(l.A).Len() }) + if len(points) == 2 { + if points[1].To(l.A).Len() < points[0].To(l.A).Len() { + return []Vec{points[1], points[2]} + } + } return points } @@ -966,10 +969,10 @@ func (c Circle) IntersectionPoints(l Line) []Vec { first := closestToCenter.Add(closestToCenter.To(l.A).Unit().Scaled(a)) second := closestToCenter.Add(closestToCenter.To(l.B).Unit().Scaled(a)) - points := []Vec{first, second} - sort.Slice(points, func(i, j int) bool { return points[i].To(l.A).Len() < points[j].To(l.A).Len() }) - - return points + if first.To(l.A).Len() < second.To(l.A).Len() { + return []Vec{first, second} + } + return []Vec{second, first} } // Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such From 9eb2834a10eb6a0460c4ef5fe442317f8a7f6211 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Thu, 4 Apr 2019 16:20:21 +0100 Subject: [PATCH 118/156] fixed index issue --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index e6652cd9..8ccab8de 100644 --- a/geometry.go +++ b/geometry.go @@ -630,7 +630,7 @@ func (r Rect) IntersectionPoints(l Line) []Vec { // Order the points if len(points) == 2 { if points[1].To(l.A).Len() < points[0].To(l.A).Len() { - return []Vec{points[1], points[2]} + return []Vec{points[1], points[0]} } } From f698bae1dfd194d83bfdaaa99b4a58a3b2f46b98 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 10 Apr 2019 12:47:07 +0100 Subject: [PATCH 119/156] Added floating point round error correction --- geometry.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 8ccab8de..42fe0182 100644 --- a/geometry.go +++ b/geometry.go @@ -49,6 +49,33 @@ func V(x, y float64) Vec { return Vec{x, y} } +// nearlyEqual compares two float64s and returns whether they are equal, accounting for rounding errors.At worst, the +// result is correct to 7 significant digits. +func nearlyEqual(a, b float64) bool { + epsilon := 0.000001 + + if a == b { + return true + } + + diff := math.Abs(a - b) + + if a == 0.0 || b == 0.0 || diff < math.SmallestNonzeroFloat64 { + return diff < (epsilon * math.SmallestNonzeroFloat64) + } + + absA := math.Abs(a) + absB := math.Abs(b) + + return diff/math.Min(absA+absB, math.MaxFloat64) < epsilon +} + +// Eq will compare two vectors and return whether they are equal accounting for rounding errors. At worst, the result +// is correct to 7 significant digits. +func (u Vec) Eq(v Vec) bool { + return nearlyEqual(u.X, v.X) && nearlyEqual(u.Y, v.Y) +} + // Unit returns a vector of length 1 facing the given angle. func Unit(angle float64) Vec { return Vec{1, 0}.Rotated(angle) @@ -275,7 +302,7 @@ func (l Line) Closest(v Vec) Vec { // Contains returns whether the provided Vec lies on the line. func (l Line) Contains(v Vec) bool { - return l.Closest(v) == v + return l.Closest(v).Eq(v) } // Formula will return the values that represent the line in the formula: y = mx + b From 96e0a8f3bfa328c855349867a16c4a1fff869301 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 10 Apr 2019 12:47:21 +0100 Subject: [PATCH 120/156] Added test cases --- geometry_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/geometry_test.go b/geometry_test.go index 2f214a7b..32bd9f69 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1181,6 +1181,19 @@ func TestLine_Intersect(t *testing.T) { args: args{k: pixel.L(pixel.V(0, 1), pixel.V(10, 11))}, want: pixel.ZV, want1: false, + }, { + name: "Lines intersect", + fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)}, + args: args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255))}, + want: pixel.V(849.1666666666666, 255), + want1: true, + }, + { + name: "Lines intersect", + fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)}, + args: args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255.0001))}, + want: pixel.V(849.1666240490657, 255.000059008986), + want1: true, }, } for _, tt := range tests { From 95ac5e1e2ae3d0ad6586cac0fe2063e8736a01f0 Mon Sep 17 00:00:00 2001 From: Immueggpain Date: Sat, 13 Apr 2019 23:47:02 +0800 Subject: [PATCH 121/156] why 2 loops and assign twice? one is enough --- sprite.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sprite.go b/sprite.go index 08087596..b7e1d250 100644 --- a/sprite.go +++ b/sprite.go @@ -102,16 +102,11 @@ func (s *Sprite) calcData() { (*s.tri)[5].Position = Vec{}.Sub(horizontal).Add(vertical) for i := range *s.tri { + (*s.tri)[i].Position = s.matrix.Project((*s.tri)[i].Position) (*s.tri)[i].Color = s.mask (*s.tri)[i].Picture = center.Add((*s.tri)[i].Position) (*s.tri)[i].Intensity = 1 } - // matrix and mask - for i := range *s.tri { - (*s.tri)[i].Position = s.matrix.Project((*s.tri)[i].Position) - (*s.tri)[i].Color = s.mask - } - s.d.Dirty() } From d601bc65e423e3297858f955e66fc5ac5f66e4dd Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 15 Apr 2019 08:46:29 +0100 Subject: [PATCH 122/156] Setting position in correct order --- sprite.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sprite.go b/sprite.go index b7e1d250..017d9851 100644 --- a/sprite.go +++ b/sprite.go @@ -102,10 +102,10 @@ func (s *Sprite) calcData() { (*s.tri)[5].Position = Vec{}.Sub(horizontal).Add(vertical) for i := range *s.tri { - (*s.tri)[i].Position = s.matrix.Project((*s.tri)[i].Position) (*s.tri)[i].Color = s.mask (*s.tri)[i].Picture = center.Add((*s.tri)[i].Position) (*s.tri)[i].Intensity = 1 + (*s.tri)[i].Position = s.matrix.Project((*s.tri)[i].Position) } s.d.Dirty() From 85b18b567a43525d0ac46080d993bca907f0ce43 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 15 Apr 2019 11:20:40 +0100 Subject: [PATCH 123/156] prevented concurrent race condition --- pixelgl/gltriangles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go index 52ed0c99..f2581021 100644 --- a/pixelgl/gltriangles.go +++ b/pixelgl/gltriangles.go @@ -77,7 +77,7 @@ func (gt *GLTriangles) SetLen(length int) { default: return } - mainthread.CallNonBlock(func() { + mainthread.Call(func() { gt.vs.Begin() gt.vs.SetLen(length) gt.vs.End() From 9d5c7afe12e3027cd6a0d3d01b1f0c56741ae936 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Mon, 15 Apr 2019 11:25:30 +0100 Subject: [PATCH 124/156] Adding test for drawing sprites --- .travis.yml | 15 +++++++----- pixel_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 pixel_test.go diff --git a/.travis.yml b/.travis.yml index 32e3f026..10c1e2d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,17 @@ addons: - libxi-dev - libopenal-dev - libasound2-dev + - xvfb go: -- 1.8 -- 1.7.4 -- tip + - tip + - 1.8.x + - 1.7.4 install: -- go get -t ./... + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2&>1 & + - go get -t ./... script: -- go test -i -race ./... -- go test -v -race ./... + - go test -i -race ./... + - go test -v -race ./... diff --git a/pixel_test.go b/pixel_test.go new file mode 100644 index 00000000..8fa3fe84 --- /dev/null +++ b/pixel_test.go @@ -0,0 +1,67 @@ +package pixel_test + +import ( + "bytes" + "image" + "os" + "testing" + + _ "image/png" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" +) + +var ( + // onePixelImage is the byte representation of a 1x1 solid white png file + onePixelImage = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 2, + 0, 0, 0, 144, 119, 83, 222, 0, 0, 1, 130, 105, 67, 67, 80, 73, 67, 67, 32, 112, 114, 111, 102, 105, 108, 101, 0, + 0, 40, 145, 125, 145, 59, 72, 3, 65, 20, 69, 143, 73, 68, 17, 37, 133, 41, 68, 44, 182, 80, 43, 5, 81, 17, 75, + 141, 66, 16, 34, 132, 168, 96, 212, 194, 221, 141, 137, 66, 118, 13, 187, 9, 54, 150, 130, 109, 192, 194, 79, + 227, 175, 176, 177, 214, 214, 194, 86, 16, 4, 63, 32, 54, 182, 86, 138, 54, 18, 214, 55, 73, 32, 65, 140, 3, + 195, 28, 238, 188, 123, 121, 243, 6, 124, 71, 25, 211, 114, 3, 3, 96, 217, 57, 39, 30, 9, 107, 243, 137, 5, 173, + 233, 149, 0, 65, 90, 104, 0, 221, 116, 179, 227, 177, 88, 148, 186, 235, 235, 94, 213, 193, 93, 191, 202, 170, + 95, 247, 231, 106, 75, 174, 184, 38, 52, 104, 194, 99, 102, 214, 201, 9, 47, 11, 143, 108, 228, 178, 138, 247, + 132, 67, 230, 170, 158, 20, 62, 23, 238, 115, 164, 65, 225, 71, 165, 27, 101, 126, 83, 156, 46, 177, 79, 101, + 134, 156, 217, 248, 132, 112, 72, 88, 75, 215, 176, 81, 195, 230, 170, 99, 9, 15, 11, 119, 39, 45, 91, 242, 125, + 243, 101, 78, 42, 222, 84, 108, 101, 242, 102, 165, 79, 245, 194, 214, 21, 123, 110, 70, 233, 178, 187, 136, 48, + 197, 52, 49, 52, 12, 242, 172, 145, 33, 71, 191, 156, 182, 40, 46, 113, 185, 15, 215, 241, 119, 150, 252, 49, + 113, 25, 226, 90, 195, 20, 199, 36, 235, 88, 232, 37, 63, 234, 15, 126, 207, 214, 77, 13, 13, 150, 147, 90, 195, + 208, 248, 226, 121, 31, 61, 208, 180, 3, 197, 130, 231, 125, 31, 123, 94, 241, 4, 252, 207, 112, 101, 87, 253, + 235, 71, 48, 250, 41, 122, 161, 170, 117, 31, 66, 112, 11, 46, 174, 171, 154, 177, 11, 151, 219, 208, 241, 148, + 213, 29, 189, 36, 249, 101, 251, 82, 41, 120, 63, 147, 111, 74, 64, 251, 45, 180, 44, 150, 231, 86, 185, 231, + 244, 1, 102, 101, 86, 209, 27, 216, 63, 128, 222, 180, 100, 47, 213, 121, 119, 115, 237, 220, 254, 173, 169, + 204, 239, 7, 178, 211, 114, 90, 10, 150, 157, 65, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 46, 35, 0, 0, 46, 35, 1, + 120, 165, 63, 118, 0, 0, 0, 7, 116, 73, 77, 69, 7, 227, 4, 15, 10, 5, 36, 189, 4, 224, 88, 0, 0, 0, 25, 116, 69, + 88, 116, 67, 111, 109, 109, 101, 110, 116, 0, 67, 114, 101, 97, 116, 101, 100, 32, 119, 105, 116, 104, 32, 71, + 73, 77, 80, 87, 129, 14, 23, 0, 0, 0, 12, 73, 68, 65, 84, 8, 215, 99, 120, 241, 226, 61, 0, 5, 123, 2, 192, 194, + 77, 211, 95, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130} +) + +func TestMain(m *testing.M) { + pixelgl.Run(func() { + os.Exit(m.Run()) + }) +} + +func TestSprite_Draw(t *testing.T) { + img, _, err := image.Decode(bytes.NewReader(onePixelImage)) + if err != nil { + t.Fatalf("Could not decode image: %v", err) + } + pic := pixel.PictureDataFromImage(img) + + sprite := pixel.NewSprite(pic, pic.Bounds()) + + cfg := pixelgl.WindowConfig{ + Title: "testing", + Bounds: pixel.R(0, 0, 150, 150), + } + + win, err := pixelgl.NewWindow(cfg) + if err != nil { + t.Fatalf("Could not create window: %v", err) + } + + sprite.Draw(win, pixel.IM) +} From 3aad3268cdac7edfe4c4e601fd5c9b9865636fd1 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 16 Apr 2019 08:47:42 +0100 Subject: [PATCH 125/156] Making xvfb a service --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10c1e2d4..c45a292a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,15 +11,17 @@ addons: - libxi-dev - libopenal-dev - libasound2-dev - - xvfb + - libgl1-mesa-dev + +services: + - xvfb + go: - tip - 1.8.x - 1.7.4 install: - - export DISPLAY=':99.0' - - Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2&>1 & - go get -t ./... script: From 2cf81cd4df2be367fff640c749fb3398f365ffcc Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Tue, 16 Apr 2019 09:01:36 +0100 Subject: [PATCH 126/156] Setting dist --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c45a292a..e9613ac2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: go +# https://github.com/golang/go/issues/31293 +dist: xenial sudo: false addons: apt: From d48ef44bdc0c408df2b4e9fc05e9404bae64fac3 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 24 Apr 2019 10:11:20 +0100 Subject: [PATCH 127/156] Added TriangleData benchmarks and improvements --- data.go | 37 +++++--- data_test.go | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 13 deletions(-) create mode 100644 data_test.go diff --git a/data.go b/data.go index a3d0b26c..39794347 100644 --- a/data.go +++ b/data.go @@ -8,6 +8,16 @@ import ( "math" ) +var ( + // zeroValueTriangleData is the default value of a TriangleData element + zeroValueTriangleData = struct { + Position Vec + Color RGBA + Picture Vec + Intensity float64 + }{Color: RGBA{1, 1, 1, 1}} +) + // TrianglesData specifies a list of Triangles vertices with three common properties: // TrianglesPosition, TrianglesColor and TrianglesPicture. type TrianglesData []struct { @@ -22,9 +32,11 @@ type TrianglesData []struct { // Prefer this function to make(TrianglesData, len), because make zeros them, while this function // does the correct intialization. func MakeTrianglesData(len int) *TrianglesData { - td := &TrianglesData{} - td.SetLen(len) - return td + td := make(TrianglesData, len) + for i := 0; i < len; i++ { + td[i] = zeroValueTriangleData + } + return &td } // Len returns the number of vertices in TrianglesData. @@ -38,15 +50,15 @@ func (td *TrianglesData) Len() int { // values ((0, 0), white, (0, 0), 0). func (td *TrianglesData) SetLen(len int) { if len > td.Len() { + newTD := make(TrianglesData, len) + copy(newTD, *td) + needAppend := len - td.Len() - for i := 0; i < needAppend; i++ { - *td = append(*td, struct { - Position Vec - Color RGBA - Picture Vec - Intensity float64 - }{Color: RGBA{1, 1, 1, 1}}) + for i := td.Len(); i < needAppend; i++ { + newTD[i] = zeroValueTriangleData } + + *td = newTD } if len < td.Len() { *td = (*td)[:len] @@ -96,10 +108,9 @@ func (td *TrianglesData) Update(t Triangles) { // Copy returns an exact independent copy of this TrianglesData. func (td *TrianglesData) Copy() Triangles { - copyTd := TrianglesData{} - copyTd.SetLen(td.Len()) + copyTd := MakeTrianglesData(td.Len()) copyTd.Update(td) - return ©Td + return copyTd } // Position returns the position property of i-th vertex. diff --git a/data_test.go b/data_test.go new file mode 100644 index 00000000..7d6d8c35 --- /dev/null +++ b/data_test.go @@ -0,0 +1,244 @@ +package pixel_test + +import ( + "testing" + + "github.com/faiface/pixel" +) + +func BenchmarkTrianglesData_Len(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + }{ + { + name: "Small slice", + tData: pixel.MakeTrianglesData(10), + }, + { + name: "Large slice", + tData: pixel.MakeTrianglesData(10000), + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = tt.tData.Len() + } + }) + } +} + +func BenchmarkTrianglesData_SetLen(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + nextLenFunc func(int, int) (int, int) + }{ + { + name: "Stay same size", + tData: pixel.MakeTrianglesData(50), + nextLenFunc: func(i, j int) (int, int) { return 50, 0 }, + }, + { + name: "Change size", + tData: pixel.MakeTrianglesData(50), + nextLenFunc: func(i, j int) (int, int) { + // 0 is shrink + if j == 0 { + next := i - 1 + if next < 1 { + return 2, 1 + } + return next, 0 + } + + // other than 0 is grow + next := i + 1 + if next == 100 { + return next, 0 + } + return next, 1 + }, + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + var newLen int + var c int + for i := 0; i < b.N; i++ { + newLen, c = tt.nextLenFunc(newLen, c) + tt.tData.SetLen(newLen) + } + }) + } +} + +func BenchmarkTrianglesData_Slice(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + }{ + { + name: "Basic slice", + tData: pixel.MakeTrianglesData(100), + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = tt.tData.Slice(25, 50) + } + }) + } +} + +func BenchmarkTrianglesData_Update(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + t pixel.Triangles + }{ + { + name: "Small Triangles", + tData: pixel.MakeTrianglesData(20), + t: pixel.MakeTrianglesData(20), + }, + { + name: "Large Triangles", + tData: pixel.MakeTrianglesData(10000), + t: pixel.MakeTrianglesData(10000), + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + tt.tData.Update(tt.t) + } + }) + } +} + +func BenchmarkTrianglesData_Copy(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + }{ + { + name: "Small copy", + tData: pixel.MakeTrianglesData(20), + }, + { + name: "Large copy", + tData: pixel.MakeTrianglesData(10000), + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = tt.tData.Copy() + } + }) + } +} + +func BenchmarkTrianglesData_Position(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + position int + }{ + { + name: "Getting beginning position", + tData: pixel.MakeTrianglesData(1000), + position: 2, + }, + { + name: "Getting middle position", + tData: pixel.MakeTrianglesData(1000), + position: 500, + }, + { + name: "Getting end position", + tData: pixel.MakeTrianglesData(1000), + position: 999, + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = tt.tData.Position(tt.position) + } + }) + } +} + +func BenchmarkTrianglesData_Color(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + position int + }{ + { + name: "Getting beginning position", + tData: pixel.MakeTrianglesData(1000), + position: 2, + }, + { + name: "Getting middle position", + tData: pixel.MakeTrianglesData(1000), + position: 500, + }, + { + name: "Getting end position", + tData: pixel.MakeTrianglesData(1000), + position: 999, + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = tt.tData.Color(tt.position) + } + }) + } +} + +func BenchmarkTrianglesData_Picture(b *testing.B) { + tests := []struct { + name string + tData *pixel.TrianglesData + position int + }{ + { + name: "Getting beginning position", + tData: pixel.MakeTrianglesData(1000), + position: 2, + }, + { + name: "Getting middle position", + tData: pixel.MakeTrianglesData(1000), + position: 500, + }, + { + name: "Getting end position", + tData: pixel.MakeTrianglesData(1000), + position: 999, + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = tt.tData.Picture(tt.position) + } + }) + } +} From 159df28dcc8d32b13345f83170b0447ed7b0bea4 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 24 Apr 2019 10:32:35 +0100 Subject: [PATCH 128/156] Reverted changes on setlen --- data.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/data.go b/data.go index 39794347..a1959697 100644 --- a/data.go +++ b/data.go @@ -50,15 +50,10 @@ func (td *TrianglesData) Len() int { // values ((0, 0), white, (0, 0), 0). func (td *TrianglesData) SetLen(len int) { if len > td.Len() { - newTD := make(TrianglesData, len) - copy(newTD, *td) - needAppend := len - td.Len() - for i := td.Len(); i < needAppend; i++ { - newTD[i] = zeroValueTriangleData + for i := 0; i < needAppend; i++ { + *td = append(*td, zeroValueTriangleData) } - - *td = newTD } if len < td.Len() { *td = (*td)[:len] From 9e6e6197dd09fed3c2ab43844e358fd81efd3e25 Mon Sep 17 00:00:00 2001 From: Ben Cragg Date: Wed, 24 Apr 2019 11:17:48 +0100 Subject: [PATCH 129/156] Added benchmark for MakeTrianglesData --- data_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/data_test.go b/data_test.go index 7d6d8c35..e27bd948 100644 --- a/data_test.go +++ b/data_test.go @@ -6,6 +6,30 @@ import ( "github.com/faiface/pixel" ) +func BenchmarkMakeTrianglesData(b *testing.B) { + tests := []struct { + name string + len int + }{ + { + name: "Small slice", + len: 10, + }, + { + name: "Large slice", + len: 10000, + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = pixel.MakeTrianglesData(tt.len) + } + }) + } +} + func BenchmarkTrianglesData_Len(b *testing.B) { tests := []struct { name string From 1f96c1e995343fdb0ea77015604bec8d5d6e1b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sun, 19 May 2019 18:44:51 +0200 Subject: [PATCH 130/156] Fixed link to examples repo in CONTRIBUTING.md --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e5d4812..e41131ef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ ## Here are a few ways you can contribute -1. **Make a community example** and place it inside the [examples/community](examples/community) folder. +1. **Make a community example** and place it inside the `community` folder of the [examples repository][examples]. 2. **Add tests**. There only few tests in Pixel at the moment. Take a look at them and make some similar. 3. **Add a small feature or an improvement**. Feel like some small feature is missing? Just make a PR. Be ready that I might reject it, though, if I don't find it particularly appealing. 4. **Join the big development** by joining the discussion at the [Gitter](https://gitter.im/pixellib/Lobby), where we can discuss bigger changes and implement them after that. @@ -12,3 +12,5 @@ ## How to make a pull request Go gives you a nice surprise when attempting to make a PR on Github. The thing is, that when user _xyz_ forks Pixel on Github, it ends up in _github.com/xyz/pixel_, which fucks up your import paths. Here's how you deal with that: https://www.reddit.com/r/golang/comments/2jdcw1/how_do_you_deal_with_github_forking/. + +[examples]: https://github.com/faiface/pixel-examples/tree/master/community From b18647916faf5feb9efd0a54facd2dbe8a4e9892 Mon Sep 17 00:00:00 2001 From: Tsukinai Date: Fri, 24 May 2019 12:00:11 -0600 Subject: [PATCH 131/156] Added ZR for zero rect Added the ZR for rectangle for both utility and consistensy with pixel.ZV --- geometry.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/geometry.go b/geometry.go index 42fe0182..0c16b996 100644 --- a/geometry.go +++ b/geometry.go @@ -479,6 +479,9 @@ type Rect struct { Min, Max Vec } +// ZR is a zero rectangle. +var ZR = Rect{Min:ZV, Max:ZV} + // R returns a new Rect with given the Min and Max coordinates. // // Note that the returned rectangle is not automatically normalized. From 285676ca1767e3b095f3ce3b368b3059d57d4100 Mon Sep 17 00:00:00 2001 From: Tsukinai Date: Fri, 24 May 2019 12:20:00 -0600 Subject: [PATCH 132/156] gofmt and fixed docs Fixed doc refrince to R(0,0,0,0) in Rect.Intersect() to be a ZR and for it to return a ZR. Also ran gofmt... --- geometry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/geometry.go b/geometry.go index 0c16b996..1933f322 100644 --- a/geometry.go +++ b/geometry.go @@ -480,7 +480,7 @@ type Rect struct { } // ZR is a zero rectangle. -var ZR = Rect{Min:ZV, Max:ZV} +var ZR = Rect{Min: ZV, Max: ZV} // R returns a new Rect with given the Min and Max coordinates. // @@ -608,7 +608,7 @@ func (r Rect) Union(s Rect) Rect { // Intersect returns the maximal Rect which is covered by both r and s. Rects r and s must be normalized. // -// If r and s don't overlap, this function returns R(0, 0, 0, 0). +// If r and s don't overlap, this function returns a ZR. func (r Rect) Intersect(s Rect) Rect { t := R( math.Max(r.Min.X, s.Min.X), @@ -617,7 +617,7 @@ func (r Rect) Intersect(s Rect) Rect { math.Min(r.Max.Y, s.Max.Y), ) if t.Min.X >= t.Max.X || t.Min.Y >= t.Max.Y { - return Rect{} + return ZR } return t } From ba6fc7db8a30dd11acba29423ed661dd4425bcd9 Mon Sep 17 00:00:00 2001 From: Tsukinai Date: Fri, 24 May 2019 12:25:35 -0600 Subject: [PATCH 133/156] docs changes changed wording to match zero-vector returns in other functions. --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 1933f322..f3709c2e 100644 --- a/geometry.go +++ b/geometry.go @@ -608,7 +608,7 @@ func (r Rect) Union(s Rect) Rect { // Intersect returns the maximal Rect which is covered by both r and s. Rects r and s must be normalized. // -// If r and s don't overlap, this function returns a ZR. +// If r and s don't overlap, this function returns a zero-rectangle. func (r Rect) Intersect(s Rect) Rect { t := R( math.Max(r.Min.X, s.Min.X), From 38283d0e55363cd13407a605ae32a1e3bc04d07b Mon Sep 17 00:00:00 2001 From: udhos Date: Fri, 24 May 2019 17:37:22 -0300 Subject: [PATCH 134/156] Support for Modules. --- README.md | 8 ++++++++ go.mod | 14 ++++++++++++++ go.sum | 23 +++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/README.md b/README.md index 0e5b038e..6a2f2cc4 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ do. go get github.com/faiface/pixel ``` +If you are using Modules (Go 1.11 or higher) and want a mutable copy of the source code: + +``` +git clone https://github.com/faiface/pixel ;# clone outside of GOPATH +cd pixel +go install ./... +``` + See [requirements](#requirements) for the list of libraries necessary for compilation. ## Tutorial diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..30f836cd --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/faiface/pixel + +go 1.12 + +require ( + github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 + github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 + github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect + github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 + github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 + github.com/pkg/errors v0.8.1 + github.com/stretchr/testify v1.3.0 + golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..f4069083 --- /dev/null +++ b/go.sum @@ -0,0 +1,23 @@ +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/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= +github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= +github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI= +github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff h1:+2zgJKVDVAz/BWSsuniCmU1kLCjL88Z8/kv39xCI9NQ= +golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From dd5e0d8b09c31278f1c675acc515ca6f83a1b340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sun, 19 May 2019 14:30:35 +0200 Subject: [PATCH 135/156] Adding NoIconify and AlwaysOnTop GLFW window hints --- pixelgl/window.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pixelgl/window.go b/pixelgl/window.go index 8e652776..10c16ce1 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -46,6 +46,16 @@ type WindowConfig struct { // Undecorated Window ommits the borders and decorations (close button, etc.). Undecorated bool + // NoIconify specifies whether fullscreen windows should not automatically + // iconify (and restore the previous video mode) on focus loss. + NoIconify bool + + // AlwaysOnTop specifies whether the windowed mode window will be floating + // above other regular windows, also called topmost or always-on-top. + // This is intended primarily for debugging purposes and cannot be used to + // implement proper full screen windows. + AlwaysOnTop bool + // VSync (vertical synchronization) synchronizes Window's framerate with the framerate of // the monitor. VSync bool @@ -100,6 +110,8 @@ func NewWindow(cfg WindowConfig) (*Window, error) { glfw.WindowHint(glfw.Resizable, bool2int[cfg.Resizable]) glfw.WindowHint(glfw.Decorated, bool2int[!cfg.Undecorated]) + glfw.WindowHint(glfw.Floating, bool2int[cfg.AlwaysOnTop]) + glfw.WindowHint(glfw.AutoIconify, bool2int[!cfg.NoIconify]) var share *glfw.Window if currWin != nil { From 23171aea6cc878d38230422c3c6084a1678fa4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Thu, 30 May 2019 14:36:11 +0200 Subject: [PATCH 136/156] Add missing github.com/golang/freetype dependency --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 30f836cd..c9d5ef24 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff diff --git a/go.sum b/go.sum index f4069083..20d14f98 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI= github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= +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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= From 109cb03faea0bcfeab3fa23e261d349c2c15f204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Thu, 30 May 2019 14:47:19 +0200 Subject: [PATCH 137/156] Update TravisCI to fail if dependencies are missing in go.mod --- .travis.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index e9613ac2..9b9d7744 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,12 +20,7 @@ services: go: - tip - - 1.8.x - - 1.7.4 - -install: - - go get -t ./... + - 1.12.x script: - - go test -i -race ./... - - go test -v -race ./... + - go test -v -race -mod=readonly ./... From c5c5127c8da65814a0805c1cf04ca167ced2fff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Thu, 30 May 2019 14:52:32 +0200 Subject: [PATCH 138/156] Fix small typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a2f2cc4..8eae673b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ go get github.com/faiface/pixel If you are using Modules (Go 1.11 or higher) and want a mutable copy of the source code: ``` -git clone https://github.com/faiface/pixel ;# clone outside of GOPATH +git clone https://github.com/faiface/pixel # clone outside of $GOPATH cd pixel go install ./... ``` From d513ffd1925bbb6d8f44c6d8576ac6e5ab740c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Thu, 30 May 2019 14:56:19 +0200 Subject: [PATCH 139/156] Force TravisCI to run tests using dependencies from the go.mod file --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9b9d7744..f5413400 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,9 @@ addons: services: - xvfb +env: + - GO111MODULE=on + go: - tip - 1.12.x From a5c3bec92889cd6cd50008ca32680ceaa7fde430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Thu, 30 May 2019 15:23:12 +0200 Subject: [PATCH 140/156] Prevent default TravisCI install action --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index f5413400..ca39e278 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,5 +25,10 @@ go: - tip - 1.12.x +install: + - # Do nothing. This is needed to prevent the default install action + # "go get -t -v ./..." from happening here because we want it to happen + # inside script step. + script: - go test -v -race -mod=readonly ./... From 8a9191c2838319c3b1f002ee5e1a56ee9628b27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sun, 23 Jun 2019 11:12:53 +0200 Subject: [PATCH 141/156] Start to maintain a change log --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 31 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f3762f93 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +- Add more examples +- Add position as out variable from vertex shader +- Add experimental joystick support +- Add mouse cursor operations +- Add `Vec.Floor(…)` function +- Add circle geometry +- Fix `Matrix.Unproject(…)` for rotated matrix +- Add 2D Line geometry +- Add floating point round error correction +- Performance improvements +- Fix race condition in `NewGLTriangles(…)` +- Add `TriangleData` benchmarks and improvements +- Add zero rectangle variable for utility and consistency +- Add support for Go Modules +- Add `NoIconify` and `AlwaysOnTop` window hints + +## [v0.8.0] - 2018-10-10 +Changelog for this and older versions can be found on the corresponding [GitHub +releases](https://github.com/faiface/pixel/releases). + +[Unreleased]: https://github.com/faiface/pixel/compare/v0.8.0...HEAD +[v0.8.0]: https://github.com/faiface/pixel/releases/tag/v0.8.0 diff --git a/README.md b/README.md index 8eae673b..e6b1142f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ go install ./... See [requirements](#requirements) for the list of libraries necessary for compilation. +All significant (e.g. breaking) changes are documented in the [CHANGELOG.md](CHANGELOG.md). + ## Tutorial The [Wiki of this repo](https://github.com/faiface/pixel/wiki) contains an extensive tutorial From 2aa6d2d212722e838f4e92e6802df351403d0941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sat, 29 Jun 2019 14:38:59 +0200 Subject: [PATCH 142/156] Update wording about tracked changes in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6b1142f..a949c2ec 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ go install ./... See [requirements](#requirements) for the list of libraries necessary for compilation. -All significant (e.g. breaking) changes are documented in the [CHANGELOG.md](CHANGELOG.md). +All significant changes are documented in [CHANGELOG.md](CHANGELOG.md). ## Tutorial From f24d4658080d4b1e5205df96cb7e59a1428cb647 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Thu, 24 Oct 2019 10:29:11 +0200 Subject: [PATCH 143/156] Fixed a couple of errors in methods comments --- geometry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index f3709c2e..5cbb9065 100644 --- a/geometry.go +++ b/geometry.go @@ -622,7 +622,7 @@ func (r Rect) Intersect(s Rect) Rect { return t } -// IntersectCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// IntersectCircle returns a minimal required Vector, such that moving the rect by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // @@ -830,7 +830,7 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectLine will return the shortest Vec such that if the Rect is moved by the Vec returned, the Line and Rect no +// IntersectLine will return the shortest Vec such that if the Circle is moved by the Vec returned, the Line and Rect no // longer intersect. func (c Circle) IntersectLine(l Line) Vec { return l.IntersectCircle(c).Scaled(-1) From 706becb764d577a4264071fe1eaf23009bdccfbc Mon Sep 17 00:00:00 2001 From: Tskken Date: Tue, 5 Nov 2019 16:14:36 -0700 Subject: [PATCH 144/156] Update adding new Rect.Intersects Method This addes a new Rect.Intersects method which is around 5x faster then Rect.Intersect when used for basic collision checks. --- geometry.go | 11 +++++++++++ geometry_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/geometry.go b/geometry.go index f3709c2e..f91fbd71 100644 --- a/geometry.go +++ b/geometry.go @@ -622,6 +622,17 @@ func (r Rect) Intersect(s Rect) Rect { return t } +// Intersects returns whether or not the given Rect intersects at any point with this Rect. +// +// This function is overall about 5x faster then Intersect, so it is better +// to use if you have no need for the returned Rect from Intersect. +func (r Rect) Intersects(s Rect) bool { + return !(s.Max.X < r.Min.X || + s.Min.X > r.Max.X || + s.Max.Y < r.Min.Y || + s.Min.Y > r.Max.Y) +} + // IntersectCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. diff --git a/geometry_test.go b/geometry_test.go index 32bd9f69..82202c94 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1555,3 +1555,29 @@ func TestLine_String(t *testing.T) { }) } } + +func BenchmarkRect_Intersect(b *testing.B) { + root := pixel.R(10, 10, 50, 50) + inter := pixel.R(11, 11, 15, 15) + + for i := 0; i < b.N; i++ { + if root.Intersect(inter) != pixel.ZR { + // do a thing + } + + // do a thing + } +} + +func BenchmarkRect_IsIntersect(b *testing.B) { + root := pixel.R(10, 10, 50, 50) + inter := pixel.R(11, 11, 15, 15) + + for i := 0; i < b.N; i++ { + if root.IsIntersect(inter) { + // do a thing + } + + // do a thing + } +} From 837a4efa806866ade0a0d20766d5b290aa4932a0 Mon Sep 17 00:00:00 2001 From: Tskken Date: Tue, 5 Nov 2019 16:16:29 -0700 Subject: [PATCH 145/156] Fixed benchmark error Fixed error for forgeting to change benchark function from IsIntersect to Intersects. --- geometry_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry_test.go b/geometry_test.go index 82202c94..e3af737a 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -1574,7 +1574,7 @@ func BenchmarkRect_IsIntersect(b *testing.B) { inter := pixel.R(11, 11, 15, 15) for i := 0; i < b.N; i++ { - if root.IsIntersect(inter) { + if root.Intersects(inter) { // do a thing } From 8d0c306de91bbeab0c12e43c965733d260bfcfd2 Mon Sep 17 00:00:00 2001 From: Tskken Date: Tue, 5 Nov 2019 16:32:55 -0700 Subject: [PATCH 146/156] typo fix fixed typo in comments. --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index f91fbd71..c2bbe6e2 100644 --- a/geometry.go +++ b/geometry.go @@ -624,7 +624,7 @@ func (r Rect) Intersect(s Rect) Rect { // Intersects returns whether or not the given Rect intersects at any point with this Rect. // -// This function is overall about 5x faster then Intersect, so it is better +// This function is overall about 5x faster than Intersect, so it is better // to use if you have no need for the returned Rect from Intersect. func (r Rect) Intersects(s Rect) bool { return !(s.Max.X < r.Min.X || From 0d44d2cc9b1a686b87abc4feea93f3f0cf7132d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0trba?= Date: Wed, 6 Nov 2019 00:46:26 +0100 Subject: [PATCH 147/156] Revert "Fixed a couple of errors in methods comments" --- geometry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometry.go b/geometry.go index 5cbfb6b1..c2bbe6e2 100644 --- a/geometry.go +++ b/geometry.go @@ -841,7 +841,7 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectLine will return the shortest Vec such that if the Circle is moved by the Vec returned, the Line and Rect no +// IntersectLine will return the shortest Vec such that if the Rect is moved by the Vec returned, the Line and Rect no // longer intersect. func (c Circle) IntersectLine(l Line) Vec { return l.IntersectCircle(c).Scaled(-1) From e51d4a6676fa48c83b5ea703cb5b044e2967cb83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0trba?= Date: Wed, 6 Nov 2019 00:50:48 +0100 Subject: [PATCH 148/156] fix some typos --- geometry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geometry.go b/geometry.go index c2bbe6e2..2d902dc5 100644 --- a/geometry.go +++ b/geometry.go @@ -633,7 +633,7 @@ func (r Rect) Intersects(s Rect) bool { s.Min.Y > r.Max.Y) } -// IntersectCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle +// IntersectCircle returns a minimal required Vector, such that moving the rect by that vector would stop the Circle // and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only // the perimeters touch. // @@ -841,7 +841,7 @@ func (c Circle) Intersect(d Circle) Circle { } } -// IntersectLine will return the shortest Vec such that if the Rect is moved by the Vec returned, the Line and Rect no +// IntersectLine will return the shortest Vec such that if the Circle is moved by the Vec returned, the Line and Rect no // longer intersect. func (c Circle) IntersectLine(l Line) Vec { return l.IntersectCircle(c).Scaled(-1) From effd43bc5671009de80fb3e9cec76b1ec33201cd Mon Sep 17 00:00:00 2001 From: Luke Meyers Date: Sun, 9 Feb 2020 20:59:59 -0800 Subject: [PATCH 149/156] Expose pixelgl.Window.SwapBuffers Allow buffers to be swapped without polling input, offering decoupling symmetrical with that provided by UpdateInput. --- pixelgl/window.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pixelgl/window.go b/pixelgl/window.go index 10c16ce1..239bd59b 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -173,6 +173,13 @@ func (w *Window) Destroy() { // Update swaps buffers and polls events. Call this method at the end of each frame. func (w *Window) Update() { + w.SwapBuffers() + w.UpdateInput() +} + +// SwapBuffers swaps buffers. Call this to swap buffers without polling window events. +// Note that Update invokes SwapBuffers. +func (w *Window) SwapBuffers() { mainthread.Call(func() { _, _, oldW, oldH := intBounds(w.bounds) newW, newH := w.window.GetSize() @@ -207,8 +214,6 @@ func (w *Window) Update() { w.window.SwapBuffers() w.end() }) - - w.UpdateInput() } // SetClosed sets the closed flag of the Window. From 73ce868aa920575fddd1fbbf90584980098d7658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0trba?= Date: Tue, 14 Apr 2020 19:05:59 +0200 Subject: [PATCH 150/156] looking for a new maintainer --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a949c2ec..d67a6172 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# [Looking for a new maintainer!](https://github.com/faiface/pixel/issues/225) +

# Pixel [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From 9cc1cddcf2eb218f036d62b55abcbcc59ac313fa Mon Sep 17 00:00:00 2001 From: "Alex R. Delp" Date: Sat, 18 Apr 2020 15:41:05 -0700 Subject: [PATCH 151/156] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d67a6172..b1ce2972 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ See [requirements](#requirements) for the list of libraries necessary for compil All significant changes are documented in [CHANGELOG.md](CHANGELOG.md). +Join the [Pixel Discord Server](https://discord.gg/q2DK4MP) to discuss its development. + ## Tutorial The [Wiki of this repo](https://github.com/faiface/pixel/wiki) contains an extensive tutorial From ed63c18bb43a61355544fde2056888dbf002412c Mon Sep 17 00:00:00 2001 From: "Alex R. Delp" Date: Mon, 20 Apr 2020 10:13:31 -0700 Subject: [PATCH 152/156] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index b1ce2972..abe2499c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# [Looking for a new maintainer!](https://github.com/faiface/pixel/issues/225) -

# Pixel [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From fc543042cca03ba2cf03aefd7a76d059df430105 Mon Sep 17 00:00:00 2001 From: "Alex R. Delp" Date: Mon, 20 Apr 2020 10:23:01 -0700 Subject: [PATCH 153/156] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abe2499c..050d643e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-# Pixel [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# Pixel [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Discord Chat](https://img.shields.io/discord/308323056592486420.svg)](https://discord.gg/q2DK4MP) A hand-crafted 2D game library in Go. Take a look into the [features](#features) to see what it can From dbdea628da665501149948f337b87fc6e1ea194f Mon Sep 17 00:00:00 2001 From: "Alex R. Delp" Date: Mon, 20 Apr 2020 10:31:12 -0700 Subject: [PATCH 154/156] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 050d643e..c6269d69 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@

-# Pixel [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Discord Chat](https://img.shields.io/discord/308323056592486420.svg)](https://discord.gg/q2DK4MP) - +# Pixel [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Discord Chat](https://img.shields.io/discord/699679031603494954)](https://discord.gg/q2DK4MP) A hand-crafted 2D game library in Go. Take a look into the [features](#features) to see what it can do. From 8012403b8f8a4878e80a5f8601ccb34648518d98 Mon Sep 17 00:00:00 2001 From: "Alex R. Delp" Date: Mon, 20 Apr 2020 10:48:12 -0700 Subject: [PATCH 155/156] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c6269d69..c586affd 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,6 @@ See [requirements](#requirements) for the list of libraries necessary for compil All significant changes are documented in [CHANGELOG.md](CHANGELOG.md). -Join the [Pixel Discord Server](https://discord.gg/q2DK4MP) to discuss its development. - ## Tutorial The [Wiki of this repo](https://github.com/faiface/pixel/wiki) contains an extensive tutorial From 37f588845a3d1193df2a19c818eb0808d084498b Mon Sep 17 00:00:00 2001 From: "Alex R. Delp" Date: Sat, 2 May 2020 12:19:32 -0700 Subject: [PATCH 156/156] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3762f93..76aa5784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Upgrade to GLFW 3.3 - Add more examples - Add position as out variable from vertex shader - Add experimental joystick support