Compare commits

...

317 Commits

Author SHA1 Message Date
Allen Ray 0a251bc08b
Merge pull request #319 from duysqubix/master
Update README.md
2023-10-09 09:06:08 -04:00
Anten Skrabec d3c72ae210
Merge pull request #320 from duysqubix/patch-1
Update README.md
2023-10-09 01:05:26 -06:00
duysqubix 20c056b950
Update README.md 2023-10-08 19:51:40 -05:00
duysqubix 9ad345b415
Update README.md
updated link to point to gopxl/pixel repo
2023-10-07 07:07:29 -05:00
Allen Ray 174abb179a
Merge pull request #318 from duysqubix/notice 2023-10-06 10:11:23 -04:00
duysqubix 4c172faca7 updated readme with notice 2023-10-04 11:48:58 -05:00
Allen Ray d899a6bbed
Merge pull request #312 from faiface/dependabot/go_modules/golang.org/x/image-0.5.0
Bump golang.org/x/image from 0.0.0-20210628002857-a66eb6448b8d to 0.5.0
2023-03-10 09:11:16 -05:00
Allen Ray a9abe2ebb7
Merge pull request #313 from marc921/add-vec-squared-len
feat(Vec): add squared len method
2023-03-10 09:11:03 -05:00
Marc Brun ab135a4965 feat(Vec): add squared len method 2023-03-09 22:43:37 +01:00
dependabot[bot] 08fc8acdfa
Bump golang.org/x/image from 0.0.0-20210628002857-a66eb6448b8d to 0.5.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.0.0-20210628002857-a66eb6448b8d to 0.5.0.
- [Release notes](https://github.com/golang/image/releases)
- [Commits](https://github.com/golang/image/commits/v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-07 03:27:23 +00:00
Anten Skrabec 069cc4e011
Merge pull request #308 from miluChen/patch-2
fix typo in interface.go
2022-06-27 03:22:05 -06:00
miluChen 2c3a9a03bd
fix typo 2022-06-26 16:42:25 -04:00
miluChen cb394bc26f
fix typo in interface.go 2022-06-26 10:45:46 -04:00
Allen Ray 8acf4e5195
Merge pull request #301 from cebarks/mod-update
update dependencies (testify and go-gl/glfw)
2021-10-13 21:17:34 -04:00
Anten Skrabec ccc8deb923 update dependencies (testify and go-gl/glfw) 2021-10-13 11:40:21 -06:00
Allen Ray e448943963
Merge pull request #299 from Benjyclay/fix/failing-builds
Fix failing builds
2021-10-13 09:45:17 -04:00
Ben 8ce328cb89 Update dependencies, including pixel/glhf to fix failing builds
Closes #229
Closes #295
2021-10-13 14:43:44 +01:00
Allen Ray 389317124e
Merge pull request #297 from zergon321/add-sprite-uncached
Added an opportunity to disable caching for sprites
2021-10-04 18:50:28 -04:00
zergon321 33024feabf Cached parameter added. 2021-10-01 18:31:18 +00:00
Allen Ray d119f130f6
Added PixelUI to related repositories. 2021-09-02 12:17:40 -04:00
Allen Ray b61f150701
Merge pull request #285 from cebarks/msaa
Add Multisample Anti-aliasing support
2021-08-31 08:36:26 -04:00
Allen Ray f079cc25fe
Update window.go 2021-08-31 08:35:57 -04:00
Allen Ray 9e8e09f1d7
Merge pull request #284 from dusk125/clipTris
Adding new triangles type: TrianglesClipped
2021-08-31 08:34:40 -04:00
Allen Ray 96863238e9
Merge pull request #291 from GGCristo/master
[README] Add "libXxf86vm-devel" dependency for CentOS/Fedora-like Linux
2021-08-31 08:34:28 -04:00
GGCristo 649411a23c [README] Add "libXxf86vm-devel" dependency for CentOS/Fedora-like Linux
distributions
2021-08-31 01:27:50 +01:00
Allen Ray d940a42454
Merge pull request #290 from dusk125/master
Fixing #277: Rect.Intersect and Rect.Intersects now have consistent behavior
2021-08-18 20:06:47 -04:00
Anten Skrabec 160e665d51 update CHANGELOG.md 2021-08-17 23:27:19 -06:00
Anten Skrabec 1d936eae7b add comment about power of two setting for msaa samples 2021-08-17 23:23:32 -06:00
Allen Ray 15972155d3 Fixing #277 2021-08-16 23:51:13 -04:00
Allen Ray 4964768d4e
Merge pull request #287 from cebarks/geometry-refactor
Break geometry.go & geometry_test.go into multiple smaller files
2021-08-16 23:30:59 -04:00
Anten Skrabec d5761bda94 make testing window invisible 2021-08-16 19:12:38 -06:00
Anten Skrabec 552871e324 update from master 2021-08-16 19:12:25 -06:00
Anten Skrabec 4a8b06746f actually add the files -.- 2021-08-16 19:10:10 -06:00
Anten Skrabec 7fe32e1553 break out geometry into their own files 2021-08-16 19:10:10 -06:00
Allen Ray 6c717101a7
Merge branch 'master' into clipTris 2021-08-16 09:55:30 -04:00
Allen Ray 881bfeda91
Merge pull request #288 from sudoless/master
Apply gofumpt and style changes + Optimization in GLShader.Update()
2021-08-16 09:41:10 -04:00
Allen Ray b4d312046c
Merge pull request #289 from cebarks/spriteplus
Add 2 related repos
2021-08-16 08:55:59 -04:00
Anten Skrabec f6e2f3a8d8 add check for valid msaa values in NewWindow() 2021-08-15 23:31:06 -06:00
Anten Skrabec 4ec3c64512 add spriteplus and pixelutils to README.md under "Related repos" 2021-08-15 23:12:03 -06:00
Alexandru-Paul Copil bb0a0f485e Optimize *GLShader.Update() by allocating array before for loop 2021-07-09 10:44:48 +03:00
Alexandru-Paul Copil aae9927b58 Apply gofumpt and basic styling changes 2021-07-09 10:43:10 +03:00
Allen Ray 9307280c91
Merge pull request #283 from dusk125/master 2021-06-22 07:49:51 -04:00
Anten Skrabec 7615c055cf move gl.Enable call to inside gl context in window creation 2021-06-21 14:13:43 -06:00
Anten Skrabec 80b107c039 add multisampling 2021-06-21 13:51:26 -06:00
Allen Ray 98f3e740e6
Merge pull request #272 from jstewart7/master
Added input event tracking to prevent missed inputs
2021-06-21 15:07:48 -04:00
Allen Ray 798c6c2a48 Adding new triangles type: TrianglesClipped 2021-06-21 10:29:53 -04:00
Allen Ray 063b8952f9 Adding Clipboard get/set functions to window 2021-06-21 08:42:27 -04:00
Alex R. Delp 842ae8d470 Add some unit tests to a few geometry functions. 2021-01-29 08:41:37 -08:00
Jacob Stewart b31c294d4d Added input event tracking to prevent missed inputs 2021-01-28 20:13:28 -05:00
Alex R. Delp 75e7b4120b
Update CHANGELOG.md 2021-01-26 17:42:25 -08:00
Alex R. Delp 0e9102e224
Update CHANGELOG.md 2021-01-26 17:41:59 -08:00
Alex R. Delp 2101dc89e3
Merge pull request #252 from roipoussiere/anchor
Add AnchorPos struct and functions
2021-01-26 17:41:24 -08:00
Alex R. Delp eae5a2e96b
Update CHANGELOG.md 2021-01-26 17:31:32 -08:00
Alex R. Delp 24ffdadbf8
Merge pull request #258 from crthpl/master
Clipboard Support
2021-01-26 17:30:48 -08:00
Alex R. Delp f931f69d6e
Update CHANGELOG.md 2020-11-21 20:24:00 -08:00
Alex R. Delp 6e34aae429
Merge pull request #246 from dusk125/tri_shader
#244 GLTriangle's fragment shader is used when rendered by the Canvas.
2020-11-21 20:22:37 -08:00
Alex R. Delp cef3a7e7a6
Update CHANGELOG.md 2020-11-21 19:58:32 -08:00
Alex R. Delp fd754b5263
Merge pull request #262 from snargleplax/rangeTargets
Use slice for range in Drawer.Dirty(), to improve performance
2020-11-21 19:56:25 -08:00
Luke Meyers ef6cafa0cb Use slice for range in Drawer.Dirty(), to improve performance 2020-10-12 22:31:00 -07:00
Theoo3 7a67b10cec undo go.mod change 2020-09-12 11:02:02 -07:00
Theoo3 7027267cbf Clipboard 2020-09-12 10:56:39 -07:00
Alex R. Delp ba7fa122b2
Update CHANGELOG.md 2020-08-22 09:50:41 -07:00
Alex R. Delp 65ff420b25
Merge pull request #253 from roipoussiere/fix-newatlas
tests and fixes faiface#123, SIGSEGV on text.NewAtlas if glyph absent
2020-08-22 09:49:59 -07:00
Alex R. Delp fd73dd5216
Update CHANGELOG.md 2020-08-22 09:43:47 -07:00
Alex R. Delp 775e23efac
Update CHANGELOG.md 2020-08-22 09:39:04 -07:00
Alex R. Delp ae6c93ba96
Update CHANGELOG.md 2020-08-22 09:37:06 -07:00
Nathanael Jourdane 3b599e70ec Fix text anchor position when txt.Bounds().W() != txt.Dot.X-txt.Orig.X 2020-08-22 15:31:07 +02:00
Nathanael Jourdane 7d92e04e63 tests and fixes faiface#123, SIGSEGV on text.NewAtlas if glyph absent 2020-08-13 11:57:30 +02:00
Nathanael Jourdane c9681cec8a Add Anchor.Opposite() func 2020-08-12 17:31:05 +02:00
Nathanael Jourdane 2342984744 bugfix: inverse all anchors 2020-08-12 17:24:29 +02:00
Nathanaël a8b7779fbe Add Text.AlignedTo() 2020-08-11 22:16:16 +02:00
Nathanaël Jourdane 7a106fa572 Makes Anchor a type Vec instead of a struct 2020-08-11 11:33:05 +02:00
Nathanaël e66e94b943 func AlignedTo is used for Rect instead of Matrix 2020-08-11 09:17:51 +02:00
Nathanaël d6663ba0a0 Add Matrix.Aligned() 2020-08-11 08:44:39 +02:00
Nathanaël Jourdane 50e1d8c96b Add Rect.AnchorTo() 2020-08-10 17:25:09 +02:00
Nathanaël Jourdane 2c79e7325a Add AnchorPos struct and functions 2020-08-10 15:57:56 +02:00
Allen Ray 865e930023
Merge branch 'master' into tri_shader 2020-06-28 20:03:04 -04:00
Alex R. Delp 88b1e1c2d3
Merge pull request #245 from dusk125/master
Adding Clipping rectangle support in GLTriangles
2020-06-28 16:54:03 -07:00
Allen Ray 850dd62a98 Updating changelog 2020-06-28 12:58:13 -04:00
Allen Ray 926463aa6f #244 GLTriangle's fragment shader is used when rendered by the Canvas.
Allows for custom fragment shaders to be used after canvas.MakeTriangles.
2020-06-21 23:27:28 -04:00
Allen Ray 543819d066 Forgot to add import in PR 2020-06-21 23:09:08 -04:00
Allen Ray c3e5e4fdb2 Moved triangle clipping to the proper location 2020-06-21 22:54:23 -04:00
Allen Ray e79a8c37e6 Adding support for clipping rectangles in GLTriangles 2020-06-21 22:51:08 -04:00
Alex R. Delp d65a63e962
Merge pull request #243 from andrebq/master
expose glfw.WaitEvent using Window.WaitInput
2020-06-17 12:21:18 -07:00
André 6cef9cf779 rename method and handle negative timeouts 2020-06-16 21:11:41 +02:00
André 1f914181d8 add entry to CHANGELOG 2020-06-14 18:28:14 +02:00
André a61d321615 expose glfw.WaitEvent using Window.WaitInput 2020-06-14 17:52:03 +02:00
Alex R. Delp 585f7c1f14
Merge pull request #237 from fgrosse/more-window-hints
Add window hints to created maximized or invisible windows
2020-05-24 00:01:25 -07:00
Friedrich Große 607e0a11c0 Update changelog 2020-05-21 12:00:13 +02:00
Friedrich Große 506588f40b Add window hints to created maximized or invisible windows 2020-05-21 11:56:00 +02:00
Alex R. Delp 42582c7f8e
Update CHANGELOG.md 2020-05-18 18:30:40 -07:00
Alex R. Delp 625c4c393d
Merge pull request #233 from zergon321/gamepad-support
Gamepad API added.
2020-05-18 18:30:17 -07:00
Alex R. Delp 5568202e30
Merge pull request #235 from fgrosse/window-initial-position
Support setting an initial window position
2020-05-16 09:09:35 -07:00
Friedrich Große 70eff126da
Merge branch 'master' into window-initial-position 2020-05-11 18:04:58 +02:00
Alex R. Delp 5d34c08754
Update CHANGELOG.md 2020-05-10 14:14:21 -07:00
Alex R. Delp c35e0abd1c
Update CHANGELOG.md 2020-05-10 14:09:06 -07:00
Alex R. Delp a3f51715d9
Merge pull request #234 from fgrosse/transparency
Support glfw.TransparentFramebuffer window hint
2020-05-10 14:05:59 -07:00
Alex R. Delp b5e4674b45
Merge branch 'master' into transparency 2020-05-10 14:04:36 -07:00
Alex R. Delp 9a9f831114
Merge pull request #236 from bcvery1/master
Fixing line intersection for lines through origin
2020-05-10 13:49:13 -07:00
Ben Cragg 6b71245324 Updated change log 2020-05-10 14:34:10 +01:00
Ben Cragg 5254b4eb62 Fixing 0,0 being legit closest point 2020-05-10 14:25:05 +01:00
Ben Cragg 76534cb8d0 Removing commented out tests 2020-05-10 14:24:07 +01:00
Ben Cragg 8792d4436c Adding vert and horz tests 2020-05-10 14:13:46 +01:00
Alex R. Delp d2d1f03c32 Add a clarifying comment in imdraw push 2020-05-09 12:46:09 -07:00
Alex R. Delp 6a71f07d12
Update README.md 2020-05-09 12:43:38 -07:00
Friedrich Große 79a3bd7c63 Update changelog 2020-05-09 21:17:52 +02:00
Friedrich Große b7d5e41486 Support setting an initial window position 2020-05-09 21:07:17 +02:00
Friedrich Große 5e3b82eeab Support glfw.TransparentFramebuffer window hint 2020-05-09 20:59:40 +02:00
Alex R. Delp d7884ac96a
Update CONTRIBUTING.md 2020-05-09 10:15:57 -07:00
Alex R. Delp b40ab4939f
Update CHANGELOG.md 2020-05-09 10:13:05 -07:00
Alex R. Delp 6b4a6905de
Update CHANGELOG.md 2020-05-09 10:12:32 -07:00
zergon321 0a8feedd8a Gamepad API added. 2020-05-09 17:17:24 +03:00
Alex R. Delp 914e25233f
Update CHANGELOG.md 2020-05-08 19:21:00 -07:00
Alex R. Delp 197e604220
Merge pull request #211 from zergon321/add-glfw-v33
pixelgl has been ported to GLFW 3.3.
2020-05-08 19:18:31 -07:00
Alex R. Delp e350f9fef1
Merge pull request #231 from delp/master
Update CHANGELOG.md Unreleased Section
2020-05-08 19:12:26 -07:00
Alex R. Delp d8a3c92052
Update CHANGELOG.md 2020-05-06 17:14:10 -07:00
Alex R. Delp d35b25061e
Merge pull request #214 from jared-nishikawa/master
Added DisableCursor
2020-05-06 17:02:32 -07:00
Alex R. Delp b9c32bc51f
Merge pull request #230 from fgrosse/fix-changelog
Fix CHANGELOG.md
2020-05-06 16:52:19 -07:00
Friedrich Große 0110a3c88a Fix CHANGELOG.md 2020-05-03 20:32:09 +02:00
Alex R. Delp 3be4e4cb84
Update README.md 2020-05-02 13:48:40 -07:00
Alex R. Delp bd68a95818
Update CHANGELOG.md 2020-05-02 12:54:03 -07:00
Alex R. Delp d181dae9ba
Update CHANGELOG.md 2020-05-02 12:53:29 -07:00
Alex R. Delp 42b1229128
Merge pull request #219 from snargleplax/master
Expose pixelgl.Window.SwapBuffers seperately from Window.Update
2020-05-02 12:32:44 -07:00
Alex R. Delp 37f588845a
Update CHANGELOG.md 2020-05-02 12:19:32 -07:00
zergon321 8909d199d1 Revert "Gamepad API added."
This reverts commit 484c1775b0.
2020-04-20 21:46:15 +03:00
zergon321 09ea5585a0 Revert "Magic number removed."
This reverts commit 7e3a88ef63.
2020-04-20 21:43:49 +03:00
Alex R. Delp 8012403b8f
Update README.md 2020-04-20 10:48:12 -07:00
Alex R. Delp dbdea628da
Update README.md 2020-04-20 10:31:12 -07:00
Alex R. Delp fc543042cc
Update README.md 2020-04-20 10:23:01 -07:00
Alex R. Delp ed63c18bb4
Update README.md 2020-04-20 10:13:31 -07:00
Alex R. Delp 9cc1cddcf2
Update README.md 2020-04-18 15:41:05 -07:00
Michal Štrba 73ce868aa9
looking for a new maintainer 2020-04-14 19:05:59 +02:00
Luke Meyers effd43bc56 Expose pixelgl.Window.SwapBuffers
Allow buffers to be swapped without polling input, offering decoupling symmetrical with that provided by UpdateInput.
2020-02-09 20:59:59 -08:00
Jared fc29ad7886 changed name to SetCursorDisabled 2020-02-07 16:22:39 -07:00
Jared 93a24aa6df Added DisableCursor 2020-01-08 22:24:48 -07:00
nightghost 7e3a88ef63 Magic number removed. 2020-01-05 21:47:11 +03:00
nightghost 484c1775b0 Gamepad API added. 2020-01-05 21:37:24 +03:00
nightghost a3aba6004d pixelgl has been ported to GLFW 3.5. 2019-12-31 01:59:20 +03:00
Michal Štrba e51d4a6676
fix some typos 2019-11-06 00:50:48 +01:00
Michal Štrba 87299dc29b
Merge pull request #205 from faiface/revert-203-comments-errors
Revert "Fixed a couple of errors in methods comments"
2019-11-06 00:46:39 +01:00
Michal Štrba 0d44d2cc9b
Revert "Fixed a couple of errors in methods comments" 2019-11-06 00:46:26 +01:00
Michal Štrba c4f9faea97
Merge pull request #203 from svera/comments-errors
Fixed a couple of errors in methods comments
2019-11-06 00:43:48 +01:00
Michal Štrba 18972c2818
Merge branch 'master' into comments-errors 2019-11-06 00:43:12 +01:00
Michal Štrba 853405f61c
Merge pull request #204 from Tskken/master
Merge adding Rect.Intersects(Rect)bool function
2019-11-06 00:39:51 +01:00
Tskken 8d0c306de9 typo fix
fixed typo in comments.
2019-11-05 16:32:55 -07:00
Tskken 837a4efa80 Fixed benchmark error
Fixed error for forgeting to change benchark function from IsIntersect to Intersects.
2019-11-05 16:16:29 -07:00
Tskken 706becb764 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.
2019-11-05 16:14:36 -07:00
Dylan Blanchard bb82a87aaf
Merge pull request #1 from faiface/master
Merge faiface/pixel version to Tskken/pixel
2019-11-05 01:04:33 -07:00
Sergio Vera f24d465808 Fixed a couple of errors in methods comments 2019-10-24 10:29:11 +02:00
Michal Štrba aeef94f200
Merge pull request #195 from fgrosse/release-notes
Start to maintain a change log
2019-06-30 00:14:12 +02:00
Friedrich Große 2aa6d2d212 Update wording about tracked changes in README.md 2019-06-29 14:39:10 +02:00
Friedrich Große 8a9191c283 Start to maintain a change log 2019-06-23 11:33:59 +02:00
Michal Štrba 07754109cb
Merge pull request #186 from fgrosse/extra-window-hints
Adding AutoIconify and Floating GLFW window hints
2019-06-12 22:27:57 +02:00
Michal Štrba 16d04f467f
Merge pull request #190 from fgrosse/fix-modules
Fix Go modules
2019-05-30 16:53:47 +02:00
Friedrich Große a5c3bec928 Prevent default TravisCI install action 2019-05-30 15:23:12 +02:00
Friedrich Große d513ffd192 Force TravisCI to run tests using dependencies from the go.mod file 2019-05-30 14:56:19 +02:00
Friedrich Große c5c5127c8d Fix small typo in README.md 2019-05-30 14:52:32 +02:00
Friedrich Große 109cb03fae Update TravisCI to fail if dependencies are missing in go.mod 2019-05-30 14:47:19 +02:00
Friedrich Große 23171aea6c Add missing github.com/golang/freetype dependency 2019-05-30 14:36:11 +02:00
Friedrich Große dd5e0d8b09 Adding NoIconify and AlwaysOnTop GLFW window hints 2019-05-30 14:29:43 +02:00
Michal Štrba 3c6f33365b
Merge pull request #189 from udhos/master
Support for Modules.
2019-05-25 14:50:33 +02:00
udhos 38283d0e55 Support for Modules. 2019-05-24 17:37:22 -03:00
Michal Štrba daa8205b8c
Merge pull request #188 from Tskken/master
Added ZR for zero rect
2019-05-24 20:33:23 +02:00
Tsukinai ba6fc7db8a docs changes
changed wording to match zero-vector returns in other functions.
2019-05-24 12:25:35 -06:00
Tsukinai 285676ca17 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...
2019-05-24 12:20:00 -06:00
Tsukinai b18647916f Added ZR for zero rect
Added the ZR for rectangle for both utility and consistensy with pixel.ZV
2019-05-24 12:00:11 -06:00
Michal Štrba de3617a259
Merge pull request #184 from fgrosse/fix-contributing-guidelines
Fixed link to examples repo in CONTRIBUTING.md
2019-05-19 22:08:02 +02:00
Friedrich Große 1f96c1e995 Fixed link to examples repo in CONTRIBUTING.md 2019-05-19 18:44:51 +02:00
Michal Štrba a68a4e38b4
Merge pull request #183 from bcvery1/TriangleEfficiency
Added TriangleData benchmarks and improvements
2019-04-25 14:17:09 +02:00
Ben Cragg 9e6e6197dd Added benchmark for MakeTrianglesData 2019-04-24 11:17:48 +01:00
Ben Cragg 159df28dcc Reverted changes on setlen 2019-04-24 10:32:35 +01:00
Ben Cragg d48ef44bdc Added TriangleData benchmarks and improvements 2019-04-24 10:11:20 +01:00
Michal Štrba 9aca3bfe7a
Merge pull request #182 from bcvery1/fixVertexRace
Fix vertex race
2019-04-16 10:27:08 +02:00
Ben Cragg 2cf81cd4df Setting dist 2019-04-16 09:01:36 +01:00
Ben Cragg 3aad3268cd Making xvfb a service 2019-04-16 08:47:42 +01:00
Ben Cragg 9d5c7afe12 Adding test for drawing sprites 2019-04-15 11:25:30 +01:00
Ben Cragg 85b18b567a prevented concurrent race condition 2019-04-15 11:20:40 +01:00
Michal Štrba e8ba24ea40
Merge pull request #181 from bcvery1/fixSpritePositioning
Setting position in correct order
2019-04-15 10:07:17 +02:00
Ben Cragg d601bc65e4 Setting position in correct order 2019-04-15 08:46:29 +01:00
Michal Štrba 2cb87efb8c
Merge pull request #176 from bcvery1/master
Avoiding floating point rounding errors
2019-04-14 22:50:57 +02:00
Michal Štrba ccf8e9dcdc
Merge pull request #177 from Immueggpain/patch-1
why 2 loops and assign twice? one is enough
2019-04-14 22:48:56 +02:00
Immueggpain 95ac5e1e2a
why 2 loops and assign twice? one is enough 2019-04-13 23:47:02 +08:00
Ben Cragg 96e0a8f3bf Added test cases 2019-04-10 12:47:21 +01:00
Ben Cragg f698bae1df Added floating point round error correction 2019-04-10 12:47:07 +01:00
Michal Štrba a6c8b92517
Merge pull request #174 from bcvery1/addLine
Add line geometry
2019-04-04 17:49:16 +02:00
Ben Cragg 9eb2834a10 fixed index issue 2019-04-04 16:20:21 +01:00
Ben Cragg 0092d6a577 implementing pre 1.8 sortslice solution 2019-04-04 16:09:25 +01:00
Ben Cragg b8bb00a161 Setting order of returned points on Circle 2019-04-04 15:49:40 +01:00
Ben Cragg 29b1220ec3 Setting order of returned points on Rect 2019-04-04 15:48:27 +01:00
Ben Cragg 364a7a84ae Prevented test results order mattering 2019-04-04 15:31:34 +01:00
Ben Cragg bcda85acd2 Adding fullstop at end of func comment 2019-04-04 15:13:47 +01:00
Ben Cragg 966150a856 Removing backticks 2019-04-04 15:13:24 +01:00
Ben Cragg f3377bb16f Added circle intersection points 2019-04-04 11:53:48 +01:00
Ben Cragg 4795a92b41 fixed line intersect and added rect intersection points 2019-04-03 16:58:30 +01:00
Ben Cragg 83c62a0313 fixing line intersect function 2019-04-03 16:21:57 +01:00
Ben Cragg c3e69c4f35 Tidying up function comments 2019-04-03 15:26:38 +01:00
Ben Cragg 352785e1b8 Supporting pre go1.10 2019-04-03 12:37:33 +01:00
Ben Cragg e6392a228d Making len more sensible 2019-04-03 12:22:21 +01:00
Ben Cragg 3592de858c Clarified comment 2019-04-03 12:19:30 +01:00
Ben Cragg a1d36f8c7e Clarified comment 2019-04-03 12:16:27 +01:00
Ben Cragg 98d5b9b417 Adding vertical/horizontal edge cases 2019-04-03 12:12:27 +01:00
Ben Cragg e5ff236d71 Removed debug lines 2019-04-03 12:03:58 +01:00
Ben Cragg faf6558294 wip line tests 2019-04-03 12:03:07 +01:00
Ben Cragg e99ac56daa wip line tests 2019-04-03 11:52:42 +01:00
Ben Cragg aa7ef59ecd wip line tests 2019-04-03 11:47:36 +01:00
Ben Cragg eb3d7e0787 wip line tests 2019-04-03 11:09:14 +01:00
Ben Cragg 11e2012ef5 WIP line tests 2019-04-03 08:23:34 +01:00
Ben Cragg 8cd352b7a3 WIP line tests 2019-04-03 08:13:33 +01:00
Ben Cragg 997f23dfb5 WIP line tests 2019-04-02 17:01:18 +01:00
Ben Cragg 04c3ef72a3 WIP line tests 2019-04-02 16:27:54 +01:00
Ben Cragg 237d77596f WIP line tests 2019-04-02 13:48:43 +01:00
Ben Cragg c7eac06499 WIP line tests 2019-04-02 13:46:50 +01:00
Ben Cragg 55b87ca5b1 WIP line tests 2019-04-02 13:33:41 +01:00
Ben Cragg 2478da5d12 wip adding line tests 2019-04-02 08:49:45 +01:00
Ben Cragg cca37c750e wip adding line tests 2019-04-01 16:01:08 +01:00
Ben Cragg 1a275b5929 Filled rect tests 2019-04-01 15:44:14 +01:00
Ben Cragg 9d07d69429 Fixed so corners are provided in anticlockwise pattern 2019-04-01 15:42:20 +01:00
Ben Cragg 128ec4d4c0 Added function to create a Line 2019-04-01 15:37:23 +01:00
Ben Cragg 1eac5d8dc2 Added test templates for new rect tests 2019-04-01 15:33:31 +01:00
Ben Cragg a5ea71811e Added test templates 2019-04-01 15:31:34 +01:00
Ben Cragg d3912a6486 Added Line struct and methods 2019-04-01 15:31:20 +01:00
Michal Štrba 0db4348595
Merge pull request #172 from bcvery1/addTilePixReadme
Add related repository TilePix to README
2019-03-26 18:35:12 +01:00
Ben Cragg b2c8bff6eb Adding related packages 2019-03-26 15:42:59 +00:00
Ben Cragg 95148365d0 Reverting back to origin 2019-03-26 15:42:12 +00:00
Ben Cragg 50ef216e79 Removing wip files 2019-03-26 15:41:32 +00:00
Ben Cragg 09db6d8b38 Merge branch 'master' of github.com:bcvery1/pixel 2019-03-26 15:32:42 +00:00
Ben Cragg 86de5123c0 Added related libraries 2019-03-26 15:25:41 +00:00
Michal Štrba 470791a59d
Merge pull request #170 from Tobaloidee/master
logo upload & readme update
2019-03-20 16:46:00 +01:00
tobaloidee 0475b1ddb4
Update README.md 2019-03-20 11:16:49 +08:00
tobaloidee c9328c9260
Delete logo 2019-03-20 11:12:07 +08:00
tobaloidee c7836cb3ad
logo upload 2019-03-20 11:10:30 +08:00
tobaloidee 3d2f083f70
Create logo 2019-03-20 11:09:54 +08:00
faiface 771e2e92e2 Merge branch 'master' of https://github.com/faiface/pixel 2019-03-18 22:56:18 +01:00
faiface 09b50d2296 add Gizmo to examples part of README 2019-03-18 22:55:57 +01:00
Michal Štrba 66670d9905
Merge pull request #164 from CodeLingoBot/rewrite
Fix function comments based on best practices from Effective Go
2019-03-12 21:59:53 +01:00
CodeLingo Bot 489b03138f Fix function comments based on best practices from Effective Go
Signed-off-by: CodeLingo Bot <bot@codelingo.io>
2019-03-11 00:45:01 +00:00
Michal Štrba 3cc0ec3d30
Merge pull request #162 from bcvery1/fixCircleIntersect
Fix circle intersect
2019-02-22 17:13:57 +01:00
Ben Cragg b3246f6234 Updated tests 2019-02-18 12:16:16 +00:00
Ben Cragg 680d16c17b Corrected returned Vectors for corner overlaps 2019-02-18 12:03:54 +00:00
Michal Štrba 8563c28493
Merge pull request #157 from bcvery1/addcircle
Added circle geometry - small feature/improvement
2019-02-14 16:00:46 +01:00
Ben Cragg 160bcb9960 Merge branch 'addcircle' of github.com:bcvery1/pixel into addcircle 2019-02-14 14:25:06 +00:00
Ben Cragg 6f1e3bbbf8 More intersection tests 2019-02-14 14:23:50 +00:00
Ben Cragg cc9f03d393 corrected function preambles 2019-02-14 14:23:50 +00:00
Ben Cragg b61602cdf4 fixed area tests 2019-02-14 14:23:50 +00:00
Ben Cragg f21c48599f Made naming consistent 2019-02-14 14:23:50 +00:00
Ben Cragg ab70533793 wip 2019-02-14 14:23:50 +00:00
Ben Cragg 0a0c8ff110 corrected area formula 2019-02-14 14:23:50 +00:00
Ben Cragg 5abf2d29a6 swapped radius and center order 2019-02-14 14:23:50 +00:00
Ben Cragg 4d30a8fe49 fixed intersect function 2019-02-14 14:23:50 +00:00
Ben Cragg ecda96a36f removed unneeded Min call 2019-02-14 14:23:50 +00:00
Ben Cragg 520459bc6d made test param generation more consistant 2019-02-14 14:23:50 +00:00
Ben Cragg e225e9a1ef corrected comment 2019-02-14 14:23:50 +00:00
Ben Cragg 13171c409b using Lerp 2019-02-14 14:23:50 +00:00
Ben Cragg 2e275ab0d5 Made test clearer 2019-02-14 14:23:50 +00:00
Ben Cragg 23168ee324 remove local var 2019-02-14 14:23:50 +00:00
Ben Cragg 44d030b9ca normalising before getting bigger/smaller 2019-02-14 14:23:50 +00:00
Ben Cragg b06a31baa3 Removed Diameter function 2019-02-14 14:23:50 +00:00
Ben Cragg 4c6d061455 added rect-circle intersection functions 2019-02-14 14:23:50 +00:00
Ben Cragg 620c551f70 moved rect test struct 2019-02-14 14:23:50 +00:00
Ben Cragg 16722d55e1 not exporting circle size comparisons 2019-02-14 14:22:52 +00:00
Ben Cragg ee24eeb67d fixed circle.Intersect 2019-02-14 14:22:52 +00:00
Ben Cragg 5072f34b91 Added Circle geometry and tests 2019-02-14 14:22:52 +00:00
Ben Cragg d4530ca9fe More intersection tests 2019-02-14 14:18:14 +00:00
Ben Cragg 3b63b7eff9 corrected function preambles 2019-02-14 14:15:26 +00:00
Ben Cragg 91c16c34da fixed area tests 2019-02-14 14:12:54 +00:00
Ben Cragg e8a3621140 Made naming consistent 2019-02-14 14:12:05 +00:00
Michal Štrba 730d33a341
Merge pull request #160 from jacekolszak/master
Fix Matrix.Unproject problem with rotated matrix
2019-02-14 15:06:00 +01:00
Jacek Olszak 42cc2a0376 #159 Use map instead of array for named Matrices 2019-02-13 15:25:12 +01:00
Jacek Olszak 742d1226d8 #159 Make namedMatrices an array instead of slice 2019-02-13 15:13:04 +01:00
Jacek Olszak 0a054f35fd #159 Name matrices in unit test to avoid confusion with test cases 2019-02-13 15:06:50 +01:00
Jacek Olszak 392fe90b11 #159 Test whether Matrix.Unprejected(Matrix.Projected(vertex)) == vertex 2019-02-13 14:39:47 +01:00
Jacek Olszak 2e0da4f44a #159 Another reduction of Simplify Matrix.Unproject formulas 2019-02-12 21:06:37 +01:00
Jacek Olszak b0ed22e0ec #159 Simplify Matrix.Unproject formulas 2019-02-12 21:00:05 +01:00
Jacek Olszak 0aec9fead0 #159 Format code in TestMatrix_Unproject 2019-02-12 18:53:38 +01:00
Jacek Olszak 1e19db5b25 #159 Add test case when matrix determinant is not 1 2019-02-12 18:40:15 +01:00
Jacek Olszak a0713598da #159 Add test case for rotated and moved matrix 2019-02-12 16:26:19 +01:00
Jacek Olszak 3b366d1edd #159 Use more precise delta for floats comparision 2019-02-12 15:24:17 +01:00
Jacek Olszak cabaee680e #159 Fix unproject for rotated matrix 2019-02-12 14:57:03 +01:00
Ben Cragg 8650692efa wip 2019-01-31 08:22:59 +00:00
Ben Cragg ba4f417559 corrected area formula 2019-01-30 08:38:51 +00:00
Ben Cragg ecd686769c swapped radius and center order 2019-01-30 08:37:27 +00:00
Ben Cragg 2cce50832d fixed intersect function 2019-01-29 12:31:59 +00:00
Ben Cragg c70d7575ce removed unneeded Min call 2019-01-29 12:27:38 +00:00
Ben Cragg 4eba5e37ae made test param generation more consistant 2019-01-29 12:18:35 +00:00
Ben Cragg 0cddb56114 corrected comment 2019-01-29 11:47:49 +00:00
Ben Cragg 4b6cf201f7 using Lerp 2019-01-29 11:45:00 +00:00
Ben Cragg cff8697c97 Made test clearer 2019-01-29 11:41:10 +00:00
Ben Cragg d0ceea6849 remove local var 2019-01-29 11:34:59 +00:00
Ben Cragg 56cd321ab8 normalising before getting bigger/smaller 2019-01-29 11:33:12 +00:00
Ben Cragg 8efed1de5c Removed Diameter function 2019-01-29 11:23:55 +00:00
Ben Cragg 9a32601c6b added rect-circle intersection functions 2019-01-29 11:20:21 +00:00
Ben Cragg fdc068e855 renamed test function to match convention 2019-01-29 09:40:44 +00:00
Ben Cragg 9b8d4c7461 moved rect test struct 2019-01-29 09:39:44 +00:00
Ben Cragg a56f7fa422 not exporting circle size comparisons 2019-01-29 09:38:24 +00:00
Ben Cragg 0d5ba92fbe fixed circle.Intersect 2019-01-29 09:33:20 +00:00
faiface 20b05d9ec2 add Vulkan support + strike-through GPU effects from the roadmap 2019-01-28 21:54:37 +01:00
Ben Cragg d102169151 Added Circle geometry and tests 2019-01-28 09:00:24 +00:00
Michal Štrba f1ad821f1a
Merge pull request #156 from Humpheh/master
Correctly index joysticks in internal state (fixes #155)
2019-01-22 22:53:08 +01:00
Humphrey Shotton 3be890ea80 Use JoystickLast instead of Joystick16 2019-01-22 21:48:24 +00:00
Humphrey Shotton a46f6f6cf4 Correctly index joysticks in internal state 2019-01-22 21:37:15 +00:00
Michal Štrba 9e0f11abbb
Merge pull request #154 from StephenMichaelis/master
Mouse/Cursor Operations
2019-01-21 23:18:30 +01:00
Stephen Michaelis 8d20deea05 update MouseEntered to MouseInsideWindow 2019-01-21 17:13:51 -05:00
Stephen Michaelis e7a625f5d2 updated Floor method 2019-01-20 10:00:35 -05:00
Stephen Michaelis d3f6331240 Adding a pixel.Vec Floor method to geometry.go 2019-01-20 09:56:30 -05:00
Stephen Michaelis ee54171247 Revert to 4c817d7 2019-01-19 14:39:29 -05:00
Stephen Michaelis 2b79131104 added go mod files 2019-01-19 13:59:38 -05:00
Stephen Michaelis 4c817d7c20 adding mouse operations 2019-01-19 10:24:01 -05:00
Ben Cragg 3bed5a8449 WIP adding tests 2019-01-18 11:37:17 +00:00
Michal Štrba a1bbfa3020
Merge pull request #152 from Humpheh/master
Added joystick input
2019-01-10 20:56:20 +01:00
Humphrey Shotton 026878de07 Mark API as experimental 2019-01-10 19:25:55 +00:00
Humphrey Shotton 52b4384240 Removed JoystickLast 2019-01-09 21:07:46 +00:00
Humphrey Shotton 676509d856 Added joystick input 2018-12-30 18:42:02 +00:00
Michal Štrba bdd9349b37
Merge pull request #147 from Lallassu/Lallassu-patch-1
Added position as out variable from vertex shader.
2018-12-11 23:55:13 +01:00
Magnus c18b8f1c29
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.
2018-12-11 09:24:17 +01:00
Michal Štrba 7b509e1d7d
Merge pull request #145 from gonutz/master
Add No-Brain Jogging to examples
2018-11-23 11:34:30 +01:00
Lars 4a0b2f5107 Replace startfield example with No-Brain Jogging 2018-11-23 11:23:38 +01:00
lars 17e407f0c1 Add No-Brain Jogging to examples 2018-11-21 23:44:46 +00:00
45 changed files with 4120 additions and 638 deletions

View File

@ -1,4 +1,6 @@
language: go
# https://github.com/golang/go/issues/31293
dist: xenial
sudo: false
addons:
apt:
@ -11,14 +13,22 @@ addons:
- libxi-dev
- libopenal-dev
- libasound2-dev
- libgl1-mesa-dev
services:
- xvfb
env:
- GO111MODULE=on
go:
- 1.8
- 1.7.4
- tip
- 1.12.x
install:
- go get -t ./...
- # 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 -i -race ./...
- go test -v -race ./...
- go test -v -race -mod=readonly ./...

63
CHANGELOG.md Normal file
View File

@ -0,0 +1,63 @@
# 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 AnchorPos struct and functions #252
- Add Clipboard Support
- Fix SIGSEGV on text.NewAtlas if glyph absent
- Use slice for range in Drawer.Dirty(), to improve performance
- GLTriangle's fragment shader is used when rendered by the Canvas.
- Add MSAA support
## [v0.10.0] 2020-08-22
- Add AnchorPos struct and functions
- Gamepad API added
- Support setting an initial window position
- Support hiding the window initially
- Support creating maximized windows
- Support waiting for events to reduce CPU load
- Adding clipping rectangle support in GLTriangles
## [v0.10.0-beta] 2020-05-10
- Add `WindowConfig.TransparentFramebuffer` option to support window transparency onto the background
- Fixed Line intersects failing on lines passing through (0, 0)
## [v0.10.0-alpha] 2020-05-08
- Upgrade to GLFW 3.3! :tada:
- Closes https://github.com/faiface/pixel/issues/137
- Add support for glfw's DisableCursor
- Closes https://github.com/faiface/pixel/issues/213
## [v0.9.0] - 2020-05-02
- Added feature from https://github.com/faiface/pixel/pull/219
- Exposing Window.SwapBuffers so buffers can be swapped without polling input
- 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.10.0...HEAD
[v0.10.0]: https://github.com/faiface/pixel/compare/v0.10.0-beta...v0.10.0
[v0.10.0-beta]: https://github.com/faiface/pixel/compare/v0.10.0-alpha...v0.10.0-beta
[v0.10.0-alpha]: https://github.com/faiface/pixel/compare/v0.9.0...v0.10.0-alpha
[v0.9.0]: https://github.com/faiface/pixel/compare/v0.8.0...v0.9.0
[v0.8.0]: https://github.com/faiface/pixel/releases/tag/v0.8.0

View File

@ -4,11 +4,13 @@
## 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.
4. **Join the big development** by joining the discussion on our [Discord Server](https://discord.gg/q2DK4MP), where we can discuss bigger changes and implement them after that.
## 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

View File

@ -1,5 +1,12 @@
# 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)
# \*\*\*\*\*NOTICE\*\*\*\*\*
This repo is not under active development anymore and has been archived. Continued development has been migrated to [Pixel2](https://github.com/gopxl/pixel). A big thank you to [faiface](https://github.com/faiface) for creating this awesome library and for all the hard work put into it. We encourage old and new users to check out the new repo and contribute to it.
<p align="center"><img src="logo/LOGOTYPE-HORIZONTAL-BLUE.png"></p>
# 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.
@ -8,8 +15,18 @@ 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.
All significant changes are documented in [CHANGELOG.md](CHANGELOG.md).
## Tutorial
The [Wiki of this repo](https://github.com/faiface/pixel/wiki) contains an extensive tutorial
@ -46,9 +63,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) | [Gizmo](https://github.com/Lallassu/gizmo) |
| --- | --- |
| ![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) |
| ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![Gizmo](https://github.com/Lallassu/gizmo/blob/master/preview.png) |
## Features
@ -94,6 +111,14 @@ 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.
- [spriteplus](https://github.com/cebarks/spriteplus) Basic `SpriteSheet` and `Animation` implementations
- [PixelUI](https://github.com/dusk125/pixelui) Imgui-based GUIs for Pixel
- [pixelutils](https://github.com/dusk125/pixelutils) Variety of game related utilities (sprite packer, id generator, ticker, sprite loader, voronoia diagrams)
## Missing features
Pixel is in development and still missing few critical features. Here're the most critical ones.
@ -104,8 +129,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!
@ -126,7 +152,7 @@ The OpenGL version used is **OpenGL 3.3**.
headers and libraries.
- On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev` and `xorg-dev` packages.
- On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel
libXinerama-devel mesa-libGL-devel libXi-devel` packages.
libXinerama-devel mesa-libGL-devel libXi-devel libXxf86vm-devel` packages.
- See [here](http://www.glfw.org/docs/latest/compile.html#compile_deps) for full details.
**The combination of Go 1.8, macOS and latest XCode seems to be problematic** as mentioned in issue
@ -135,6 +161,8 @@ The OpenGL version used is **OpenGL 3.3**.
## Contributing
Join us in the [Discord Chat!](https://discord.gg/q2DK4MP)
Pixel is in, let's say, mid-stage of development. Many of the important features are here, some are
missing. That's why **contributions are very important and welcome!** All alone, I will be able to
finish the library, but it'll take a lot of time. With your help, it'll take much less. I encourage
@ -152,10 +180,6 @@ better result.
Take a look at [CONTRIBUTING.md](CONTRIBUTING.md) for further information.
For any kind of discussion, feel free to use our
[Gitter](https://gitter.im/pixellib/Lobby)
community.
## License
[MIT](LICENSE)

View File

@ -26,7 +26,7 @@ var _ BasicTarget = (*Batch)(nil)
//
// Note, that if the container does not support TrianglesColor, color masking will not work.
func NewBatch(container Triangles, pic Picture) *Batch {
b := &Batch{cont: Drawer{Triangles: container, Picture: pic}}
b := &Batch{cont: Drawer{Triangles: container, Picture: pic, Cached: true}}
b.SetMatrix(IM)
b.SetColorMask(Alpha(1))
return b

334
circle.go Normal file
View File

@ -0,0 +1,334 @@
package pixel
import (
"fmt"
"math"
)
// Circle is a 2D circle. It is defined by two properties:
// - Center vector
// - Radius float64
type Circle struct {
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(center Vec, radius float64) Circle {
return Circle{
Center: center,
Radius: radius,
}
}
// 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(%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{pixel.Vec{0, 0}, 10}
func (c Circle) Norm() Circle {
return Circle{
Center: c.Center,
Radius: math.Abs(c.Radius),
}
}
// Area returns the area of the Circle.
func (c Circle) Area() float64 {
return math.Pi * math.Pow(c.Radius, 2)
}
// Moved returns the Circle moved by the given vector delta.
func (c Circle) Moved(delta Vec) Circle {
return Circle{
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(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{
Center: c.Center,
Radius: c.Radius + radiusDelta,
}
}
// 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()
}
// 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 {
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 {
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.Norm(), d.Norm())
smallerC := minCircle(c.Norm(), d.Norm())
// 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 := Lerp(smallerC.Center, biggerC.Center, theta)
return Circle{
Center: center,
Radius: r,
}
}
// 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 {
// Check if one of the circles encompasses the other; if so, return that one
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
}
// 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(center, 0)
}
radius := c.Center.To(d.Center).Len() - (c.Radius + d.Radius)
return Circle{
Center: center,
Radius: math.Abs(radius),
}
}
// 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)
}
// 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 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 {
// 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
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
}
// 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
centerToCorner = c.Center.To(r.Min)
}
if c.Center.To(r.Max).Len() <= c.Radius {
// Closest to top-right
centerToCorner = c.Center.To(r.Max)
}
if c.Center.To(rectTopLeft).Len() <= c.Radius {
// Closest to top-left
centerToCorner = c.Center.To(rectTopLeft)
}
if c.Center.To(rectBottomRight).Len() <= c.Radius {
// Closest to bottom-right
centerToCorner = c.Center.To(rectBottomRight)
}
cornerToCircumferenceLen := c.Radius - centerToCorner.Len()
return centerToCorner.Unit().Scaled(cornerToCircumferenceLen)
}
}
// 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. 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)
// 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))
if first.To(l.A).Len() < second.To(l.A).Len() {
return []Vec{first, second}
}
return []Vec{second, first}
}

466
circle_test.go Normal file
View File

@ -0,0 +1,466 @@
package pixel_test
import (
"math"
"reflect"
"testing"
"github.com/faiface/pixel"
)
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.ZV},
want: pixel.Circle{Radius: 10, Center: pixel.ZV},
},
{
name: "C(): zero radius",
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.ZV},
want: pixel.Circle{Radius: -5, Center: pixel.ZV},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := pixel.C(tt.args.center, tt.args.radius); !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.ZV},
want: "Circle(Vec(0, 0), 10.00)",
},
{
name: "Circle.String(): zero radius",
fields: fields{radius: 0, center: pixel.ZV},
want: "Circle(Vec(0, 0), 0.00)",
},
{
name: "Circle.String(): negative radius",
fields: fields{radius: -5, center: pixel.ZV},
want: "Circle(Vec(0, 0), -5.00)",
},
{
name: "Circle.String(): irrational radius",
fields: fields{radius: math.Pi, center: pixel.ZV},
want: "Circle(Vec(0, 0), 3.14)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
}
})
}
}
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.ZV},
want: pixel.C(pixel.ZV, 10),
},
{
name: "Circle.Norm(): zero radius",
fields: fields{radius: 0, center: pixel.ZV},
want: pixel.C(pixel.ZV, 0),
},
{
name: "Circle.Norm(): negative radius",
fields: fields{radius: -5, center: 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.center, tt.fields.radius)
if got := c.Norm(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Circle.Norm() = %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.ZV},
want: 100 * math.Pi,
},
{
name: "Circle.Area(): zero radius",
fields: fields{radius: 0, center: pixel.ZV},
want: 0,
},
{
name: "Circle.Area(): negative radius",
fields: fields{radius: -5, center: pixel.ZV},
want: 25 * math.Pi,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
}
})
}
}
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.ZV},
args: args{delta: 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(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(pixel.V(-5, -10), 10),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
}
})
}
}
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.ZV},
args: args{radiusDelta: 5},
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(pixel.V(0, 0), 10),
},
{
name: "Circle.Resized(): negative delta",
fields: fields{radius: 10, center: pixel.ZV},
args: args{radiusDelta: -5},
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.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)
}
})
}
}
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.center, tt.fields.radius)
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(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(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.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)
}
})
}
}
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.ZV},
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(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(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(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(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.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)
}
})
}
}
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
}{
{
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) {
c := pixel.Circle{
Center: tt.fields.Center,
Radius: tt.fields.Radius,
}
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])
}
}
})
}
}

42
data.go
View File

@ -8,6 +8,16 @@ import (
"math"
)
// zeroValueTriangleData is the default value of a TriangleData element
var zeroValueTriangleData = struct {
Position Vec
Color RGBA
Picture Vec
Intensity float64
ClipRect Rect
IsClipped bool
}{Color: RGBA{1, 1, 1, 1}}
// TrianglesData specifies a list of Triangles vertices with three common properties:
// TrianglesPosition, TrianglesColor and TrianglesPicture.
type TrianglesData []struct {
@ -15,6 +25,8 @@ type TrianglesData []struct {
Color RGBA
Picture Vec
Intensity float64
ClipRect Rect
IsClipped bool
}
// MakeTrianglesData creates TrianglesData of length len initialized with default property values.
@ -22,9 +34,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.
@ -40,12 +54,7 @@ func (td *TrianglesData) SetLen(len int) {
if len > td.Len() {
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}})
*td = append(*td, zeroValueTriangleData)
}
}
if len < td.Len() {
@ -82,6 +91,11 @@ func (td *TrianglesData) updateData(t Triangles) {
(*td)[i].Picture, (*td)[i].Intensity = t.Picture(i)
}
}
if t, ok := t.(TrianglesClipped); ok {
for i := range *td {
(*td)[i].ClipRect, (*td)[i].IsClipped = t.ClipRect(i)
}
}
}
// Update copies vertex properties from the supplied Triangles into this TrianglesData.
@ -96,10 +110,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 &copyTd
return copyTd
}
// Position returns the position property of i-th vertex.
@ -117,6 +130,11 @@ func (td *TrianglesData) Picture(i int) (pic Vec, intensity float64) {
return (*td)[i].Picture, (*td)[i].Intensity
}
// ClipRect returns the clipping rectangle property of the i-th vertex.
func (td *TrianglesData) ClipRect(i int) (rect Rect, has bool) {
return (*td)[i].ClipRect, (*td)[i].IsClipped
}
// PictureData specifies an in-memory rectangular area of pixels and implements Picture and
// PictureColor.
//

300
data_test.go Normal file
View File

@ -0,0 +1,300 @@
package pixel_test
import (
"testing"
"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
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)
}
})
}
}
func BenchmarkTrianglesData_ClipRect(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.ClipRect(tt.position)
}
})
}
}

View File

@ -23,8 +23,10 @@ package pixel
type Drawer struct {
Triangles Triangles
Picture Picture
Cached bool
targets map[Target]*drawerTarget
allTargets []*drawerTarget
inited bool
}
@ -46,7 +48,7 @@ func (d *Drawer) lazyInit() {
func (d *Drawer) Dirty() {
d.lazyInit()
for _, t := range d.targets {
for _, t := range d.allTargets {
t.clean = false
}
}
@ -68,6 +70,7 @@ func (d *Drawer) Draw(t Target) {
pics: make(map[Picture]TargetPicture),
}
d.targets[t] = dt
d.allTargets = append(d.allTargets, dt)
}
if dt.tris == nil {
@ -89,8 +92,11 @@ func (d *Drawer) Draw(t Target) {
pic := dt.pics[d.Picture]
if pic == nil {
pic = t.MakePicture(d.Picture)
if d.Cached {
dt.pics[d.Picture] = pic
}
}
pic.Draw(dt.tris)
}

View File

@ -1,405 +0,0 @@
package pixel
import (
"fmt"
"math"
)
// Clamp returns x clamped to the interval [min, max].
//
// If x is less than min, min is returned. If x is more than max, max is returned. Otherwise, x is
// returned.
func Clamp(x, min, max float64) float64 {
if x < min {
return min
}
if x > max {
return max
}
return x
}
// Vec is a 2D vector type with X and Y coordinates.
//
// Create vectors with the V constructor:
//
// u := pixel.V(1, 2)
// v := pixel.V(8, -3)
//
// Use various methods to manipulate them:
//
// w := u.Add(v)
// fmt.Println(w) // Vec(9, -1)
// fmt.Println(u.Sub(v)) // Vec(-7, 5)
// u = pixel.V(2, 3)
// v = pixel.V(8, 1)
// if u.X < 0 {
// fmt.Println("this won't happen")
// }
// x := u.Unit().Dot(v.Unit())
type Vec struct {
X, Y float64
}
// ZV is a zero vector.
var ZV = Vec{0, 0}
// V returns a new 2D vector with the given coordinates.
func V(x, y float64) Vec {
return Vec{x, y}
}
// Unit returns a vector of length 1 facing the given angle.
func Unit(angle float64) Vec {
return Vec{1, 0}.Rotated(angle)
}
// String returns the string representation of the vector u.
//
// u := pixel.V(4.5, -1.3)
// u.String() // returns "Vec(4.5, -1.3)"
// fmt.Println(u) // Vec(4.5, -1.3)
func (u Vec) String() string {
return fmt.Sprintf("Vec(%v, %v)", u.X, u.Y)
}
// XY returns the components of the vector in two return values.
func (u Vec) XY() (x, y float64) {
return u.X, u.Y
}
// Add returns the sum of vectors u and v.
func (u Vec) Add(v Vec) Vec {
return Vec{
u.X + v.X,
u.Y + v.Y,
}
}
// Sub returns the difference betweeen vectors u and v.
func (u Vec) Sub(v Vec) Vec {
return Vec{
u.X - v.X,
u.Y - v.Y,
}
}
// To returns the vector from u to v. Equivalent to v.Sub(u).
func (u Vec) To(v Vec) Vec {
return Vec{
v.X - u.X,
v.Y - u.Y,
}
}
// Scaled returns the vector u multiplied by c.
func (u Vec) Scaled(c float64) Vec {
return Vec{u.X * c, u.Y * c}
}
// ScaledXY returns the vector u multiplied by the vector v component-wise.
func (u Vec) ScaledXY(v Vec) Vec {
return Vec{u.X * v.X, u.Y * v.Y}
}
// Len returns the length of the vector u.
func (u Vec) Len() float64 {
return math.Hypot(u.X, u.Y)
}
// Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi].
func (u Vec) Angle() float64 {
return math.Atan2(u.Y, u.X)
}
// Unit returns a vector of length 1 facing the direction of u (has the same angle).
func (u Vec) Unit() Vec {
if u.X == 0 && u.Y == 0 {
return Vec{1, 0}
}
return u.Scaled(1 / u.Len())
}
// Rotated returns the vector u rotated by the given angle in radians.
func (u Vec) Rotated(angle float64) Vec {
sin, cos := math.Sincos(angle)
return Vec{
u.X*cos - u.Y*sin,
u.X*sin + u.Y*cos,
}
}
// Normal returns a vector normal to u. Equivalent to u.Rotated(math.Pi / 2), but faster.
func (u Vec) Normal() Vec {
return Vec{-u.Y, u.X}
}
// Dot returns the dot product of vectors u and v.
func (u Vec) Dot(v Vec) float64 {
return u.X*v.X + u.Y*v.Y
}
// Cross return the cross product of vectors u and v.
func (u Vec) Cross(v Vec) float64 {
return u.X*v.Y - v.X*u.Y
}
// Project returns a projection (or component) of vector u in the direction of vector v.
//
// Behaviour is undefined if v is a zero vector.
func (u Vec) Project(v Vec) Vec {
len := u.Dot(v) / v.Len()
return v.Unit().Scaled(len)
}
// Map applies the function f to both x and y components of the vector u and returns the modified
// vector.
//
// u := pixel.V(10.5, -1.5)
// v := u.Map(math.Floor) // v is Vec(10, -2), both components of u floored
func (u Vec) Map(f func(float64) float64) Vec {
return Vec{
f(u.X),
f(u.Y),
}
}
// Lerp returns a linear interpolation between vectors a and b.
//
// This function basically returns a point along the line between a and b and t chooses which one.
// If t is 0, then a will be returned, if t is 1, b will be returned. Anything between 0 and 1 will
// return the appropriate point between a and b and so on.
func Lerp(a, b Vec, t float64) Vec {
return a.Scaled(1 - t).Add(b.Scaled(t))
}
// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
// points, Min and Max.
//
// The invariant should hold, that Max's components are greater or equal than Min's components
// respectively.
type Rect struct {
Min, Max Vec
}
// R returns a new Rect with given the Min and Max coordinates.
//
// Note that the returned rectangle is not automatically normalized.
func R(minX, minY, maxX, maxY float64) Rect {
return Rect{
Min: Vec{minX, minY},
Max: Vec{maxX, maxY},
}
}
// String returns the string representation of the Rect.
//
// r := pixel.R(100, 50, 200, 300)
// r.String() // returns "Rect(100, 50, 200, 300)"
// fmt.Println(r) // Rect(100, 50, 200, 300)
func (r Rect) String() string {
return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)
}
// Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
func (r Rect) Norm() Rect {
return Rect{
Min: Vec{
math.Min(r.Min.X, r.Max.X),
math.Min(r.Min.Y, r.Max.Y),
},
Max: Vec{
math.Max(r.Min.X, r.Max.X),
math.Max(r.Min.Y, r.Max.Y),
},
}
}
// W returns the width of the Rect.
func (r Rect) W() float64 {
return r.Max.X - r.Min.X
}
// H returns the height of the Rect.
func (r Rect) H() float64 {
return r.Max.Y - r.Min.Y
}
// Size returns the vector of width and height of the Rect.
func (r Rect) Size() Vec {
return V(r.W(), r.H())
}
// Area returns the area of r. If r is not normalized, area may be negative.
func (r Rect) Area() float64 {
return r.W() * r.H()
}
// Center returns the position of the center of the Rect.
func (r Rect) Center() Vec {
return Lerp(r.Min, r.Max, 0.5)
}
// Moved returns the Rect moved (both Min and Max) by the given vector delta.
func (r Rect) Moved(delta Vec) Rect {
return Rect{
Min: r.Min.Add(delta),
Max: r.Max.Add(delta),
}
}
// Resized returns the Rect resized to the given size while keeping the position of the given
// anchor.
//
// r.Resized(r.Min, size) // resizes while keeping the position of the lower-left corner
// r.Resized(r.Max, size) // same with the top-right corner
// r.Resized(r.Center(), size) // resizes around the center
//
// This function does not make sense for resizing a rectangle of zero area and will panic. Use
// ResizedMin in the case of zero area.
func (r Rect) Resized(anchor, size Vec) Rect {
if r.W()*r.H() == 0 {
panic(fmt.Errorf("(%T).Resize: zero area", r))
}
fraction := Vec{size.X / r.W(), size.Y / r.H()}
return Rect{
Min: anchor.Add(r.Min.Sub(anchor).ScaledXY(fraction)),
Max: anchor.Add(r.Max.Sub(anchor).ScaledXY(fraction)),
}
}
// ResizedMin returns the Rect resized to the given size while keeping the position of the Rect's
// Min.
//
// Sizes of zero area are safe here.
func (r Rect) ResizedMin(size Vec) Rect {
return Rect{
Min: r.Min,
Max: r.Min.Add(size),
}
}
// Contains checks whether a vector u is contained within this Rect (including it's borders).
func (r Rect) Contains(u Vec) bool {
return r.Min.X <= u.X && u.X <= r.Max.X && r.Min.Y <= u.Y && u.Y <= r.Max.Y
}
// Union returns the minimal Rect which covers both r and s. Rects r and s must be normalized.
func (r Rect) Union(s Rect) Rect {
return R(
math.Min(r.Min.X, s.Min.X),
math.Min(r.Min.Y, s.Min.Y),
math.Max(r.Max.X, s.Max.X),
math.Max(r.Max.Y, s.Max.Y),
)
}
// 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).
func (r Rect) Intersect(s Rect) Rect {
t := R(
math.Max(r.Min.X, s.Min.X),
math.Max(r.Min.Y, s.Min.Y),
math.Min(r.Max.X, s.Max.X),
math.Min(r.Max.Y, s.Max.Y),
)
if t.Min.X >= t.Max.X || t.Min.Y >= t.Max.Y {
return Rect{}
}
return t
}
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
// as movement, scaling and rotations.
//
// Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For
// example:
//
// pixel.IM.Moved(pixel.V(100, 200)).Rotated(pixel.ZV, math.Pi/2)
//
// This code creates a Matrix that first moves everything by 100 units horizontally and 200 units
// vertically and then rotates everything by 90 degrees around the origin.
//
// Layout is:
// [0] [2] [4]
// [1] [3] [5]
// 0 0 1 (implicit row)
type Matrix [6]float64
// IM stands for identity matrix. Does nothing, no transformation.
var IM = Matrix{1, 0, 0, 1, 0, 0}
// String returns a string representation of the Matrix.
//
// m := pixel.IM
// fmt.Println(m) // Matrix(1 0 0 | 0 1 0)
func (m Matrix) String() string {
return fmt.Sprintf(
"Matrix(%v %v %v | %v %v %v)",
m[0], m[2], m[4],
m[1], m[3], m[5],
)
}
// Moved moves everything by the delta vector.
func (m Matrix) Moved(delta Vec) Matrix {
m[4], m[5] = m[4]+delta.X, m[5]+delta.Y
return m
}
// ScaledXY scales everything around a given point by the scale factor in each axis respectively.
func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
m[4], m[5] = m[4]-around.X, m[5]-around.Y
m[0], m[2], m[4] = m[0]*scale.X, m[2]*scale.X, m[4]*scale.X
m[1], m[3], m[5] = m[1]*scale.Y, m[3]*scale.Y, m[5]*scale.Y
m[4], m[5] = m[4]+around.X, m[5]+around.Y
return m
}
// Scaled scales everything around a given point by the scale factor.
func (m Matrix) Scaled(around Vec, scale float64) Matrix {
return m.ScaledXY(around, V(scale, scale))
}
// Rotated rotates everything around a given point by the given angle in radians.
func (m Matrix) Rotated(around Vec, angle float64) Matrix {
sint, cost := math.Sincos(angle)
m[4], m[5] = m[4]-around.X, m[5]-around.Y
m = m.Chained(Matrix{cost, sint, -sint, cost, 0, 0})
m[4], m[5] = m[4]+around.X, m[5]+around.Y
return m
}
// Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied
// after the transformations of this Matrix.
func (m Matrix) Chained(next Matrix) Matrix {
return Matrix{
next[0]*m[0] + next[2]*m[1],
next[1]*m[0] + next[3]*m[1],
next[0]*m[2] + next[2]*m[3],
next[1]*m[2] + next[3]*m[3],
next[0]*m[4] + next[2]*m[5] + next[4],
next[1]*m[4] + next[3]*m[5] + next[5],
}
}
// Project applies all transformations added to the Matrix to a vector u and returns the result.
//
// Time complexity is O(1).
func (m Matrix) Project(u Vec) Vec {
return Vec{m[0]*u.X + m[2]*u.Y + m[4], m[1]*u.X + m[3]*u.Y + m[5]}
}
// 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]}
}

View File

@ -1,79 +0,0 @@
package pixel_test
import (
"fmt"
"testing"
"github.com/faiface/pixel"
)
type rectTestTransform struct {
name string
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
}{
{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)
}
})
}
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module github.com/faiface/pixel
go 1.12
require (
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be
github.com/go-gl/mathgl v1.0.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0
golang.org/x/image v0.5.0
)

54
go.sum Normal file
View File

@ -0,0 +1,54 @@
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-20211013000516-57b20770c369 h1:gv4BgP50atccdK/1tZHDyP6rMwiiutR2HPreR/OyLzI=
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369/go.mod h1:dDdUO+G9ZnJ9sc8nIUvhLkE45k8PEKW6+A3TdWsfpV0=
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-20210905235341-f7a045908259 h1:8q7+xl2D2qHPLTII1t4vSMNP2VKwDcn+Avf2WXvdB1A=
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be h1:UVW91pfMB1GRQfVwC7//RGVbqX6Ea8jURmJhlANak1M=
github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
github.com/go-gl/mathgl v1.0.0/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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -128,7 +128,9 @@ func (imd *IMDraw) Draw(t pixel.Target) {
// Push adds some points to the IM queue. All Pushed points will have the same properties except for
// the position.
func (imd *IMDraw) Push(pts ...pixel.Vec) {
// Assert that Color is of type pixel.RGBA,
if _, ok := imd.Color.(pixel.RGBA); !ok {
// otherwise cast it
imd.Color = pixel.ToRGBA(imd.Color)
}
opts := point{

View File

@ -92,7 +92,7 @@ type TrianglesColor interface {
Color(i int) RGBA
}
// TrianglesPicture specifies Triangles with Picture propery.
// TrianglesPicture specifies Triangles with Picture property.
//
// The first value returned from Picture method is Picture coordinates. The second one specifies the
// weight of the Picture. Value of 0 means, that Picture should be completely ignored, 1 means that
@ -102,10 +102,19 @@ type TrianglesPicture interface {
Picture(i int) (pic Vec, intensity float64)
}
// TrianglesClipped specifies Triangles with Clipping Rectangle property.
//
// The first value returned from ClipRect method is the clipping rectangle. The second one specifies
// if the triangle is clipped.
type TrianglesClipped interface {
Triangles
ClipRect(i int) (rect Rect, is bool)
}
// Picture represents a rectangular area of raster data, such as a color. It has Bounds which
// specify the rectangle where data is located.
type Picture interface {
// Bounds returns the rectangle of the Picture. All data is located witih this rectangle.
// Bounds returns the rectangle of the Picture. All data is located within this rectangle.
// Querying properties outside the rectangle should return default value of that property.
Bounds() Rect
}

699
line_test.go Normal file
View File

@ -0,0 +1,699 @@
package pixel_test
import (
"math"
"reflect"
"testing"
"github.com/faiface/pixel"
)
func TestLine_Bounds(t *testing.T) {
type fields struct {
A pixel.Vec
B pixel.Vec
}
tests := []struct {
name string
fields fields
want pixel.Rect
}{
{
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) {
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
}{
{
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) {
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
}{
{
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 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 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)},
args: args{v: pixel.V(20, 20)},
want: pixel.V(10, 10),
},
{
name: "Vertical line",
fields: fields{A: pixel.V(0, -10), B: pixel.V(0, 10)},
args: args{v: pixel.V(-1, 0)},
want: pixel.V(0, 0),
},
{
name: "Horizontal line",
fields: fields{A: pixel.V(-10, 0), B: pixel.V(10, 0)},
args: args{v: pixel.V(0, -1)},
want: pixel.V(0, 0),
},
}
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
}{
{
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 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)},
args: args{v: pixel.V(0, 10)},
want: false,
},
}
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
}{
{
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) {
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 {
if math.IsNaN(tt.wantB) && !math.IsNaN(gotB) {
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
}{
{
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 intersect 2",
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",
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)},
args: args{k: pixel.L(pixel.V(0, 10), pixel.V(1, 20))},
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)},
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 {
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
}{
{
name: "Cirle intersects",
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
args: args{c: pixel.C(pixel.V(6, 4), 2)},
want: pixel.V(0.5857864376269049, -0.5857864376269049),
},
{
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) {
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
}{
{
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(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,
},
{
name: "Line intersects at 0,0",
fields: fields{A: pixel.V(0, -10), B: pixel.V(0, 10)},
args: args{r: pixel.R(-1, 0, 2, 2)},
want: pixel.V(-1, 0),
},
}
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
}{
{
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) {
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) {
// 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
}
type args struct {
around pixel.Vec
angle float64
}
tests := []struct {
name string
fields fields
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: 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: 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: 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) {
l := pixel.Line{
A: tt.fields.A,
B: tt.fields.B,
}
// 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 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)
}
})
}
}
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
}{
{
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) {
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
}{
{
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) {
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
}{
{
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) {
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)
}
})
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

15
math.go Normal file
View File

@ -0,0 +1,15 @@
package pixel
// Clamp returns x clamped to the interval [min, max].
//
// If x is less than min, min is returned. If x is more than max, max is returned. Otherwise, x is
// returned.
func Clamp(x, min, max float64) float64 {
if x < min {
return min
}
if x > max {
return max
}
return x
}

46
math_test.go Normal file
View File

@ -0,0 +1,46 @@
package pixel_test
import (
"fmt"
"math"
"testing"
"github.com/faiface/pixel"
)
// 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)
}
type clampTest struct {
number float64
min float64
max float64
expected float64
}
func TestClamp(t *testing.T) {
tests := []clampTest{
{number: 1, min: 0, max: 5, expected: 1},
{number: 2, min: 0, max: 5, expected: 2},
{number: 8, min: 0, max: 5, expected: 5},
{number: -5, min: 0, max: 5, expected: 0},
{number: -5, min: -4, max: 5, expected: -4},
}
for _, tc := range tests {
result := pixel.Clamp(tc.number, tc.min, tc.max)
if result != tc.expected {
t.Error(fmt.Sprintf("Clamping %v with min %v and max %v should have given %v, but gave %v", tc.number, tc.min, tc.max, tc.expected, result))
}
}
}

98
matrix.go Normal file
View File

@ -0,0 +1,98 @@
package pixel
import (
"fmt"
"math"
)
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
// as movement, scaling and rotations.
//
// Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For
// example:
//
// pixel.IM.Moved(pixel.V(100, 200)).Rotated(pixel.ZV, math.Pi/2)
//
// This code creates a Matrix that first moves everything by 100 units horizontally and 200 units
// vertically and then rotates everything by 90 degrees around the origin.
//
// Layout is:
// [0] [2] [4]
// [1] [3] [5]
// 0 0 1 (implicit row)
type Matrix [6]float64
// IM stands for identity matrix. Does nothing, no transformation.
var IM = Matrix{1, 0, 0, 1, 0, 0}
// String returns a string representation of the Matrix.
//
// m := pixel.IM
// fmt.Println(m) // Matrix(1 0 0 | 0 1 0)
func (m Matrix) String() string {
return fmt.Sprintf(
"Matrix(%v %v %v | %v %v %v)",
m[0], m[2], m[4],
m[1], m[3], m[5],
)
}
// Moved moves everything by the delta vector.
func (m Matrix) Moved(delta Vec) Matrix {
m[4], m[5] = m[4]+delta.X, m[5]+delta.Y
return m
}
// ScaledXY scales everything around a given point by the scale factor in each axis respectively.
func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
m[4], m[5] = m[4]-around.X, m[5]-around.Y
m[0], m[2], m[4] = m[0]*scale.X, m[2]*scale.X, m[4]*scale.X
m[1], m[3], m[5] = m[1]*scale.Y, m[3]*scale.Y, m[5]*scale.Y
m[4], m[5] = m[4]+around.X, m[5]+around.Y
return m
}
// Scaled scales everything around a given point by the scale factor.
func (m Matrix) Scaled(around Vec, scale float64) Matrix {
return m.ScaledXY(around, V(scale, scale))
}
// Rotated rotates everything around a given point by the given angle in radians.
func (m Matrix) Rotated(around Vec, angle float64) Matrix {
sint, cost := math.Sincos(angle)
m[4], m[5] = m[4]-around.X, m[5]-around.Y
m = m.Chained(Matrix{cost, sint, -sint, cost, 0, 0})
m[4], m[5] = m[4]+around.X, m[5]+around.Y
return m
}
// Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied
// after the transformations of this Matrix.
func (m Matrix) Chained(next Matrix) Matrix {
return Matrix{
next[0]*m[0] + next[2]*m[1],
next[1]*m[0] + next[3]*m[1],
next[0]*m[2] + next[2]*m[3],
next[1]*m[2] + next[3]*m[3],
next[0]*m[4] + next[2]*m[5] + next[4],
next[1]*m[4] + next[3]*m[5] + next[5],
}
}
// Project applies all transformations added to the Matrix to a vector u and returns the result.
//
// Time complexity is O(1).
func (m Matrix) Project(u Vec) Vec {
return Vec{m[0]*u.X + m[2]*u.Y + m[4], m[1]*u.X + m[3]*u.Y + m[5]}
}
// Unproject does the inverse operation to Project.
//
// Time complexity is O(1).
func (m Matrix) Unproject(u Vec) Vec {
det := m[0]*m[3] - m[2]*m[1]
return Vec{
(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,
}
}

View File

@ -1,10 +1,13 @@
package pixel_test
import (
"fmt"
"math"
"math/rand"
"testing"
"github.com/faiface/pixel"
"github.com/stretchr/testify/assert"
)
func BenchmarkMatrix(b *testing.B) {
@ -61,3 +64,86 @@ func BenchmarkMatrix(b *testing.B) {
}
})
}
func TestMatrix_Unproject(t *testing.T) {
const delta = 1e-15
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, delta)
assert.InDelta(t, unprojected.Y, 0, delta)
})
t.Run("for moved matrix", func(t *testing.T) {
matrix := pixel.IM.
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(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) {
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))
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) {
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),
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 matrixName, matrix := range namedMatrices {
for _, vertex := range vertices {
testCase := fmt.Sprintf("for matrix %s and vertex %v", matrixName, 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)
assert.True(t, math.IsNaN(unprojected.X))
assert.True(t, math.IsNaN(unprojected.Y))
})
}

68
pixel_test.go Normal file
View File

@ -0,0 +1,68 @@
package pixel_test
import (
"bytes"
"image"
"os"
"testing"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)
// onePixelImage is the byte representation of a 1x1 solid white png file
var 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),
Invisible: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
t.Fatalf("Could not create window: %v", err)
}
sprite.Draw(win, pixel.IM)
}

View File

@ -17,7 +17,7 @@ import (
// It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor.
type Canvas struct {
gf *GLFrame
shader *glShader
shader *GLShader
cmp pixel.ComposeMethod
mat mgl32.Mat3
@ -37,9 +37,8 @@ func NewCanvas(bounds pixel.Rect) *Canvas {
col: mgl32.Vec4{1, 1, 1, 1},
}
baseShader(c)
c.shader = NewGLShader(baseCanvasFragmentShader)
c.SetBounds(bounds)
c.shader.update()
return c
}
@ -47,22 +46,28 @@ func NewCanvas(bounds pixel.Rect) *Canvas {
// attribute variable. If the uniform already exists, including defaults, they will be reassigned
// to the new value. The value can be a pointer.
func (c *Canvas) SetUniform(name string, value interface{}) {
c.shader.setUniform(name, value)
c.shader.SetUniform(name, value)
}
// SetFragmentShader allows you to set a new fragment shader on the underlying
// framebuffer. Argument "src" is the GLSL source, not a filename.
func (c *Canvas) SetFragmentShader(src string) {
c.shader.fs = src
c.shader.update()
c.shader.Update()
}
// MakeTriangles creates a specialized copy of the supplied Triangles that draws onto this Canvas.
//
// TrianglesPosition, TrianglesColor and TrianglesPicture are supported.
func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
if gt, ok := t.(*GLTriangles); ok {
return &canvasTriangles{
GLTriangles: NewGLTriangles(c.shader.s, t),
GLTriangles: gt,
dst: c,
}
}
return &canvasTriangles{
GLTriangles: NewGLTriangles(c.shader, t),
dst: c,
}
}
@ -288,15 +293,15 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
setBlendFunc(cmp)
frame := ct.dst.gf.Frame()
shader := ct.dst.shader.s
shader := ct.shader.s
frame.Begin()
shader.Begin()
ct.dst.shader.uniformDefaults.transform = mat
ct.dst.shader.uniformDefaults.colormask = col
ct.shader.uniformDefaults.transform = mat
ct.shader.uniformDefaults.colormask = col
dstBounds := ct.dst.Bounds()
ct.dst.shader.uniformDefaults.bounds = mgl32.Vec4{
ct.shader.uniformDefaults.bounds = mgl32.Vec4{
float32(dstBounds.Min.X),
float32(dstBounds.Min.Y),
float32(dstBounds.W()),
@ -304,15 +309,15 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
}
bx, by, bw, bh := intBounds(bounds)
ct.dst.shader.uniformDefaults.texbounds = mgl32.Vec4{
ct.shader.uniformDefaults.texbounds = mgl32.Vec4{
float32(bx),
float32(by),
float32(bw),
float32(bh),
}
for loc, u := range ct.dst.shader.uniforms {
ct.dst.shader.s.SetUniformAttr(loc, u.Value())
for loc, u := range ct.shader.uniforms {
ct.shader.s.SetUniformAttr(loc, u.Value())
}
if tex == nil {
@ -354,17 +359,3 @@ func (cp *canvasPicture) Draw(t pixel.TargetTriangles) {
}
ct.draw(cp.GLPicture.Texture(), cp.GLPicture.Bounds())
}
const (
canvasPosition int = iota
canvasColor
canvasTexCoords
canvasIntensity
)
var defaultCanvasVertexFormat = glhf.AttrFormat{
canvasPosition: {Name: "aPosition", Type: glhf.Vec2},
canvasColor: {Name: "aColor", Type: glhf.Vec4},
canvasTexCoords: {Name: "aTexCoords", Type: glhf.Vec2},
canvasIntensity: {Name: "aIntensity", Type: glhf.Float},
}

View File

@ -7,10 +7,10 @@ import (
"github.com/pkg/errors"
)
// glShader is a type to assist with managing a canvas's underlying
// GLShader is a type to assist with managing a canvas's underlying
// shader configuration. This allows for customization of shaders on
// a per canvas basis.
type glShader struct {
type GLShader struct {
s *glhf.Shader
vf, uf glhf.AttrFormat
vs, fs string
@ -22,6 +22,7 @@ type glShader struct {
colormask mgl32.Vec4
bounds mgl32.Vec4
texbounds mgl32.Vec4
cliprect mgl32.Vec4
}
}
@ -32,15 +33,52 @@ type gsUniformAttr struct {
ispointer bool
}
// reinitialize GLShader data and recompile the underlying gl shader object
func (gs *glShader) update() {
gs.uf = nil
for _, u := range gs.uniforms {
gs.uf = append(gs.uf, glhf.Attr{
Name: u.Name,
Type: u.Type,
})
const (
canvasPosition int = iota
canvasColor
canvasTexCoords
canvasIntensity
canvasClip
)
var defaultCanvasVertexFormat = glhf.AttrFormat{
canvasPosition: glhf.Attr{Name: "aPosition", Type: glhf.Vec2},
canvasColor: glhf.Attr{Name: "aColor", Type: glhf.Vec4},
canvasTexCoords: glhf.Attr{Name: "aTexCoords", Type: glhf.Vec2},
canvasIntensity: glhf.Attr{Name: "aIntensity", Type: glhf.Float},
canvasClip: glhf.Attr{Name: "aClipRect", Type: glhf.Vec4},
}
// Sets up a base shader with everything needed for a Pixel
// canvas to render correctly. The defaults can be overridden
// by simply using the SetUniform function.
func NewGLShader(fragmentShader string) *GLShader {
gs := &GLShader{
vf: defaultCanvasVertexFormat,
vs: baseCanvasVertexShader,
fs: fragmentShader,
}
gs.SetUniform("uTransform", &gs.uniformDefaults.transform)
gs.SetUniform("uColorMask", &gs.uniformDefaults.colormask)
gs.SetUniform("uBounds", &gs.uniformDefaults.bounds)
gs.SetUniform("uTexBounds", &gs.uniformDefaults.texbounds)
gs.Update()
return gs
}
// Update reinitialize GLShader data and recompile the underlying gl shader object
func (gs *GLShader) Update() {
gs.uf = make([]glhf.Attr, len(gs.uniforms))
for idx := range gs.uniforms {
gs.uf[idx] = glhf.Attr{
Name: gs.uniforms[idx].Name,
Type: gs.uniforms[idx].Type,
}
}
var shader *glhf.Shader
mainthread.Call(func() {
var err error
@ -59,7 +97,7 @@ func (gs *glShader) update() {
}
// gets the uniform index from GLShader
func (gs *glShader) getUniform(Name string) int {
func (gs *GLShader) getUniform(Name string) int {
for i, u := range gs.uniforms {
if u.Name == Name {
return i
@ -75,7 +113,7 @@ func (gs *glShader) getUniform(Name string) int {
//
// utime := float32(time.Since(starttime)).Seconds())
// mycanvas.shader.AddUniform("u_time", &utime)
func (gs *glShader) setUniform(name string, value interface{}) {
func (gs *GLShader) SetUniform(name string, value interface{}) {
t, p := getAttrType(value)
if loc := gs.getUniform(name); loc > -1 {
gs.uniforms[loc].Name = name
@ -92,24 +130,6 @@ func (gs *glShader) setUniform(name string, value interface{}) {
})
}
// Sets up a base shader with everything needed for a Pixel
// canvas to render correctly. The defaults can be overridden
// by simply using the SetUniform function.
func baseShader(c *Canvas) {
gs := &glShader{
vf: defaultCanvasVertexFormat,
vs: baseCanvasVertexShader,
fs: baseCanvasFragmentShader,
}
gs.setUniform("uTransform", &gs.uniformDefaults.transform)
gs.setUniform("uColorMask", &gs.uniformDefaults.colormask)
gs.setUniform("uBounds", &gs.uniformDefaults.bounds)
gs.setUniform("uTexBounds", &gs.uniformDefaults.texbounds)
c.shader = gs
}
// Value returns the attribute's concrete value. If the stored value
// is a pointer, we return the dereferenced value.
func (gu *gsUniformAttr) Value() interface{} {
@ -222,10 +242,14 @@ in vec2 aPosition;
in vec4 aColor;
in vec2 aTexCoords;
in float aIntensity;
in vec4 aClipRect;
in float aIsClipped;
out vec4 vColor;
out vec2 vTexCoords;
out float vIntensity;
out vec2 vPosition;
out vec4 vClipRect;
uniform mat3 uTransform;
uniform vec4 uBounds;
@ -234,9 +258,12 @@ void main() {
vec2 transPos = (uTransform * vec3(aPosition, 1.0)).xy;
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;
vClipRect = aClipRect;
}
`
@ -246,6 +273,7 @@ var baseCanvasFragmentShader = `
in vec4 vColor;
in vec2 vTexCoords;
in float vIntensity;
in vec4 vClipRect;
out vec4 fragColor;
@ -254,6 +282,9 @@ uniform vec4 uTexBounds;
uniform sampler2D uTexture;
void main() {
if ((vClipRect != vec4(0,0,0,0)) && (gl_FragCoord.x < vClipRect.x || gl_FragCoord.y < vClipRect.y || gl_FragCoord.x > vClipRect.z || gl_FragCoord.y > vClipRect.w))
discard;
if (vIntensity == 0) {
fragColor = uColorMask * vColor;
} else {

View File

@ -15,23 +15,43 @@ import (
type GLTriangles struct {
vs *glhf.VertexSlice
data []float32
shader *glhf.Shader
shader *GLShader
}
var (
_ pixel.TrianglesPosition = (*GLTriangles)(nil)
_ pixel.TrianglesColor = (*GLTriangles)(nil)
_ pixel.TrianglesPicture = (*GLTriangles)(nil)
_ pixel.TrianglesClipped = (*GLTriangles)(nil)
)
// The following is a helper so that the indices of
// each of these items is easier to see/debug.
const (
triPosX = iota
triPosY
triColorR
triColorG
triColorB
triColorA
triPicX
triPicY
triIntensity
triClipMinX
triClipMinY
triClipMaxX
triClipMaxY
trisAttrLen
)
// NewGLTriangles returns GLTriangles initialized with the data from the supplied Triangles.
//
// Only draw the Triangles using the provided Shader.
func NewGLTriangles(shader *glhf.Shader, t pixel.Triangles) *GLTriangles {
func NewGLTriangles(shader *GLShader, t pixel.Triangles) *GLTriangles {
var gt *GLTriangles
mainthread.Call(func() {
gt = &GLTriangles{
vs: glhf.MakeVertexSlice(shader, 0, t.Len()),
vs: glhf.MakeVertexSlice(shader.s, 0, t.Len()),
shader: shader,
}
})
@ -48,7 +68,7 @@ func (gt *GLTriangles) VertexSlice() *glhf.VertexSlice {
}
// Shader returns the GLTriangles's associated shader.
func (gt *GLTriangles) Shader() *glhf.Shader {
func (gt *GLTriangles) Shader() *GLShader {
return gt.shader
}
@ -70,6 +90,7 @@ func (gt *GLTriangles) SetLen(length int) {
1, 1, 1, 1,
0, 0,
0,
0, 0, 0, 0,
)
}
case length < gt.Len():
@ -77,7 +98,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()
@ -110,17 +131,22 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
col = (*t)[i].Color
tx, ty = (*t)[i].Picture.XY()
in = (*t)[i].Intensity
rec = (*t)[i].ClipRect
)
d := gt.data[i*stride : i*stride+9]
d[0] = float32(px)
d[1] = float32(py)
d[2] = float32(col.R)
d[3] = float32(col.G)
d[4] = float32(col.B)
d[5] = float32(col.A)
d[6] = float32(tx)
d[7] = float32(ty)
d[8] = float32(in)
d := gt.data[i*stride : i*stride+trisAttrLen]
d[triPosX] = float32(px)
d[triPosY] = float32(py)
d[triColorR] = float32(col.R)
d[triColorG] = float32(col.G)
d[triColorB] = float32(col.B)
d[triColorA] = float32(col.A)
d[triPicX] = float32(tx)
d[triPicY] = float32(ty)
d[triIntensity] = float32(in)
d[triClipMinX] = float32(rec.Min.X)
d[triClipMinY] = float32(rec.Min.Y)
d[triClipMaxX] = float32(rec.Max.X)
d[triClipMaxY] = float32(rec.Max.Y)
}
return
}
@ -128,25 +154,34 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
if t, ok := t.(pixel.TrianglesPosition); ok {
for i := 0; i < length; i++ {
px, py := t.Position(i).XY()
gt.data[i*stride+0] = float32(px)
gt.data[i*stride+1] = float32(py)
gt.data[i*stride+triPosX] = float32(px)
gt.data[i*stride+triPosY] = float32(py)
}
}
if t, ok := t.(pixel.TrianglesColor); ok {
for i := 0; i < length; i++ {
col := t.Color(i)
gt.data[i*stride+2] = float32(col.R)
gt.data[i*stride+3] = float32(col.G)
gt.data[i*stride+4] = float32(col.B)
gt.data[i*stride+5] = float32(col.A)
gt.data[i*stride+triColorR] = float32(col.R)
gt.data[i*stride+triColorG] = float32(col.G)
gt.data[i*stride+triColorB] = float32(col.B)
gt.data[i*stride+triColorA] = float32(col.A)
}
}
if t, ok := t.(pixel.TrianglesPicture); ok {
for i := 0; i < length; i++ {
pic, intensity := t.Picture(i)
gt.data[i*stride+6] = float32(pic.X)
gt.data[i*stride+7] = float32(pic.Y)
gt.data[i*stride+8] = float32(intensity)
gt.data[i*stride+triPicX] = float32(pic.X)
gt.data[i*stride+triPicY] = float32(pic.Y)
gt.data[i*stride+triIntensity] = float32(intensity)
}
}
if t, ok := t.(pixel.TrianglesClipped); ok {
for i := 0; i < length; i++ {
rect, _ := t.ClipRect(i)
gt.data[i*stride+triClipMinX] = float32(rect.Min.X)
gt.data[i*stride+triClipMinY] = float32(rect.Min.Y)
gt.data[i*stride+triClipMaxX] = float32(rect.Max.X)
gt.data[i*stride+triClipMaxY] = float32(rect.Max.Y)
}
}
}
@ -160,6 +195,12 @@ func (gt *GLTriangles) Update(t pixel.Triangles) {
}
gt.updateData(t)
// Copy the verteces down to the glhf.VertexData
gt.CopyVertices()
}
// CopyVertices copies the GLTriangle data down to the vertex data.
func (gt *GLTriangles) CopyVertices() {
// this code is supposed to copy the vertex data and CallNonBlock the update if
// the data is small enough, otherwise it'll block and not copy the data
if len(gt.data) < 256 { // arbitrary heurestic constant
@ -185,19 +226,31 @@ func (gt *GLTriangles) Copy() pixel.Triangles {
return NewGLTriangles(gt.shader, gt)
}
// index is a helper function that returns the index in the data
// slice given the i-th vertex and the item index.
func (gt *GLTriangles) index(i, idx int) int {
return i*gt.vs.Stride() + idx
}
// Position returns the Position property of the i-th vertex.
func (gt *GLTriangles) Position(i int) pixel.Vec {
px := gt.data[i*gt.vs.Stride()+0]
py := gt.data[i*gt.vs.Stride()+1]
px := gt.data[gt.index(i, triPosX)]
py := gt.data[gt.index(i, triPosY)]
return pixel.V(float64(px), float64(py))
}
// SetPosition sets the position property of the i-th vertex.
func (gt *GLTriangles) SetPosition(i int, p pixel.Vec) {
gt.data[gt.index(i, triPosX)] = float32(p.X)
gt.data[gt.index(i, triPosY)] = float32(p.Y)
}
// Color returns the Color property of the i-th vertex.
func (gt *GLTriangles) Color(i int) pixel.RGBA {
r := gt.data[i*gt.vs.Stride()+2]
g := gt.data[i*gt.vs.Stride()+3]
b := gt.data[i*gt.vs.Stride()+4]
a := gt.data[i*gt.vs.Stride()+5]
r := gt.data[gt.index(i, triColorR)]
g := gt.data[gt.index(i, triColorG)]
b := gt.data[gt.index(i, triColorB)]
a := gt.data[gt.index(i, triColorA)]
return pixel.RGBA{
R: float64(r),
G: float64(g),
@ -206,10 +259,44 @@ func (gt *GLTriangles) Color(i int) pixel.RGBA {
}
}
// SetColor sets the color property of the i-th vertex.
func (gt *GLTriangles) SetColor(i int, c pixel.RGBA) {
gt.data[gt.index(i, triColorR)] = float32(c.R)
gt.data[gt.index(i, triColorG)] = float32(c.G)
gt.data[gt.index(i, triColorB)] = float32(c.B)
gt.data[gt.index(i, triColorA)] = float32(c.A)
}
// Picture returns the Picture property of the i-th vertex.
func (gt *GLTriangles) Picture(i int) (pic pixel.Vec, intensity float64) {
tx := gt.data[i*gt.vs.Stride()+6]
ty := gt.data[i*gt.vs.Stride()+7]
intensity = float64(gt.data[i*gt.vs.Stride()+8])
tx := gt.data[gt.index(i, triPicX)]
ty := gt.data[gt.index(i, triPicY)]
intensity = float64(gt.data[gt.index(i, triIntensity)])
return pixel.V(float64(tx), float64(ty)), intensity
}
// SetPicture sets the picture property of the i-th vertex.
func (gt *GLTriangles) SetPicture(i int, pic pixel.Vec, intensity float64) {
gt.data[gt.index(i, triPicX)] = float32(pic.X)
gt.data[gt.index(i, triPicY)] = float32(pic.Y)
gt.data[gt.index(i, triIntensity)] = float32(intensity)
}
// ClipRect returns the Clipping rectangle property of the i-th vertex.
func (gt *GLTriangles) ClipRect(i int) (rect pixel.Rect, is bool) {
mx := gt.data[gt.index(i, triClipMinX)]
my := gt.data[gt.index(i, triClipMinY)]
ax := gt.data[gt.index(i, triClipMaxX)]
ay := gt.data[gt.index(i, triClipMaxY)]
rect = pixel.R(float64(mx), float64(my), float64(ax), float64(ay))
is = rect.Area() != 0.0
return
}
// SetClipRect sets the Clipping rectangle property of the i-th vertex.
func (gt *GLTriangles) SetClipRect(i int, rect pixel.Rect) {
gt.data[gt.index(i, triClipMinX)] = float32(rect.Min.X)
gt.data[gt.index(i, triClipMinY)] = float32(rect.Min.Y)
gt.data[gt.index(i, triClipMaxX)] = float32(rect.Max.X)
gt.data[gt.index(i, triClipMaxY)] = float32(rect.Max.Y)
}

View File

@ -1,9 +1,11 @@
package pixelgl
import (
"time"
"github.com/faiface/mainthread"
"github.com/faiface/pixel"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/go-gl/glfw/v3.3/glfw"
)
// Pressed returns whether the Button is currently pressed down.
@ -11,14 +13,14 @@ func (w *Window) Pressed(button Button) bool {
return w.currInp.buttons[button]
}
// JustPressed returns whether the Button has just been pressed down.
// JustPressed returns whether the Button has been pressed in the last frame.
func (w *Window) JustPressed(button Button) bool {
return w.currInp.buttons[button] && !w.prevInp.buttons[button]
return w.pressEvents[button]
}
// JustReleased returns whether the Button has just been released up.
// JustReleased returns whether the Button has been released in the last frame.
func (w *Window) JustReleased(button Button) bool {
return !w.currInp.buttons[button] && w.prevInp.buttons[button]
return w.releaseEvents[button]
}
// Repeated returns whether a repeat event has been triggered on button.
@ -33,6 +35,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
}
})
}
// MouseInsideWindow returns true if the mouse position is within the Window's Bounds.
func (w *Window) MouseInsideWindow() bool {
return w.cursorInsideWindow
}
// 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
@ -334,8 +362,10 @@ func (w *Window) initInput() {
w.window.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
switch action {
case glfw.Press:
w.tempPressEvents[Button(button)] = true
w.tempInp.buttons[Button(button)] = true
case glfw.Release:
w.tempReleaseEvents[Button(button)] = true
w.tempInp.buttons[Button(button)] = false
}
})
@ -346,14 +376,20 @@ func (w *Window) initInput() {
}
switch action {
case glfw.Press:
w.tempPressEvents[Button(key)] = true
w.tempInp.buttons[Button(key)] = true
case glfw.Release:
w.tempReleaseEvents[Button(key)] = true
w.tempInp.buttons[Button(key)] = false
case glfw.Repeat:
w.tempInp.repeat[Button(key)] = true
}
})
w.window.SetCursorEnterCallback(func(_ *glfw.Window, entered bool) {
w.cursorInsideWindow = entered
})
w.window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) {
w.tempInp.mouse = pixel.V(
x+w.bounds.Min.X,
@ -378,11 +414,36 @@ func (w *Window) UpdateInput() {
mainthread.Call(func() {
glfw.PollEvents()
})
w.doUpdateInput()
}
// UpdateInputWait blocks until an event is received or a timeout. If timeout is 0
// then it will wait indefinitely
func (w *Window) UpdateInputWait(timeout time.Duration) {
mainthread.Call(func() {
if timeout <= 0 {
glfw.WaitEvents()
} else {
glfw.WaitEventsTimeout(timeout.Seconds())
}
})
w.doUpdateInput()
}
// internal input bookkeeping
func (w *Window) doUpdateInput() {
w.prevInp = w.currInp
w.currInp = w.tempInp
w.pressEvents = w.tempPressEvents
w.releaseEvents = w.tempReleaseEvents
// Clear last frame's temporary status
w.tempPressEvents = [KeyLast + 1]bool{}
w.tempReleaseEvents = [KeyLast + 1]bool{}
w.tempInp.repeat = [KeyLast + 1]bool{}
w.tempInp.scroll = pixel.ZV
w.tempInp.typed = ""
w.updateJoystickInput()
}

193
pixelgl/joystick.go Normal file
View File

@ -0,0 +1,193 @@
package pixelgl
import (
"github.com/go-gl/glfw/v3.3/glfw"
)
// Joystick is a joystick or controller (gamepad).
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)
)
// GamepadAxis corresponds to a gamepad axis.
type GamepadAxis int
// Gamepad axis IDs.
const (
AxisLeftX = GamepadAxis(glfw.AxisLeftX)
AxisLeftY = GamepadAxis(glfw.AxisLeftY)
AxisRightX = GamepadAxis(glfw.AxisRightX)
AxisRightY = GamepadAxis(glfw.AxisRightY)
AxisLeftTrigger = GamepadAxis(glfw.AxisLeftTrigger)
AxisRightTrigger = GamepadAxis(glfw.AxisRightTrigger)
AxisLast = GamepadAxis(glfw.AxisLast)
)
// GamepadButton corresponds to a gamepad button.
type GamepadButton int
// Gamepad button IDs.
const (
ButtonA = GamepadButton(glfw.ButtonA)
ButtonB = GamepadButton(glfw.ButtonB)
ButtonX = GamepadButton(glfw.ButtonX)
ButtonY = GamepadButton(glfw.ButtonY)
ButtonLeftBumper = GamepadButton(glfw.ButtonLeftBumper)
ButtonRightBumper = GamepadButton(glfw.ButtonRightBumper)
ButtonBack = GamepadButton(glfw.ButtonBack)
ButtonStart = GamepadButton(glfw.ButtonStart)
ButtonGuide = GamepadButton(glfw.ButtonGuide)
ButtonLeftThumb = GamepadButton(glfw.ButtonLeftThumb)
ButtonRightThumb = GamepadButton(glfw.ButtonRightThumb)
ButtonDpadUp = GamepadButton(glfw.ButtonDpadUp)
ButtonDpadRight = GamepadButton(glfw.ButtonDpadRight)
ButtonDpadDown = GamepadButton(glfw.ButtonDpadDown)
ButtonDpadLeft = GamepadButton(glfw.ButtonDpadLeft)
ButtonLast = GamepadButton(glfw.ButtonLast)
ButtonCross = GamepadButton(glfw.ButtonCross)
ButtonCircle = GamepadButton(glfw.ButtonCircle)
ButtonSquare = GamepadButton(glfw.ButtonSquare)
ButtonTriangle = GamepadButton(glfw.ButtonTriangle)
)
// 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 GamepadButton) bool {
return w.currJoy.getButton(js, int(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 GamepadButton) bool {
return w.currJoy.getButton(js, int(button)) && !w.prevJoy.getButton(js, int(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 GamepadButton) bool {
return !w.currJoy.getButton(js, int(button)) && w.prevJoy.getButton(js, int(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 GamepadAxis) float64 {
return w.currJoy.getAxis(js, int(axis))
}
// Used internally during Window.UpdateInput to update the state of the joysticks.
func (w *Window) updateJoystickInput() {
for js := Joystick1; js <= JoystickLast; js++ {
// Determine and store if the joystick was connected
joystickPresent := glfw.Joystick(js).Present()
w.tempJoy.connected[js] = joystickPresent
if joystickPresent {
if glfw.Joystick(js).IsGamepad() {
gamepadInputs := glfw.Joystick(js).GetGamepadState()
w.tempJoy.buttons[js] = gamepadInputs.Buttons[:]
w.tempJoy.axis[js] = gamepadInputs.Axes[:]
} else {
w.tempJoy.buttons[js] = glfw.Joystick(js).GetButtons()
w.tempJoy.axis[js] = glfw.Joystick(js).GetAxes()
}
if !w.currJoy.connected[js] {
// The joystick was recently connected, we get the name
w.tempJoy.name[js] = glfw.Joystick(js).GetName()
} else {
// Use the name from the previous one
w.tempJoy.name[js] = w.currJoy.name[js]
}
} else {
w.tempJoy.buttons[js] = []glfw.Action{}
w.tempJoy.axis[js] = []float32{}
w.tempJoy.name[js] = ""
}
}
w.prevJoy = w.currJoy
w.currJoy = w.tempJoy
}
type joystickState struct {
connected [JoystickLast + 1]bool
name [JoystickLast + 1]string
buttons [JoystickLast + 1][]glfw.Action
axis [JoystickLast + 1][]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)] == glfw.Press
}
// 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])
}

View File

@ -2,7 +2,7 @@ package pixelgl
import (
"github.com/faiface/mainthread"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/go-gl/glfw/v3.3/glfw"
)
// Monitor represents a physical display attached to your computer.

View File

@ -2,7 +2,7 @@ package pixelgl
import (
"github.com/faiface/mainthread"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/pkg/errors"
)

View File

@ -1,6 +1,7 @@
package pixelgl
import (
"fmt"
"image"
"image/color"
"runtime"
@ -8,7 +9,8 @@ import (
"github.com/faiface/glhf"
"github.com/faiface/mainthread"
"github.com/faiface/pixel"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/go-gl/gl/v3.3-core/gl"
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/pkg/errors"
)
@ -36,19 +38,48 @@ type WindowConfig struct {
// Bounds specify the bounds of the Window in pixels.
Bounds pixel.Rect
// Initial window position
Position pixel.Vec
// If set to nil, the Window will be windowed. Otherwise it will be fullscreen on the
// specified Monitor.
Monitor *Monitor
// Whether the Window is resizable.
// Resizable specifies whether the window will be resizable by the user.
Resizable bool
// Undecorated Window ommits the borders and decorations (close button, etc.).
// Undecorated Window omits 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
// TransparentFramebuffer specifies whether the window framebuffer will be
// transparent. If enabled and supported by the system, the window
// framebuffer alpha channel will be used to combine the framebuffer with
// the background. This does not affect window decorations.
TransparentFramebuffer bool
// VSync (vertical synchronization) synchronizes Window's framerate with the framerate of
// the monitor.
VSync bool
// Maximized specifies whether the window is maximized.
Maximized bool
// Invisible specifies whether the window will be initially hidden.
// You can make the window visible later using Window.Show().
Invisible bool
//SamplesMSAA specifies the level of MSAA to be used. Must be one of 0, 2, 4, 8, 16. 0 to disable.
SamplesMSAA int
}
// Window is a window handler. Use this type to manipulate a window (input, drawing, etc.).
@ -59,6 +90,7 @@ type Window struct {
canvas *Canvas
vsync bool
cursorVisible bool
cursorInsideWindow bool
// need to save these to correctly restore a fullscreen window
restore struct {
@ -72,6 +104,11 @@ type Window struct {
scroll pixel.Vec
typed string
}
pressEvents, tempPressEvents [KeyLast + 1]bool
releaseEvents, tempReleaseEvents [KeyLast + 1]bool
prevJoy, currJoy, tempJoy joystickState
}
var currWin *Window
@ -87,6 +124,17 @@ func NewWindow(cfg WindowConfig) (*Window, error) {
w := &Window{bounds: cfg.Bounds, cursorVisible: true}
flag := false
for _, v := range []int{0, 2, 4, 8, 16} {
if cfg.SamplesMSAA == v {
flag = true
break
}
}
if !flag {
return nil, fmt.Errorf("invalid value '%v' for msaaSamples", cfg.SamplesMSAA)
}
err := mainthread.CallErr(func() error {
var err error
@ -97,6 +145,16 @@ 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])
glfw.WindowHint(glfw.TransparentFramebuffer, bool2int[cfg.TransparentFramebuffer])
glfw.WindowHint(glfw.Maximized, bool2int[cfg.Maximized])
glfw.WindowHint(glfw.Visible, bool2int[!cfg.Invisible])
glfw.WindowHint(glfw.Samples, cfg.SamplesMSAA)
if cfg.Position.X != 0 || cfg.Position.Y != 0 {
glfw.WindowHint(glfw.Visible, glfw.False)
}
var share *glfw.Window
if currWin != nil {
@ -114,9 +172,15 @@ func NewWindow(cfg WindowConfig) (*Window, error) {
return err
}
if cfg.Position.X != 0 || cfg.Position.Y != 0 {
w.window.SetPos(int(cfg.Position.X), int(cfg.Position.Y))
w.window.Show()
}
// enter the OpenGL context
w.begin()
glhf.Init()
gl.Enable(gl.MULTISAMPLE)
w.end()
return nil
@ -158,6 +222,24 @@ 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()
}
// ClipboardText returns the current value of the systems clipboard.
func (w *Window) ClipboardText() string {
return w.window.GetClipboardString()
}
// SetClipboardText passes the given string to the underlying glfw window to set the
// systems clipboard.
func (w *Window) SetClipboardText(text string) {
w.window.SetClipboardString(text)
}
// 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()
@ -192,8 +274,6 @@ func (w *Window) Update() {
w.window.SwapBuffers()
w.end()
})
w.UpdateInput()
}
// SetClosed sets the closed flag of the Window.
@ -354,6 +434,15 @@ func (w *Window) SetCursorVisible(visible bool) {
})
}
// SetCursorDisabled hides the cursor and provides unlimited virtual cursor movement
// make cursor visible using SetCursorVisible
func (w *Window) SetCursorDisabled() {
w.cursorVisible = false
mainthread.Call(func() {
w.window.SetInputMode(glfw.CursorMode, glfw.CursorDisabled)
})
}
// CursorVisible returns the visibility status of the mouse cursor.
func (w *Window) CursorVisible() bool {
return w.cursorVisible
@ -429,3 +518,27 @@ func (w *Window) Color(at pixel.Vec) pixel.RGBA {
func (w *Window) Canvas() *Canvas {
return w.canvas
}
// Show makes the window visible, if it was previously hidden. If the window is
// already visible or is in full screen mode, this function does nothing.
func (w *Window) Show() {
mainthread.Call(func() {
w.window.Show()
})
}
// Clipboard returns the contents of the system clipboard.
func (w *Window) Clipboard() string {
var clipboard string
mainthread.Call(func() {
clipboard = w.window.GetClipboardString()
})
return clipboard
}
// SetClipboardString sets the system clipboard to the specified UTF-8 encoded string.
func (w *Window) SetClipboard(str string) {
mainthread.Call(func() {
w.window.SetClipboardString(str)
})
}

284
rectangle.go Normal file
View File

@ -0,0 +1,284 @@
package pixel
import (
"fmt"
"math"
)
// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
// points, Min and Max.
//
// The invariant should hold, that Max's components are greater or equal than Min's components
// respectively.
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.
func R(minX, minY, maxX, maxY float64) Rect {
return Rect{
Min: Vec{minX, minY},
Max: Vec{maxX, maxY},
}
}
// String returns the string representation of the Rect.
//
// r := pixel.R(100, 50, 200, 300)
// r.String() // returns "Rect(100, 50, 200, 300)"
// fmt.Println(r) // Rect(100, 50, 200, 300)
func (r Rect) String() string {
return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)
}
// Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
func (r Rect) Norm() Rect {
return Rect{
Min: Vec{
math.Min(r.Min.X, r.Max.X),
math.Min(r.Min.Y, r.Max.Y),
},
Max: Vec{
math.Max(r.Min.X, r.Max.X),
math.Max(r.Min.Y, r.Max.Y),
},
}
}
// W returns the width of the Rect.
func (r Rect) W() float64 {
return r.Max.X - r.Min.X
}
// H returns the height of the Rect.
func (r Rect) H() float64 {
return r.Max.Y - r.Min.Y
}
// Size returns the vector of width and height of the Rect.
func (r Rect) Size() Vec {
return V(r.W(), r.H())
}
// Area returns the area of r. If r is not normalized, area may be negative.
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]},
}
}
// Anchor is a vector used to define anchors, such as `Center`, `Top`, `TopRight`, etc.
type Anchor Vec
var (
Center = Anchor{0.5, 0.5}
Top = Anchor{0.5, 0}
TopRight = Anchor{0, 0}
Right = Anchor{0, 0.5}
BottomRight = Anchor{0, 1}
Bottom = Anchor{0.5, 1}
BottomLeft = Anchor{1, 1}
Left = Anchor{1, 0.5}
TopLeft = Anchor{1, 0}
)
var anchorStrings map[Anchor]string = map[Anchor]string{
Center: "center",
Top: "top",
TopRight: "top-right",
Right: "right",
BottomRight: "bottom-right",
Bottom: "bottom",
BottomLeft: "bottom-left",
Left: "left",
TopLeft: "top-left",
}
// String returns the string representation of an anchor.
func (anchor Anchor) String() string {
return anchorStrings[anchor]
}
var oppositeAnchors map[Anchor]Anchor = map[Anchor]Anchor{
Center: Center,
Top: Bottom,
Bottom: Top,
Right: Left,
Left: Right,
TopRight: BottomLeft,
BottomLeft: TopRight,
BottomRight: TopLeft,
TopLeft: BottomRight,
}
// Opposite returns the opposite position of the anchor (ie. Top -> Bottom; BottomLeft -> TopRight, etc.).
func (anchor Anchor) Opposite() Anchor {
return oppositeAnchors[anchor]
}
// AnchorPos returns the relative position of the given anchor.
func (r Rect) AnchorPos(anchor Anchor) Vec {
return r.Size().ScaledXY(V(0, 0).Sub(Vec(anchor)))
}
// AlignedTo returns the rect moved by the given anchor.
func (rect Rect) AlignedTo(anchor Anchor) Rect {
return rect.Moved(rect.AnchorPos(anchor))
}
// Center returns the position of the center of the Rect.
// `rect.Center()` is equivalent to `rect.Anchor(pixel.Anchor.Center)`
func (r Rect) Center() Vec {
return Lerp(r.Min, r.Max, 0.5)
}
// Moved returns the Rect moved (both Min and Max) by the given vector delta.
func (r Rect) Moved(delta Vec) Rect {
return Rect{
Min: r.Min.Add(delta),
Max: r.Max.Add(delta),
}
}
// Resized returns the Rect resized to the given size while keeping the position of the given
// anchor.
//
// r.Resized(r.Min, size) // resizes while keeping the position of the lower-left corner
// r.Resized(r.Max, size) // same with the top-right corner
// r.Resized(r.Center(), size) // resizes around the center
//
// This function does not make sense for resizing a rectangle of zero area and will panic. Use
// ResizedMin in the case of zero area.
func (r Rect) Resized(anchor, size Vec) Rect {
if r.W()*r.H() == 0 {
panic(fmt.Errorf("(%T).Resize: zero area", r))
}
fraction := Vec{size.X / r.W(), size.Y / r.H()}
return Rect{
Min: anchor.Add(r.Min.Sub(anchor).ScaledXY(fraction)),
Max: anchor.Add(r.Max.Sub(anchor).ScaledXY(fraction)),
}
}
// ResizedMin returns the Rect resized to the given size while keeping the position of the Rect's
// Min.
//
// Sizes of zero area are safe here.
func (r Rect) ResizedMin(size Vec) Rect {
return Rect{
Min: r.Min,
Max: r.Min.Add(size),
}
}
// Contains checks whether a vector u is contained within this Rect (including it's borders).
func (r Rect) Contains(u Vec) bool {
return r.Min.X <= u.X && u.X <= r.Max.X && r.Min.Y <= u.Y && u.Y <= r.Max.Y
}
// Union returns the minimal Rect which covers both r and s. Rects r and s must be normalized.
func (r Rect) Union(s Rect) Rect {
return R(
math.Min(r.Min.X, s.Min.X),
math.Min(r.Min.Y, s.Min.Y),
math.Max(r.Max.X, s.Max.X),
math.Max(r.Max.Y, s.Max.Y),
)
}
// 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 zero-rectangle.
func (r Rect) Intersect(s Rect) Rect {
t := R(
math.Max(r.Min.X, s.Min.X),
math.Max(r.Min.Y, s.Min.Y),
math.Min(r.Max.X, s.Max.X),
math.Min(r.Max.Y, s.Max.Y),
)
if t.Min.X >= t.Max.X || t.Min.Y >= t.Max.Y {
return ZR
}
return t
}
// Intersects returns whether or not the given Rect intersects at any point with this Rect.
//
// 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 ||
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 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.
//
// 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 {
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)
}
// 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. 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{})
for _, edge := range r.Edges() {
if intersect, ok := l.Intersect(edge); ok {
pointMap[intersect] = struct{}{}
}
}
points := make([]Vec, 0, len(pointMap))
for point := range pointMap {
points = append(points, point)
}
// 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[0]}
}
}
return points
}
// Vertices returns a slice of the four corners which make up the rectangle.
func (r Rect) Vertices() [4]Vec {
return [4]Vec{
r.Min,
V(r.Min.X, r.Max.Y),
r.Max,
V(r.Max.X, r.Min.Y),
}
}

384
rectangle_test.go Normal file
View File

@ -0,0 +1,384 @@
package pixel_test
import (
"fmt"
"reflect"
"testing"
"github.com/faiface/pixel"
)
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)
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
}{
{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_Edges(t *testing.T) {
type fields struct {
Min pixel.Vec
Max pixel.Vec
}
tests := []struct {
name string
fields fields
want [4]pixel.Line
}{
{
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) {
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_Vertices(t *testing.T) {
type fields struct {
Min pixel.Vec
Max pixel.Vec
}
tests := []struct {
name string
fields fields
want [4]pixel.Vec
}{
{
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) {
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 TestRect_IntersectCircle(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 pixel.Vec
}{
{
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.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.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.IntersectCircle(): circle overlaps bottom-left corner",
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
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.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.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.5, 10.5), 1)},
want: pixel.V(0.2, 0.2),
},
{
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.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.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.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.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.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) {
r := pixel.Rect{
Min: tt.fields.Min,
Max: tt.fields.Max,
}
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)
}
})
}
}
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, 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) {
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)
}
})
}
}
var rectIntTests = []struct {
name string
r1, r2 pixel.Rect
want pixel.Rect
intersect bool
}{
{
name: "Nothing touching",
r1: pixel.R(0, 0, 10, 10),
r2: pixel.R(21, 21, 40, 40),
want: pixel.ZR,
},
{
name: "Edge touching",
r1: pixel.R(0, 0, 10, 10),
r2: pixel.R(10, 10, 20, 20),
want: pixel.ZR,
},
{
name: "Bit of overlap",
r1: pixel.R(0, 0, 10, 10),
r2: pixel.R(0, 9, 20, 20),
want: pixel.R(0, 9, 10, 10),
intersect: true,
},
{
name: "Fully overlapped",
r1: pixel.R(0, 0, 10, 10),
r2: pixel.R(0, 0, 10, 10),
want: pixel.R(0, 0, 10, 10),
intersect: true,
},
}
func TestRect_Intersect(t *testing.T) {
for _, tt := range rectIntTests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.r1.Intersect(tt.r2); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Rect.Intersect() = %v, want %v", got, tt.want)
}
})
}
}
func TestRect_Intersects(t *testing.T) {
for _, tt := range rectIntTests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.r1.Intersects(tt.r2); got != tt.intersect {
t.Errorf("Rect.Intersects() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -27,7 +27,7 @@ func NewSprite(pic Picture, frame Rect) *Sprite {
tri := MakeTrianglesData(6)
s := &Sprite{
tri: tri,
d: Drawer{Triangles: tri},
d: Drawer{Triangles: tri, Cached: true},
}
s.matrix = IM
s.mask = Alpha(1)
@ -44,6 +44,13 @@ func (s *Sprite) Set(pic Picture, frame Rect) {
}
}
// SetCached makes the sprite cache all the
// incoming pictures if the argument is true, and
// doesn't make it do that if the argument is false.
func (s *Sprite) SetCached(cached bool) {
s.d.Cached = cached
}
// Picture returns the current Sprite's Picture.
func (s *Sprite) Picture() Picture {
return s.d.Picture
@ -105,12 +112,7 @@ func (s *Sprite) calcData() {
(*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()

View File

@ -1,7 +1,6 @@
package text
import (
"fmt"
"image"
"image/draw"
"sort"
@ -60,9 +59,10 @@ func NewAtlas(face font.Face, runeSets ...[]rune) *Atlas {
))
for r, fg := range fixedMapping {
dr, mask, maskp, _, _ := face.Glyph(fg.dot, r)
if dr, mask, maskp, _, ok := face.Glyph(fg.dot, r); ok {
draw.Draw(atlasImg, dr, mask, maskp, draw.Src)
}
}
bounds := pixel.R(
i2f(fixedBounds.Min.X),
@ -203,7 +203,6 @@ func makeMapping(face font.Face, runes []rune, padding, width fixed.Int26_6) (ma
for _, r := range runes {
b, advance, ok := face.GlyphBounds(r)
if !ok {
fmt.Println(r)
continue
}

View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/faiface/pixel/text"
"golang.org/x/image/font/inconsolata"
)
func TestAtlas7x13(t *testing.T) {
@ -22,3 +23,7 @@ func TestAtlas7x13(t *testing.T) {
}
}
}
func TestAtlasInconsolata(t *testing.T) {
text.NewAtlas(inconsolata.Regular8x16, text.ASCII)
}

View File

@ -101,6 +101,7 @@ type Text struct {
trans pixel.TrianglesData
transD pixel.Drawer
dirty bool
anchor pixel.Anchor
}
// New creates a new Text capable of drawing runes contained in the provided Atlas. Orig and Dot
@ -135,6 +136,7 @@ func New(orig pixel.Vec, atlas *Atlas) *Text {
txt.transD.Picture = txt.atlas.pic
txt.transD.Triangles = &txt.trans
txt.transD.Cached = true
txt.Clear()
@ -182,6 +184,12 @@ func (txt *Text) BoundsOf(s string) pixel.Rect {
return bounds
}
// AlignedTo returns the text moved by the given anchor.
func (txt *Text) AlignedTo(anchor pixel.Anchor) *Text {
txt.anchor = anchor
return txt
}
// Clear removes all written text from the Text. The Dot field is reset to Orig.
func (txt *Text) Clear() {
txt.prevR = -1
@ -244,6 +252,10 @@ func (txt *Text) DrawColorMask(t pixel.Target, matrix pixel.Matrix, mask color.C
txt.mat = matrix
txt.dirty = true
}
offset := txt.Orig.Sub(txt.Bounds().Max.Add(txt.Bounds().AnchorPos(txt.anchor.Opposite())))
txt.mat = pixel.IM.Moved(offset).Chained(txt.mat)
if mask == nil {
mask = pixel.Alpha(1)
}

View File

@ -76,7 +76,7 @@ func BenchmarkTextWrite(b *testing.B) {
b.Run(fmt.Sprintf("%d", len(chunk)), func(b *testing.B) {
txt := text.New(pixel.ZV, atlas)
for i := 0; i < b.N; i++ {
txt.Write(chunk)
_, _ = txt.Write(chunk)
}
})
}

462
vector.go Normal file
View File

@ -0,0 +1,462 @@
package pixel
import (
"fmt"
"math"
)
// Vec is a 2D vector type with X and Y coordinates.
//
// Create vectors with the V constructor:
//
// u := pixel.V(1, 2)
// v := pixel.V(8, -3)
//
// Use various methods to manipulate them:
//
// w := u.Add(v)
// fmt.Println(w) // Vec(9, -1)
// fmt.Println(u.Sub(v)) // Vec(-7, 5)
// u = pixel.V(2, 3)
// v = pixel.V(8, 1)
// if u.X < 0 {
// fmt.Println("this won't happen")
// }
// x := u.Unit().Dot(v.Unit())
type Vec struct {
X, Y float64
}
// ZV is a zero vector.
var ZV = Vec{0, 0}
// V returns a new 2D vector with the given coordinates.
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)
}
// String returns the string representation of the vector u.
//
// u := pixel.V(4.5, -1.3)
// u.String() // returns "Vec(4.5, -1.3)"
// fmt.Println(u) // Vec(4.5, -1.3)
func (u Vec) String() string {
return fmt.Sprintf("Vec(%v, %v)", u.X, u.Y)
}
// XY returns the components of the vector in two return values.
func (u Vec) XY() (x, y float64) {
return u.X, u.Y
}
// Add returns the sum of vectors u and v.
func (u Vec) Add(v Vec) Vec {
return Vec{
u.X + v.X,
u.Y + v.Y,
}
}
// Sub returns the difference betweeen vectors u and v.
func (u Vec) Sub(v Vec) Vec {
return Vec{
u.X - v.X,
u.Y - v.Y,
}
}
// Floor converts x and y to their integer equivalents.
func (u Vec) Floor() 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{
v.X - u.X,
v.Y - u.Y,
}
}
// Scaled returns the vector u multiplied by c.
func (u Vec) Scaled(c float64) Vec {
return Vec{u.X * c, u.Y * c}
}
// ScaledXY returns the vector u multiplied by the vector v component-wise.
func (u Vec) ScaledXY(v Vec) Vec {
return Vec{u.X * v.X, u.Y * v.Y}
}
// Len returns the length of the vector u.
func (u Vec) Len() float64 {
return math.Hypot(u.X, u.Y)
}
// SqLen returns the squared length of the vector u (faster to compute than Len).
func (u Vec) SqLen() float64 {
return u.X*u.X + u.Y*u.Y
}
// Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi].
func (u Vec) Angle() float64 {
return math.Atan2(u.Y, u.X)
}
// Unit returns a vector of length 1 facing the direction of u (has the same angle).
func (u Vec) Unit() Vec {
if u.X == 0 && u.Y == 0 {
return Vec{1, 0}
}
return u.Scaled(1 / u.Len())
}
// Rotated returns the vector u rotated by the given angle in radians.
func (u Vec) Rotated(angle float64) Vec {
sin, cos := math.Sincos(angle)
return Vec{
u.X*cos - u.Y*sin,
u.X*sin + u.Y*cos,
}
}
// Normal returns a vector normal to u. Equivalent to u.Rotated(math.Pi / 2), but faster.
func (u Vec) Normal() Vec {
return Vec{-u.Y, u.X}
}
// Dot returns the dot product of vectors u and v.
func (u Vec) Dot(v Vec) float64 {
return u.X*v.X + u.Y*v.Y
}
// Cross return the cross product of vectors u and v.
func (u Vec) Cross(v Vec) float64 {
return u.X*v.Y - v.X*u.Y
}
// Project returns a projection (or component) of vector u in the direction of vector v.
//
// Behaviour is undefined if v is a zero vector.
func (u Vec) Project(v Vec) Vec {
len := u.Dot(v) / v.Len()
return v.Unit().Scaled(len)
}
// Map applies the function f to both x and y components of the vector u and returns the modified
// vector.
//
// u := pixel.V(10.5, -1.5)
// v := u.Map(math.Floor) // v is Vec(10, -2), both components of u floored
func (u Vec) Map(f func(float64) float64) Vec {
return Vec{
f(u.X),
f(u.Y),
}
}
// Lerp returns a linear interpolation between vectors a and b.
//
// This function basically returns a point along the line between a and b and t chooses which one.
// If t is 0, then a will be returned, if t is 1, b will be returned. Anything between 0 and 1 will
// return the appropriate point between a and b and so on.
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
}
// L creates and returns a new Line.
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()
}
// 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 {
// 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
// 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 l.B
}
perpendicularM := -1 / m
perpendicularB := v.Y - (perpendicularM * v.X)
// Coordinates of intersect (of infinite lines)
x := (perpendicularB - b) / (m - perpendicularM)
y := m*x + b
// 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)
if toStart.Len() < toEnd.Len() {
return l.A
}
return l.B
}
return V(x, y)
}
// Contains returns whether the provided Vec lies on the line.
func (l Line) Contains(v Vec) bool {
return l.Closest(v).Eq(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.
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)
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 lDir.X == kDir.X && lDir.Y == 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 := k.Formula()
// Account for vertical lines
if math.IsInf(math.Abs(lm), 1) && math.IsInf(math.Abs(km), 1) {
// Both vertical, therefore parallel
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
}
y = intersectM*verticalLine.A.X + intersectB
x = verticalLine.A.X
} else {
// 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 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)
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 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
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()
var closest *Vec
closestCorner := corners[0]
for _, c := range corners {
cc := l.Closest(c)
if closest == nil || (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.To(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)
}

27
vector_test.go Normal file
View File

@ -0,0 +1,27 @@
package pixel_test
import (
"fmt"
"testing"
"github.com/faiface/pixel"
)
type floorTest struct {
input pixel.Vec
expected pixel.Vec
}
func TestFloor(t *testing.T) {
tests := []floorTest{
{input: pixel.V(4.50, 6.70), expected: pixel.V(4, 6)},
{input: pixel.V(9.0, 6.70), expected: pixel.V(9, 6)},
}
for _, tc := range tests {
result := tc.input.Floor()
if result != tc.expected {
t.Error(fmt.Sprintf("Expected %v but got %v", tc.expected, result))
}
}
}