Compare commits

..

388 Commits
master ... v0.7

Author SHA1 Message Date
Michal Štrba 2d9a739e40
Merge pull request #97 from mewpull/master
contributing: remove tracking from link to Gitter
2018-02-19 12:21:47 +01:00
mewmew cbf4b9106d contributing: remove tracking from link to Gitter 2018-02-19 09:30:03 +01:00
faiface 5af5565fb6 update README.md 2018-02-18 20:58:41 +01:00
faiface 902248de11 add CONTRIBUTING.md 2018-02-18 20:53:23 +01:00
Michal Štrba 93a9cf4093
Merge pull request #90 from peterhellberg/text-reset-and-default-atlas
Add Atlas7x13, Clear now sets Dot field to Orig
2018-01-19 23:29:34 +01:00
Peter Hellberg 5ed20d5485 Change package to text_test 2018-01-19 23:09:48 +01:00
Peter Hellberg f72c39f382 Remove new7x13Atlas function 2018-01-19 23:08:06 +01:00
Peter Hellberg 5f53a59dd6 Document that Clear resets the Dot to Orig
Remove note on how to reset Dot to the Orig
2018-01-19 23:00:33 +01:00
Peter Hellberg 1c68874dc8 Add Atlas7x13, Clear now sets Dot field to Orig
Remove unused f2i function
2018-01-19 22:20:54 +01:00
Michal Štrba e788339625
Merge pull request #89 from terakilobyte/master
adds a game of life example
2018-01-19 15:26:04 +01:00
Nathan c1abca5cab addressing PR feedback 2018-01-18 20:44:54 -05:00
Nathan 8e4da92871 adds a game of life example 2018-01-18 19:52:52 -05:00
Michal Štrba c1a222d84b
Merge pull request #87 from svera/parallax-scroll
Parallax scroll
2018-01-12 12:59:03 +01:00
Michal Štrba 0183f7fbbb
Merge pull request #86 from svera/update-image
Update image
2018-01-12 12:57:11 +01:00
Sergio Vera Castellano 452ef16712 Removed unneeded parentheses 2018-01-12 12:52:50 +01:00
Sergio Vera Castellano 3706fa8879 Removed unneeded parentheses 2018-01-12 12:51:07 +01:00
Sergio Vera Castellano 0e9ff7b70e Added new image 2018-01-12 12:48:09 +01:00
Sergio Vera Castellano 1a41307942 Fixed image positioning 2018-01-12 12:47:16 +01:00
Michal Štrba 14ace447b5
Merge pull request #85 from svera/parallax-scroll
Parallax scroll (fixes #75)
2018-01-12 12:44:11 +01:00
Sergio Vera Castellano b4c1033c47 Corrected image placement 2018-01-12 12:43:09 +01:00
Michal Štrba 50dd323ba4
Merge pull request #82 from svera/procedural-terrain-1d
Procedural terrain 1D
2018-01-12 12:42:22 +01:00
Sergio Vera Castellano b40854c1d7 Update image 2018-01-12 12:40:02 +01:00
Sergio Vera Castellano 2f957ede44 Updated comment 2018-01-12 12:31:00 +01:00
Sergio Vera Castellano 0f4afdc244 Added result image 2018-01-12 12:29:08 +01:00
Sergio Vera Castellano d04bf34361 Updated window title 2018-01-12 12:23:10 +01:00
Sergio Vera Castellano ff0040bbb1 Small adjustments 2018-01-12 12:19:58 +01:00
Sergio Vera Castellano 1f1324dbe8 Fixed images 2018-01-12 10:37:52 +01:00
Sergio Vera Castellano dc889f0631 Fixed comments 2018-01-12 10:33:24 +01:00
Michal Štrba 8dd236979d
Merge pull request #84 from peterhellberg/change-screenshot-aspect-ratios
Change screenshot aspect ratios
2018-01-11 23:18:13 +01:00
Peter Hellberg 9942ab3da3 Change screenshot aspect ratios 2018-01-11 23:14:54 +01:00
faiface 3afca9093d add some community examples screenshots to README 2018-01-11 22:55:26 +01:00
Michal Štrba 97f07acd5e
Merge pull request #83 from peterhellberg/add-pixel-experiments-to-community-examples
Add pixel experiments to community examples
2018-01-11 22:50:56 +01:00
Peter Hellberg da55b9ddca Add bouncing experiment to community examples 2018-01-11 22:42:04 +01:00
Peter Hellberg b50671c353 Add starfield experiment to community examples 2018-01-11 22:39:45 +01:00
Peter Hellberg dc2b6fe7b1 Add raycaster experiment to community examples 2018-01-11 22:39:36 +01:00
Sergio Vera Castellano bd468c8629 Added result image 2018-01-11 17:53:16 +01:00
Sergio Vera Castellano 060142a8f9 Added readme 2018-01-11 17:43:39 +01:00
Sergio Vera Castellano 75e90bb010 Refactored code 2018-01-11 16:55:20 +01:00
Sergio Vera Castellano f4b7c74524 First commit 2018-01-11 14:52:23 +01:00
Sergio Vera Castellano 55fa9fd100 Fixed readme 2018-01-11 09:58:34 +01:00
Sergio Vera Castellano 814aff8f1c Removed log 2018-01-11 09:56:36 +01:00
Sergio Vera Castellano 79a78f2f1c First commit 2018-01-11 09:54:39 +01:00
Michal Štrba 95f4386cf5
Merge pull request #79 from mewpull/poll
pixelgl: export UpdateInput
2018-01-04 19:32:21 +01:00
mewmew f7b8a5a1a3 pixelgl: clarify UpdateInput docs 2018-01-04 19:23:53 +01:00
mewmew 4dc0b013f3 pixelgl: export UpdateInput 2018-01-01 21:04:56 +01:00
Michal Štrba 597550c16b
Merge pull request #77 from svera/isometric-basics
Isometric community example
2017-12-31 18:33:17 +01:00
Sergio Vera Castellano c65ed3a739 Added sample image 2017-12-31 18:07:00 +01:00
Sergio Vera Castellano b04ac93aaa First commit 2017-12-31 17:56:58 +01:00
Michal Štrba 9b325eee4e
Merge pull request #74 from svera/scrolling-background
Infinite scrolling background demo
2017-12-28 12:20:12 +01:00
Sergio Vera Castellano 82de22b7cf made framerate independent 2017-12-28 10:43:17 +01:00
Sergio Vera Castellano 302f1f5411 Small example explanation 2017-12-27 17:05:43 +01:00
Sergio Vera Castellano 5b2ad29f7b First commit 2017-12-27 16:29:21 +01:00
Michal Štrba 225de575de
Merge pull request #70 from PlainSight/improve-geo-tests
Improve geometry tests
2017-11-20 02:17:18 +01:00
Michal Štrba cceaac897e
Merge pull request #69 from PlainSight/add-getpos-to-window
Add GetPos function to window
2017-11-20 02:15:20 +01:00
ALex Ogier 1c4776d672 minor adjustments with how tests are named and run 2017-11-20 13:51:15 +13:00
ALex Ogier c9df32add0 refactor and add test cases 2017-11-20 13:51:15 +13:00
ALex Ogier 41a3073be3 add GetPos function to window 2017-11-20 13:37:22 +13:00
faiface be9fbb76bd README: add Windows guide mention 2017-11-15 21:38:24 +01:00
faiface 3d6f497bd5 dirty fix #58 (panic on minimizing windows) 2017-11-08 22:28:32 +01:00
Michal Štrba 0d89f0e36e
Merge pull request #61 from Schobers/master
Added SetPos to pixelgl.Window
2017-10-27 20:48:00 +02:00
Sander Schobers 8e68e71083 Added SetPos to pixelgl.Window 2017-10-27 20:35:45 +02:00
faiface 5612defeba README: mention OpenGL version 2017-10-19 21:34:09 +02:00
faiface 926b8181b3 add Vec.Project 2017-10-15 19:50:41 +02:00
faiface 6e27fc84e7 add pixel.Unit 2017-10-15 19:43:12 +02:00
faiface 6215259c1d add pixel.Clamp 2017-10-15 19:42:13 +02:00
faiface 4792a9ebd8 fix Matrix.Chained (wrong order of composition) 2017-09-04 00:40:12 +02:00
faiface 1de2dbe561 fix Vec.Normal to rotate by pi/2, not -pi/2 2017-08-29 20:23:32 +02:00
Michal Štrba 9eaf82e088 Merge pull request #48 from PlainSight/fix-rectangle-resize
Fix Rectangle "Resized" function
2017-07-29 01:11:02 +02:00
Alex 78d02fe8ea add more test transforms, now includes: center, origin, min, max and middle of side 2017-07-29 11:05:46 +12:00
Alex 35ebc39ba6 fix operation order for rectangle resize function, add tests 2017-07-29 00:40:06 +12:00
faiface d34b63676d remove audio package in favor of faiface/beep 2017-07-14 18:34:28 +02:00
faiface 321c494681 audio: wav: fix Stream for non 2 chans 2 bytes configurations 2017-07-14 02:41:01 +02:00
faiface 7fbd71cecc audio: wav: more precise Position and Seek 2017-07-14 02:22:33 +02:00
faiface 264bdff76d audio: wav: fix Stream (move position, forgot to do it previously) 2017-07-14 02:10:12 +02:00
faiface 447050b720 audio: wav: simplify Stream 2017-07-14 02:09:36 +02:00
faiface 4573035f89 merge master 2017-07-13 01:24:50 +02:00
faiface 63ababf515 audio: add Gain effect 2017-07-13 00:29:53 +02:00
faiface cce4cccd26 audio: minor change 2017-07-13 00:21:16 +02:00
faiface accdb6f51c update .travis.yml for ALSA 2017-07-12 22:54:36 +02:00
faiface d4f724a0d3 audio: minor change 2017-07-12 22:44:15 +02:00
faiface e3d1835268 audio: add Ctrl doc comments 2017-07-12 22:43:55 +02:00
faiface 66a8b51d54 audio: wav: add doc comments 2017-07-12 21:30:35 +02:00
faiface 9a1d011a9f audio: wav: simplify code, more DRY, encapsulate decoder type, only export Decode function 2017-07-12 19:37:20 +02:00
faiface e48de5789c audio: make Take propagate errors 2017-07-12 02:58:17 +02:00
faiface af52257c66 audio: wav: update streamer to satisfy StreamSeekCloser 2017-07-12 00:32:37 +02:00
faiface 31eeca4179 add StreamSeeker, StreamCloser and StreamSeekCloser interfaces 2017-07-12 00:05:42 +02:00
faiface 5b75852399 audio: add Err method to Streamer 2017-07-11 23:42:57 +02:00
faiface 0d5ad8a0bf audio: wav: fix seeking to include the header 2017-07-11 20:12:04 +02:00
faiface bf50427297 audio: rename Ctrl.Duration -> Ctrl.Position 2017-07-11 20:01:15 +02:00
faiface f3647fc451 audio: wav: add Seek and Position 2017-07-11 20:00:29 +02:00
faiface 73a3f65adb audio: wav: rename decoder.go -> streamer.go 2017-07-11 18:31:33 +02:00
faiface 099522c410 minor change 2017-07-11 17:36:00 +02:00
faiface 3ca575a4e4 audio: add initial wav streamer implementation 2017-07-11 17:34:17 +02:00
faiface dc4b4ec61f audio: speaker: fix byte encoding 2017-07-11 16:42:16 +02:00
faiface eca7b33334 audio: speaker: remove unnecessary channel confirmation 2017-07-09 16:34:49 +02:00
faiface 5ca0239191 audio: speaker: allow to call Init more than once 2017-07-08 22:01:59 +02:00
faiface af9bcae596 audio: add Mixer doc 2017-07-08 14:54:01 +02:00
faiface 62fcf430d2 audio: optimize Mixer.Stream and Mix(...).Stream (remove unnecessary mix buffer) 2017-07-08 14:43:24 +02:00
faiface b3cceb1b7d audio: add Mixer.Len 2017-07-08 14:28:34 +02:00
faiface 006e4f5481 audio: speaker: use Mixer to play sounds simultaneously 2017-07-07 18:46:40 +02:00
faiface 33c6f0ca5d audio: add Mixer 2017-07-07 18:46:26 +02:00
faiface c5078ff6b0 audio: add Ctrl decorator 2017-07-07 18:46:20 +02:00
faiface 9398991a01 audio: add Mix compositor 2017-07-07 16:11:48 +02:00
faiface 1a58fea88b minor change 2017-07-07 14:08:25 +02:00
faiface 6c527c3b89 speaker: remove Update, add Lock and Unlock 2017-07-07 13:20:55 +02:00
Michal Štrba 0777dc3bc1 Merge pull request #45 from aerth/audio-dep
fix audio dependency
2017-07-07 00:16:32 +02:00
aerth e2823f2580
fix audio dependency 2017-07-06 15:08:41 -07:00
faiface f47736b7cb fix edge sample value (-1 and +1) overflow 2017-07-06 23:36:18 +02:00
faiface 5d3f082240 actually fix the race condition 2017-07-06 22:34:22 +02:00
faiface d0f6e646ac minor change 2017-07-06 22:26:47 +02:00
faiface fdb6359fbb speaker: fix race condition 2017-07-06 22:26:20 +02:00
faiface 0737b86059 speaker: improve concurrency, only lock when and what necessary 2017-07-06 22:11:03 +02:00
faiface cb4bb4c3ef reimplement speaker 2017-07-06 21:44:34 +02:00
faiface 41963b01cc Merge branch 'audio' of https://github.com/faiface/pixel into audio 2017-07-06 20:52:12 +02:00
Michal Štrba 065f4f3c90 Merge pull request #44 from alistanis/audio
initial incomplete speaker implementation
2017-07-06 20:52:02 +02:00
Christopher Cooper 8e571bfe8e addresses some review comments 2017-07-06 11:49:35 -04:00
Christopher Cooper 32ff29438d fix bounds check 2017-07-05 18:15:53 -04:00
Christopher Cooper 1a88ab2edd mremove prints 2017-07-05 18:11:26 -04:00
Christopher Cooper f9972888a2 add initial speaker implementation 2017-07-05 18:10:03 -04:00
Christopher Cooper 6ac68670bd Merge pull request #1 from faiface/audio
Audio
2017-07-05 18:05:50 -04:00
Michal Štrba 57b578bba7 Merge pull request #43 from aerth/audio
add libasound2-dev travis dependency
2017-07-05 22:04:44 +02:00
aerth cdce974e49
add libasound2-dev dependency 2017-07-05 12:24:26 -07:00
faiface d7487f1f7a fix Rect.Intersect 2017-07-05 20:36:25 +02:00
faiface 4137f87f22 clarify Rect.Intersect doc 2017-07-05 19:58:09 +02:00
faiface bee65f5833 add Rect.Intersect 2017-07-05 19:54:30 +02:00
faiface 46e79f21b9 add Rect.Area 2017-07-05 19:54:18 +02:00
faiface 2a8c17c33c add Rect.Intersect 2017-07-05 19:51:54 +02:00
faiface c49c77a116 add Rect.Area 2017-07-05 19:35:11 +02:00
faiface d640879775 add Take decorator function 2017-07-05 18:14:18 +02:00
faiface c86834b8f6 add Seq compositor 2017-07-05 17:59:43 +02:00
faiface 330e9da360 add StreamerFunc helper type 2017-07-03 19:07:30 +02:00
faiface 7f2b8b6fe9 change SampleRate to float64 2017-07-03 18:55:27 +02:00
faiface 42737212d8 fix grammar in Streamer doc 2017-07-03 17:37:55 +02:00
faiface c7bb0a1639 minor stylistic corrections in Streamer doc 2017-07-03 17:28:54 +02:00
faiface a48d07ff6b audio: add Streamer interface 2017-07-03 14:05:08 +02:00
faiface 99573a5f1e optimize ToRGBA (weird one) 2017-07-03 00:23:07 +02:00
faiface c23446fb49 improve sprite.Draw(batch) benchmark 2017-07-03 00:22:45 +02:00
faiface 578ae8fa53 fix bug in text benchmark 2017-07-02 23:26:26 +02:00
faiface 6af6195bd0 optimize Drawer (reduce map access) 2017-07-02 23:23:27 +02:00
faiface 0c28c0785e add text benchmarks 2017-07-02 19:35:56 +02:00
faiface 17f735c2d0 remove unnecessary reassign in color benchmarks 2017-07-02 19:26:43 +02:00
faiface 7629b6ef5e add imdraw benchmarks 2017-07-02 19:22:40 +02:00
faiface ae0526fda0 add ToRGBA, sprite.Draw(batch) and Matrix benchmarks 2017-07-02 19:04:20 +02:00
Michal Štrba 80cfdfcb6a Merge pull request #40 from aerth/master
Use travis-ci
2017-06-16 01:14:23 +02:00
aerth b01300dab9
move button 2017-06-15 23:05:00 +00:00
aerth c078a58652
Link to travis build 2017-06-15 22:52:47 +00:00
aerth b58d6bf5ec
add travis config 2017-06-15 22:40:37 +00:00
faiface 75ab96923c another minor code style change 2017-06-11 14:14:02 +02:00
faiface 2a9b7e5210 minor code style change 2017-06-11 14:06:45 +02:00
faiface 79c5d20194 Merge branch 'dev' 2017-06-11 01:19:57 +02:00
faiface e6484064aa one more tiny doc change 2017-06-11 01:18:23 +02:00
faiface 3fcad7503f minor doc changes 2017-06-11 01:17:37 +02:00
Michal Štrba 1545bd7af5 Merge pull request #38 from seebs/master
more performance tweaks
2017-06-11 01:12:03 +02:00
Seebs 0dc27e409b Push: Don't convert pixel.RGBA to pixel.RGBA
Because that's expensive, even in the case where the conversion
is trivial. Use type assertion first. Reduces runtime cost of
imdraw.Push from ~15.3% to 8.4%, so not-quite-50% of runtime
cost of pushing points.

If you were setting imd.Color to Color objects that aren't RGBA
every single point, not much help. But if you set it and then
draw a bunch of points, this will be a big win.
2017-06-10 17:56:15 -05:00
Seebs f2ef87f198 Improve normal calculations
Soooo. It turns out that the bunch of smallish (~4-5% of runtime)
loads associated with Len(), Unit(), Rotated(), and so on... Were
actually more like 15% or more of computational effort. I first
figured this out by creating:

	func (u Vec) Normal(v Vec) Vec

which gives you a vector normal to u->v. That consumed a lot
of CPU time, and was followed by .Unit().Scaled(imd.thickness / 2),
which consumed a bit more CPU time.

After some poking, and in the interests of avoiding UI cruft,
the final selection is
	func (u Vec) Normal() Vec

This returns the vector rotated 90 degrees, which turns out to
be the most common problem.
2017-06-10 17:55:16 -05:00
Seebs ef6a44fef8 Slightly clean up normal calculations
We never actually need the "normal" value; it's an extra calculation
we didn't need, because ijNormal is the same value early on. It's
totally possible that we could further simplify this; there's a lot
of time going into the normal computations.
2017-06-10 10:47:22 -05:00
Seebs 9a6e6066bd don't call Len() when it can't change
updateData()'s loops checking gt.Len() turns out to have been costing
significant computation, not least because each call then in turn
called gt.vs.Stride().
2017-06-10 10:47:18 -05:00
faiface 7ebbf7e9b5 switch back to OpenGL 3.3 (OS X issues) 2017-06-10 15:11:45 +02:00
faiface 31fc049ab7 optimize GLTriangles SetLen and Update 2017-06-10 01:10:59 +02:00
faiface c331fe2583 Merge branch 'master' into gl2.1 2017-06-09 19:26:14 +02:00
faiface 6b9ea45e96 minor, mostly stylistic, changes 2017-06-09 18:13:05 +02:00
Michal Štrba 2e4c6018c9 Merge pull request #36 from seebs/master
revised performance tuning pull request
2017-06-09 18:04:25 +02:00
Seebs 7215265523 Don't duplicate computations in gltriangles.go
The computation including a call to Stride() can't be optimized away
safely because the compiler can't tell that Stride() is effectively
constant, but we know it won't change so we can make a slice pointing
at that part of the array.

CPU time for updateData goes from 26.35% to 18.65% in my test case.
2017-06-09 10:37:47 -05:00
Seebs fc858bff4d Reduce copying in fillPolygon
A slice of points means copying every point into the slice, then
copying every point's data from the slice to TrianglesData. An
array of indicies lets the compiler make better choices.
2017-06-09 10:37:47 -05:00
Seebs 918031892a smaller imdraw optimizations
For polyline, don't compute each normal twice; when we're going through a line,
the "next" normal for segment N is always the "previous" normal for segment
N+1, and we can compute fewer of them.
2017-06-09 10:37:47 -05:00
Seebs 9a7ab1c6b0 use point pool
For internal operations (anything using getAndClearPoints), there's a
pretty good chance that the operation will repeatedly invoke something
like fillPolygon(), meaning that it needs to push "a few" points
and then invoke something that uses those points.

So, we add a slice for containing spare slices of points, and on the
way out of each such function, shove the current imd.points (as used
inside that function) onto a stack, and set imd.points to [0:0] of
the thing it was called with.

Performance goes from 11-13fps to 17-18fps on my test case.
2017-06-09 10:37:47 -05:00
Seebs 34cdd8729b Simplify Matrix math, use 6-value affine matrixes.
It turns out that affine matrices are much simpler than the 3x3 matrices
they imply, and we can use this to dramatically streamline some code.
For a test program, this was about a 50% gain in frame rate just from
the cost of the applyMatrixAndMask calls in imdraw, which were calling
matrix.Project() many times. Simplifying matrix.Project, alone, got a
nearly 50% frame rate boost!

Also modify pixelgl's SetMatrix to copy the six values of a 3x2
Affine into the corresponding locations of a 3x3 matrix.
2017-06-09 10:37:43 -05:00
Seebs 0358330d3b The initializer is surprisingly expensive.
Removing the call to Alpha(1) and replacing it with an inline definition
produces measurable improvements. Replacing each instance of ZV with
Vec{} further improves things. We keep an inline RGBA because there
are circumstances (mostly when using pictures) where we don't want to
have to set colors to get default behavior.

For a fairly triangle-heavy thing, this reduces time spent in SetLen
from something over 10% of execution time to around 2.5% of execution
time.
2017-06-07 21:25:54 -05:00
faiface 4b7553cd73 add maze generator community example 2017-05-30 13:30:09 +02:00
faiface 781c44f119 add text tutorial link to README 2017-05-30 02:53:24 +02:00
faiface c385b247b3 add 07 guide code 2017-05-30 02:52:33 +02:00
faiface 3706d040ce fix typo in doc 2017-05-28 18:50:56 +02:00
faiface 4749e3ee7e add Canvas.Frame method 2017-05-28 18:44:30 +02:00
faiface e06acda99b minorly simplify typewriter example 2017-05-28 00:06:29 +02:00
faiface 9e0c65d8dd remove profiling from typewriter example 2017-05-27 19:19:31 +02:00
faiface f80edafc7b remove profiling from typewriter example 2017-05-27 19:14:25 +02:00
faiface 67a69d96d6 switch to OpenGL 2.1 2017-05-27 13:14:13 +02:00
faiface bbeab0aebf update 06 guide code 2017-05-26 21:26:06 +02:00
faiface d5f7088b7d update 05 guide code 2017-05-26 14:04:44 +02:00
faiface 9a401948ae remove accidentaly set theme 2017-05-25 22:51:17 +02:00
Michal Štrba abc99bdef8 Set theme jekyll-theme-time-machine 2017-05-25 20:03:02 +02:00
faiface debdbea894 limit ttf face glyphcacheentries size in typewriter example 2017-05-25 15:21:28 +02:00
faiface fc30e51016 update 04 guide code 2017-05-24 22:05:01 +02:00
faiface 90432a7857 update drawing features in README 2017-05-24 20:39:11 +02:00
faiface 51c32e407f update 03 guide code 2017-05-24 20:34:46 +02:00
faiface 659dc6fd5f update 02 guide code 2017-05-24 16:16:59 +02:00
faiface 50ba35d4cb add text input mention to README 2017-05-24 14:42:00 +02:00
faiface 9102076f1b add text drawing feature mention to README 2017-05-24 14:36:18 +02:00
faiface 2834411318 fix link in README 2017-05-24 14:34:49 +02:00
faiface 7fe45b5a88 replace xor screenshot with typewriter screenshot in README 2017-05-24 14:33:50 +02:00
faiface 51e1843f4b remove debug print from typewriter example 2017-05-24 02:21:25 +02:00
faiface eab5be6207 fiddle with constants in typewriter example 2017-05-24 00:01:33 +02:00
faiface 62fa797088 clarify typewriter doc 2017-05-23 22:06:43 +02:00
faiface 970faf0b63 clarify typewriter example readme 2017-05-23 20:40:45 +02:00
faiface 6c269fc8a5 add typewriter example 2017-05-23 20:07:23 +02:00
faiface a794d27972 clarify Rect doc 2017-05-21 19:38:21 +02:00
faiface bc08b65073 fix typo in ToRGBA doc 2017-05-21 19:33:01 +02:00
faiface bf6e20a04b fix Matrix doc 2017-05-21 19:30:29 +02:00
faiface ecdd8462bb replace complex128 Vec with a struct 2017-05-21 19:25:06 +02:00
faiface 3af9c2b20e remove Text.SetMatrix and Text.SetColorMask, add Text.Draw(target, matrix) and Text.DrawColorMask(target, matrix, mask) 2017-05-21 18:23:51 +02:00
faiface 3ae612a84d minor change 2017-05-21 18:23:20 +02:00
faiface 0ac5371d7e update Sprite's doc 2017-05-21 15:31:07 +02:00
faiface fccedc5a9d remove Text.Matrix() and Text.ColorMask() getters 2017-05-19 01:58:34 +02:00
faiface f7aac5ed09 change Text properties to fields 2017-05-18 23:59:42 +02:00
faiface 1d928485d6 change IMDraw properties to fields 2017-05-18 23:50:45 +02:00
faiface b832e83517 change Sprite.Draw and Canvas.Draw signatures (include Matrix) 2017-05-17 23:45:22 +02:00
faiface 53167788d6 accept zero target size in Rect.Resized 2017-05-15 01:15:16 +02:00
faiface 9d60c5fa32 improve Atlas creation time 2-3 times 2017-05-11 19:48:43 +02:00
faiface cce26f0a51 add Window.Repeat 2017-05-10 23:54:06 +02:00
faiface b15c10298e fix and simplify input handling in Window 2017-05-10 21:22:47 +02:00
faiface 3a14aae310 add Window.Typed 2017-05-10 21:10:10 +02:00
faiface e86120db20 change text.New to take an Atlas 2017-05-10 17:56:09 +02:00
faiface a510048648 add text package doc 2017-05-09 16:48:26 +02:00
faiface feb12a1c7e add Text.Matrix and Text.ColorMask 2017-05-09 16:46:11 +02:00
faiface 3ffbbb9cda add examples on LineHeight and TabWidth to Text doc 2017-05-09 16:40:44 +02:00
faiface 863e1e2f0c add note about not destroying face.Face to Atlas doc 2017-05-09 16:39:03 +02:00
faiface e3268db31e mention control characters in Text doc 2017-05-09 16:36:59 +02:00
faiface 7b10ad8497 remove accidental markdown formating from Text doc 2017-05-09 16:35:51 +02:00
faiface abcdff5960 fix typo in Text doc 2017-05-09 16:34:54 +02:00
faiface dc3f9857d8 minor change in Text doc 2017-05-09 16:34:13 +02:00
faiface dd6d38b8f3 minor change in Atlas doc 2017-05-09 16:32:29 +02:00
faiface 46122dd826 clarify doc 2017-05-09 16:31:09 +02:00
faiface c8114d8467 add Text doc 2017-05-09 16:27:55 +02:00
faiface f58676289a minor change 2017-05-09 15:26:50 +02:00
faiface 80735cfc0c add Atlas doc 2017-05-09 15:25:08 +02:00
faiface dcdc812af5 don't cache kerning in Atlas (too expensive and no benefit) 2017-05-09 15:10:35 +02:00
faiface 248d68f6aa improve Atlas creation, atlas is now square picture (was one row of characters) 2017-05-09 14:20:34 +02:00
faiface 3841afb70f fix PictureDataFromImage (wrong bounds when Min not (0, 0)) 2017-05-09 01:04:04 +02:00
faiface 217ac0c4d7 rename Text.LineHeight(scale) -> height, since now it's absolute height 2017-05-07 22:03:56 +02:00
faiface 65236863fe rename Glyph.Orig -> Dot 2017-05-07 21:49:26 +02:00
faiface cfaff8c0cb minor change 2017-05-07 21:12:48 +02:00
faiface 2d1f61f746 improve Text code 2017-05-07 21:08:10 +02:00
faiface 883bdc32c7 add Text.BoundsOf 2017-05-07 21:00:19 +02:00
faiface c9eea2639e restructure Text writing for more flexibility and consistency 2017-05-07 20:59:56 +02:00
faiface 5b524dadd8 add Atlas.DrawRune 2017-05-07 20:59:41 +02:00
faiface a86876a1cd Merge branch 'dev' into text 2017-05-06 22:58:36 +02:00
faiface 29785fb937 IMDraw: change default point color to (1, 1, 1, 1) (was (0, 0, 0, 0)) 2017-05-06 22:57:55 +02:00
faiface 1d17e45825 fix Text.Bounds 2017-05-05 16:42:40 +02:00
faiface b91d8be6cd remove Batch from Text and optimize it 2017-05-05 16:13:26 +02:00
faiface c47d77b2b5 add Text.Bounds, Atlas.Ascent, Atlas.Descent 2017-05-05 16:02:47 +02:00
faiface e9a3c900cf add Rect.Union 2017-05-05 15:43:24 +02:00
faiface 3d3cbd6027 fix Text.Color 2017-05-04 22:30:18 +02:00
faiface 4e6d6eeb3a move Atlas type to a separate file 2017-05-03 23:59:37 +02:00
faiface 17ddf4fec5 change Text.LineHeight to use actual units instead of scale (such as 1.5) 2017-05-03 23:57:09 +02:00
faiface e1e1815537 add Text.Atlas (Atlas has some useful stuff, e.g. line height) 2017-05-03 23:55:44 +02:00
faiface a05abdca76 add Text.WriteByte 2017-05-03 23:55:10 +02:00
faiface 317124058e export Atlas from text package + add Text.WriteRune and Text.WriteString 2017-05-03 23:54:24 +02:00
faiface 3b1e0eaa21 add Text.SetMatrix and Text.SetColorMask 2017-05-03 21:48:05 +02:00
faiface 6a7500959f add Text.LineHeight and text.TabWidth 2017-05-03 21:04:18 +02:00
faiface e2a16764c4 add Text.Orig (start of the text) 2017-05-03 20:56:06 +02:00
faiface 424b6e0f0b optimize Text.Write (remove one allocation) 2017-05-03 00:13:39 +02:00
faiface bc145eb1b8 add incomplete Text type in text package 2017-05-02 22:46:51 +02:00
faiface 5303ec5648 optimize Drawer.Dirty (defer the hard part until drawing) 2017-05-02 22:46:27 +02:00
faiface f7dad2f3c2 strikethrough advanced window manipulation mssing feature 2017-05-02 01:15:33 +02:00
faiface 3b5bfa90e6 fix creating window with no icon 2017-05-01 12:18:23 +02:00
faiface dff622523b just align doc comment to 100 chars per line 2017-05-01 01:38:57 +02:00
Michal Štrba 51a9702fc8 Merge pull request #14 from otraore/set-icon
Add support for setting an icon of a window
2017-05-01 01:33:59 +02:00
Ousmane Traore f7bb304b92 Set value rather than append 2017-04-30 19:30:11 -04:00
Ousmane Traore 5efd04b420 Address review comments 2017-04-30 18:43:05 -04:00
Ousmane Traore ea7bc5aff9 Add Icons paramter to window config 2017-04-30 17:19:51 -04:00
faiface 7b8a0c152e fix Window.CursorVisible intial value (was false) 2017-04-30 20:42:25 +02:00
Michal Štrba 2115296062 Merge pull request #13 from otraore/hide-cursor
Add ability to hide the cursor
2017-04-30 18:50:43 +02:00
Ousmane Traore dc745825d6 Address review comments 2017-04-30 12:33:27 -04:00
Ousmane Traore 29fe3b16ca Add ability to hide the cursor 2017-04-30 10:40:31 -04:00
faiface d37ad8f1ba fix two typos in comments in platformer example 2017-04-28 18:11:29 +02:00
faiface 85ba21a2f4 add Canvas.SetPixels and Canvas.Pixels methods 2017-04-28 13:24:30 +02:00
faiface 915faeee0c merge dev branch 2017-04-26 23:16:34 +02:00
faiface 113d052872 fix link in readme 2017-04-26 23:15:08 +02:00
faiface 9571b4339b add note to readme about macOS, go 1.8 and xcode problems 2017-04-26 23:14:01 +02:00
Michal Štrba 64aa32a4b4 Merge pull request #6 from faiface/dev
merge shader error fix
2017-04-26 18:01:45 +02:00
Michal Štrba 01db742aba Merge pull request #5 from ivanov/fix-4
fix "Attempt to use 'texture' as a variable" shader compiler error
2017-04-26 17:58:39 +02:00
Paul Ivanov ff64cf248b fix for 'texture' as a variable error, closes #4 2017-04-26 08:35:10 -07:00
faiface d595d5f647 fix compiler error (ouch) 2017-04-26 15:11:12 +02:00
faiface 951c7f4c59 remove PictureData.SetColor
was confusing since Pictures in e.g. Sprites are not really updatable
2017-04-26 15:04:46 +02:00
faiface e8aa765ed3 add a note about memory leaks to Drawer and Sprite docs 2017-04-26 14:28:25 +02:00
faiface bdbce3a27b change first sentence in readme 2017-04-24 22:51:43 +02:00
faiface 0389ee7bf9 minor change in readme 2017-04-24 21:00:01 +02:00
faiface 736f4549a9 add contributing section to readme 2017-04-24 20:55:18 +02:00
faiface ef9fd8fe10 minor change in readme 2017-04-24 20:39:18 +02:00
faiface 4246e90215 update example screenshots 2017-04-24 20:07:32 +02:00
faiface 1d0169b2b7 remove fps counters from examples for simplicity and consistency 2017-04-24 19:34:31 +02:00
faiface 2626892e97 adjust gitter badge location 2017-04-24 14:21:09 +02:00
Michal Štrba 1e2eb29a4f Merge pull request #2 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2017-04-24 14:20:12 +02:00
The Gitter Badger b60b7e5207 Add Gitter badge 2017-04-24 12:18:51 +00:00
faiface 8165c38c6d change wording in readme 2017-04-23 22:29:23 +02:00
faiface 82fa73952e put images into 2x2 table in readme 2017-04-23 20:57:36 +02:00
faiface bab33976cc add useful links to readme 2017-04-23 20:35:58 +02:00
faiface 0e54ed1080 add missing feature to readme (advanced window manipulation) 2017-04-23 17:48:54 +02:00
faiface db4f08aefc fix typo in readme 2017-04-23 17:30:03 +02:00
faiface fd6e056f92 add missing feature (tests and benchmarks) to readme 2017-04-23 14:35:56 +02:00
faiface e93b9a1d4c add missing feature (mobile/web backend) to readme 2017-04-23 14:32:41 +02:00
faiface d9a94fd157 update a few sentences in readme 2017-04-23 13:54:53 +02:00
faiface 6e5cbaa493 reorder features in readme 2017-04-23 01:30:15 +02:00
faiface 9e150a7a1b add link to "PixelGL backend" in readme 2017-04-22 23:58:13 +02:00
faiface 9c74e66118 reword a sentence in readme 2017-04-22 23:46:08 +02:00
faiface ee74866587 clarify readme 2017-04-22 23:44:17 +02:00
faiface a2ad3dbf7c fix grammar in readme 2017-04-22 23:36:54 +02:00
faiface af1ccf885f add feature to readme 2017-04-22 23:34:34 +02:00
faiface b756edfac6 reorder features in readme 2017-04-22 23:34:06 +02:00
faiface c69ef58898 add feature to readme 2017-04-22 23:33:35 +02:00
faiface 6e63f27d6e fix typo in readme 2017-04-22 23:29:54 +02:00
faiface acfd836a7f change tutorial part in readme 2017-04-22 23:29:05 +02:00
faiface 8679a702a9 add stunning readme (contributing part missing) 2017-04-22 23:27:54 +02:00
faiface e9380d7eed minor change in platformer example readme 2017-04-22 16:17:33 +02:00
faiface 3ed8c0016c minor change in platformer example readme 2017-04-22 15:56:56 +02:00
faiface 2afe44a9c9 add instructions to platformer readme 2017-04-22 14:22:09 +02:00
faiface c028b66b68 adjust camera movement in platformer example 2017-04-22 14:20:51 +02:00
faiface 080735510c temporarily fix issue #1 2017-04-22 13:15:57 +02:00
faiface 862f30a004 fix screenshot in platformer example readme 2017-04-22 13:11:26 +02:00
faiface c0ddb0b287 add platformer example 2017-04-22 13:09:23 +02:00
faiface 3e493c13e1 remove debug print 2017-04-21 23:10:02 +02:00
faiface f9f61911a7 fix Canvas drawing when bounds don't start at (0, 0) 2017-04-21 23:07:48 +02:00
faiface d30b73fb8b minor change in pixel package doc 2017-04-21 17:01:46 +02:00
faiface 4aea56198f add pixelgl package doc 2017-04-21 17:00:18 +02:00
faiface 591fadaaa5 minor change in pixel package doc 2017-04-21 16:57:36 +02:00
faiface 497df7842b add pixel doc 2017-04-21 16:57:08 +02:00
faiface a403cfe50b remove commented code 2017-04-21 16:43:56 +02:00
faiface bdbba8f3c1 add code for the guide 06 2017-04-21 01:42:50 +02:00
faiface c485793a83 fix drawing non-closed lines in IMDraw 2017-04-21 01:17:29 +02:00
faiface 97158ba502 simplify code in IMDraw 2017-04-16 00:59:07 +02:00
faiface 01ff4230da add IMDraw.Rectangle 2017-04-16 00:01:43 +02:00
faiface 37dcef6ab6 minor change 2017-04-15 21:03:22 +02:00
faiface ab9d608c45 fix typo in Alpha doc 2017-04-15 18:04:03 +02:00
faiface 05da8e6f92 minor change in xor example code 2017-04-15 17:16:57 +02:00
faiface 49c2beeca9 fix typo in xor example code 2017-04-15 17:15:12 +02:00
faiface f4ce166964 clarify xor example readme 2017-04-15 17:14:15 +02:00
faiface 93e9ab79b1 add xor example 2017-04-15 17:10:35 +02:00
faiface 58be215a76 update screenshot in smoke example 2017-04-15 16:05:29 +02:00
faiface ce083a9a9f add smoke example 2017-04-15 16:03:27 +02:00
faiface 2ef17e7a95 change window title + update screenshot in lights example 2017-04-15 14:25:38 +02:00
faiface 80f48bc0b6 minor change in lights example code 2017-04-15 14:21:27 +02:00
faiface 420e83bc61 update lights example code 2017-04-15 14:09:06 +02:00
faiface b51b3e8f38 revert previous commit 2017-04-14 22:46:46 +02:00
faiface 620784b34c fix window title in lights example 2017-04-14 22:45:44 +02:00
faiface 368dab0e7c fix screenshot in lights example readme 2017-04-14 22:41:14 +02:00
faiface 9bfb83861a add screenshot to lights example readme 2017-04-14 22:39:15 +02:00
faiface 4e30fb9f5c add lights screenshot 2017-04-14 22:37:41 +02:00
faiface 11c76944b6 minor change in light example readme 2017-04-14 22:29:03 +02:00
faiface 30af7c6612 add lights example readme 2017-04-14 22:28:30 +02:00
faiface 411ad7c27d add lights example 2017-04-14 22:20:12 +02:00
faiface 37b0f0956b add code for part 05 of the guide 2017-04-14 16:03:07 +02:00
faiface 10684b6add minor change 2017-04-13 20:30:32 +02:00
faiface cf666b5866 immediate-like-mode -> immediate-mode-like 2017-04-13 17:44:28 +02:00
faiface f325092c02 add imdraw package doc comment 2017-04-13 17:41:38 +02:00
faiface 939a76923d simplify code in Rect.Resized 2017-04-13 15:26:48 +02:00
faiface fcbc61c570 fix grammar in Vec doc 2017-04-13 15:18:13 +02:00
faiface e55d490801 fix grammar in Drawer doc 2017-04-13 15:15:17 +02:00
faiface b5a2c12175 remove a bunch of unnecessary Window control methods 2017-04-13 15:03:13 +02:00
faiface 14a01a5522 minor optimization in Sprite 2017-04-12 16:18:25 +02:00
faiface 3276c4e4a1 clarify IMDraw.Draw doc 2017-04-12 16:03:36 +02:00
faiface c61b677fa9 fix Canvas.Draw 2017-04-12 16:02:39 +02:00
faiface c9e0f7262d add Canvas.Draw 2017-04-12 16:00:56 +02:00
faiface a2499cf90d remove accidentaly kept updateLock field from GLTriangles 2017-04-12 11:25:11 +02:00
faiface 6d2aeb64e7 fix type in ComposeMethod doc 2017-04-11 17:28:27 +02:00
faiface 34a6d020a2 add ComposeMethod.Compose 2017-04-11 17:15:02 +02:00
faiface 9b624ae466 move ComposeTarget to separate file 2017-04-11 16:45:56 +02:00
faiface f2a0a19f6e fix race condition in GLTriangles 2017-04-11 15:02:58 +02:00
faiface a555999120 adopt RGB and Alpha 2017-04-10 17:25:56 +02:00
faiface aa2c560d4c rename ComposeDst* -> ComposeR* + add ComposePlus, ComposeCopy 2017-04-10 13:59:16 +02:00
faiface 4eca5f2d1e fix compile error 2017-04-10 00:48:17 +02:00
faiface 4374bb7be1 remove smooth argument from Canvas constructor 2017-04-10 00:47:06 +02:00
faiface 3fa31cdab5 fix drawing onto Canvas 2017-04-10 00:41:56 +02:00
faiface 219559cf20 add Window.SetComposeMethod 2017-04-10 00:41:48 +02:00
faiface 3d3f1c6e11 add Canvas.SetComposeMethod 2017-04-10 00:30:50 +02:00
faiface 58e8b8f892 add missing ComposeDstOver mode 2017-04-10 00:25:17 +02:00
faiface 13b9e6aee5 add ComposeTarget interface 2017-04-10 00:20:19 +02:00
faiface 9e8697cdf6 fix spelling in doc 2017-04-09 23:19:30 +02:00
faiface 134fe2bf7b use glhf.BlendFunc 2017-04-09 23:16:34 +02:00
faiface 9df8a3761b adjust RGB doc 2017-04-09 23:08:35 +02:00
faiface a081d5b0c8 add RGB and Alpha functions 2017-04-09 23:03:30 +02:00
faiface 32ae09e1e5 replace NRGBA to RGBA because Porter-Duff (!!!) 2017-04-09 22:00:26 +02:00
faiface 317cfb17b0 add code for 04 guide 2017-04-09 15:32:46 +02:00
faiface a9e6f41315 fix obsolete Batch doc 2017-04-09 15:32:31 +02:00
faiface 9cbce8f638 fix typo in Window doc 2017-04-08 18:05:50 +02:00
faiface e3f7901f2c adjust WindowConfig doc, more consistent with the rest 2017-04-07 12:34:16 +02:00
faiface ee19c6b361 fix GLFrame.SetBounds to not reallocate when not necessary 2017-04-05 23:20:55 +02:00
faiface 5ad013b286 fix Matrix.String 2017-04-04 14:10:39 +02:00
faiface 7fdf8c0d60 add Matrix.String 2017-04-04 14:08:37 +02:00
faiface 3a25f78b1a minor change 2017-04-04 14:02:39 +02:00
faiface ad733f5946 update code for 03 guide 2017-04-03 17:38:12 +02:00
faiface 8d5879070f add code for 03_moving_scaling_and_rotating guide 2017-04-03 13:52:52 +02:00
faiface 2155babc5d add Window.Color 2017-04-02 19:08:48 +02:00
faiface dc9afb5557 add code for 02_drawing_a_sprite guide 2017-04-02 19:07:28 +02:00
faiface 79f7f4fb42 split Canvas into Canvas+GLFrame + add GLPicture 2017-04-01 21:54:44 +02:00
faiface 2562f6b754 remove Slice and Original from Canvas 2017-03-31 15:03:06 +02:00
faiface b138fc5d5b make Sprite accept Picture and frame 2017-03-31 15:00:59 +02:00
faiface 15a270e689 remove Slice and Original from Picture interface 2017-03-30 23:34:07 +02:00
faiface 538ad90185 add code for 01_creating_a_window guide 2017-03-27 00:57:13 +02:00
114 changed files with 4687 additions and 4424 deletions

View File

@ -1,6 +1,4 @@
language: go
# https://github.com/golang/go/issues/31293
dist: xenial
sudo: false
addons:
apt:
@ -13,22 +11,13 @@ 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:
- # 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.
- go get -t ./...
script:
- go test -v -race -mod=readonly ./...
- go test -i -race ./...
- go test -v -race ./...

View File

@ -1,63 +0,0 @@
# 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,13 +4,11 @@
## Here are a few ways you can contribute
1. **Make a community example** and place it inside the `community` folder of the [examples repository][examples].
1. **Make a community example** and place it inside the [examples/community](examples/community) folder.
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 on our [Discord Server](https://discord.gg/q2DK4MP), where we can discuss bigger changes and implement them after that.
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.
## 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,12 +1,5 @@
# \*\*\*\*\*NOTICE\*\*\*\*\*
# 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)
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.
@ -15,18 +8,8 @@ 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
@ -39,33 +22,32 @@ covering several topics of Pixel. Here's the content of the tutorial parts so fa
- [Drawing efficiently with Batch](https://github.com/faiface/pixel/wiki/Drawing-efficiently-with-Batch)
- [Drawing shapes with IMDraw](https://github.com/faiface/pixel/wiki/Drawing-shapes-with-IMDraw)
- [Typing text on the screen](https://github.com/faiface/pixel/wiki/Typing-text-on-the-screen)
- [Using a custom fragment shader](https://github.com/faiface/pixel/wiki/Using-a-custom-fragment-shader)
## [Examples](https://github.com/faiface/pixel-examples)
## Examples
The [examples](https://github.com/faiface/pixel-examples) repository contains a few
The [examples](https://github.com/faiface/pixel/tree/master/examples) directory contains a few
examples demonstrating Pixel's functionality.
**To run an example**, navigate to it's directory, then `go run` the `main.go` file. For example:
```
$ cd pixel-examples/platformer
$ cd examples/platformer
$ go run main.go
```
Here are some screenshots from the examples!
| [Lights](https://github.com/faiface/pixel-examples/blob/master/lights) | [Platformer](https://github.com/faiface/pixel-examples/blob/master/platformer) |
| [Lights](examples/lights) | [Platformer](examples/platformer) |
| --- | --- |
| ![Lights](https://github.com/faiface/pixel-examples/blob/master/lights/screenshot.png) | ![Platformer](https://github.com/faiface/pixel-examples/blob/master/platformer/screenshot.png) |
| ![Lights](examples/lights/screenshot.png) | ![Platformer](examples/platformer/screenshot.png) |
| [Smoke](https://github.com/faiface/pixel-examples/blob/master/smoke) | [Typewriter](https://github.com/faiface/pixel-examples/blob/master/typewriter) |
| [Smoke](examples/smoke) | [Typewriter](examples/typewriter) |
| --- | --- |
| ![Smoke](https://github.com/faiface/pixel-examples/blob/master/smoke/screenshot.png) | ![Typewriter](https://github.com/faiface/pixel-examples/blob/master/typewriter/screenshot.png) |
| ![Smoke](examples/smoke/screenshot.png) | ![Typewriter](examples/typewriter/screenshot.png) |
| [Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster) | [Gizmo](https://github.com/Lallassu/gizmo) |
| [Raycaster](examples/community/raycaster) | [Starfield](examples/community/starfield) |
| --- | --- |
| ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![Gizmo](https://github.com/Lallassu/gizmo/blob/master/preview.png) |
| ![Raycaster](examples/community/raycaster/screenshot.png) | ![Starfield](examples/community/starfield/screenshot.png) |
## Features
@ -111,14 +93,6 @@ 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.
@ -129,9 +103,8 @@ 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)~~ (solved with the addition of GLSL effects)
- More advanced graphical effects (e.g. blur)
- 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!
@ -152,7 +125,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 libXxf86vm-devel` packages.
libXinerama-devel mesa-libGL-devel libXi-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
@ -161,8 +134,6 @@ 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
@ -180,6 +151,10 @@ 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?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
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, Cached: true}}
b := &Batch{cont: Drawer{Triangles: container, Picture: pic}}
b.SetMatrix(IM)
b.SetColorMask(Alpha(1))
return b

334
circle.go
View File

@ -1,334 +0,0 @@
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}
}

View File

@ -1,466 +0,0 @@
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])
}
}
})
}
}

49
data.go
View File

@ -8,16 +8,6 @@ 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 {
@ -25,8 +15,6 @@ type TrianglesData []struct {
Color RGBA
Picture Vec
Intensity float64
ClipRect Rect
IsClipped bool
}
// MakeTrianglesData creates TrianglesData of length len initialized with default property values.
@ -34,11 +22,9 @@ 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 := make(TrianglesData, len)
for i := 0; i < len; i++ {
td[i] = zeroValueTriangleData
}
return &td
td := &TrianglesData{}
td.SetLen(len)
return td
}
// Len returns the number of vertices in TrianglesData.
@ -54,7 +40,12 @@ func (td *TrianglesData) SetLen(len int) {
if len > td.Len() {
needAppend := len - td.Len()
for i := 0; i < needAppend; i++ {
*td = append(*td, zeroValueTriangleData)
*td = append(*td, struct {
Position Vec
Color RGBA
Picture Vec
Intensity float64
}{Color: RGBA{1, 1, 1, 1}})
}
}
if len < td.Len() {
@ -91,11 +82,6 @@ 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.
@ -110,9 +96,10 @@ func (td *TrianglesData) Update(t Triangles) {
// Copy returns an exact independent copy of this TrianglesData.
func (td *TrianglesData) Copy() Triangles {
copyTd := MakeTrianglesData(td.Len())
copyTd := TrianglesData{}
copyTd.SetLen(td.Len())
copyTd.Update(td)
return copyTd
return &copyTd
}
// Position returns the position property of i-th vertex.
@ -130,11 +117,6 @@ 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.
//
@ -183,8 +165,13 @@ func verticalFlip(rgba *image.RGBA) {
//
// The resulting PictureData's Bounds will be the equivalent of the supplied image.Image's Bounds.
func PictureDataFromImage(img image.Image) *PictureData {
rgba := image.NewRGBA(img.Bounds())
var rgba *image.RGBA
if rgbaImg, ok := img.(*image.RGBA); ok {
rgba = rgbaImg
} else {
rgba = image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)
}
verticalFlip(rgba)

View File

@ -1,300 +0,0 @@
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,10 +23,8 @@ package pixel
type Drawer struct {
Triangles Triangles
Picture Picture
Cached bool
targets map[Target]*drawerTarget
allTargets []*drawerTarget
inited bool
}
@ -48,7 +46,7 @@ func (d *Drawer) lazyInit() {
func (d *Drawer) Dirty() {
d.lazyInit()
for _, t := range d.allTargets {
for _, t := range d.targets {
t.clean = false
}
}
@ -70,7 +68,6 @@ 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 {
@ -92,11 +89,8 @@ 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

@ -0,0 +1,16 @@
# bouncing
Bouncing particles using the [imdraw](https://godoc.org/github.com/faiface/pixel/imdraw) package.
Made by [Peter Hellberg](https://github.com/peterhellberg/) as part of his [pixel-experiments](https://github.com/peterhellberg/pixel-experiments)
## Screenshots
![bouncing animation](https://user-images.githubusercontent.com/565124/32401910-7cd87fb2-c119-11e7-8121-7fb46e5e11a8.gif)
![bouncing screenshot](screenshot.png)
## Links
- https://github.com/peterhellberg/pixel-experiments/tree/master/bouncing
- https://gist.github.com/peterhellberg/674f32a15a7d2d249e634ce781f333e8

View File

@ -0,0 +1,303 @@
package main
import (
"image/color"
"math"
"math/rand"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
)
var (
w, h, s, scale = float64(640), float64(360), float64(2.3), float64(32)
p, bg = newPalette(Colors), color.RGBA{32, p.color().G, 32, 255}
balls = []*ball{
newRandomBall(scale),
newRandomBall(scale),
}
)
func run() {
win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
Bounds: pixel.R(0, 0, w, h),
VSync: true,
Undecorated: true,
})
if err != nil {
panic(err)
}
imd := imdraw.New(nil)
imd.EndShape = imdraw.RoundEndShape
imd.Precision = 3
go func() {
start := time.Now()
for range time.Tick(16 * time.Millisecond) {
bg = color.RGBA{32 + (p.color().R/128)*4, 32 + (p.color().G/128)*4, 32 + (p.color().B/128)*4, 255}
s = pixel.V(math.Sin(time.Since(start).Seconds())*0.8, 0).Len()*2 - 1
scale = 64 + 15*s
imd.Intensity = 1.2 * s
}
}()
for !win.Closed() {
win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
if win.JustPressed(pixelgl.KeySpace) {
for _, ball := range balls {
ball.color = ball.palette.next()
}
}
if win.JustPressed(pixelgl.KeyEnter) {
for _, ball := range balls {
ball.pos = center()
ball.vel = randomVelocity()
}
}
imd.Clear()
for _, ball := range balls {
imd.Color = ball.color
imd.Push(ball.pos)
}
imd.Polygon(scale)
for _, ball := range balls {
imd.Color = color.RGBA{ball.color.R, ball.color.G, ball.color.B, 128 - uint8(128*s)}
imd.Push(ball.pos)
}
imd.Polygon(scale * s)
for _, ball := range balls {
aliveParticles := []*particle{}
for _, particle := range ball.particles {
if particle.life > 0 {
aliveParticles = append(aliveParticles, particle)
}
}
for _, particle := range aliveParticles {
imd.Color = particle.color
imd.Push(particle.pos)
imd.Circle(16*particle.life, 0)
}
}
win.Clear(bg)
imd.Draw(win)
win.Update()
}
}
func main() {
rand.Seed(4)
go func() {
for range time.Tick(32 * time.Millisecond) {
for _, ball := range balls {
go ball.update()
for _, particle := range ball.particles {
go particle.update()
}
}
}
}()
pixelgl.Run(run)
}
func newParticleAt(pos, vel pixel.Vec) *particle {
c := p.color()
c.A = 5
return &particle{pos, vel, c, rand.Float64() * 1.5}
}
func newRandomBall(radius float64) *ball {
return &ball{
center(), randomVelocity(),
math.Pi * (radius * radius),
radius, p.random(), p, []*particle{},
}
}
func center() pixel.Vec {
return pixel.V(w/2, h/2)
}
func randomVelocity() pixel.Vec {
return pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale / 4)
}
type particle struct {
pos pixel.Vec
vel pixel.Vec
color color.RGBA
life float64
}
func (p *particle) update() {
p.pos = p.pos.Add(p.vel)
p.life -= 0.03
switch {
case p.pos.Y < 0 || p.pos.Y >= h:
p.vel.Y *= -1.0
case p.pos.X < 0 || p.pos.X >= w:
p.vel.X *= -1.0
}
}
type ball struct {
pos pixel.Vec
vel pixel.Vec
mass float64
radius float64
color color.RGBA
palette *Palette
particles []*particle
}
func (b *ball) update() {
b.pos = b.pos.Add(b.vel)
var bounced bool
switch {
case b.pos.Y <= b.radius || b.pos.Y >= h-b.radius:
b.vel.Y *= -1.0
bounced = true
if b.pos.Y < b.radius {
b.pos.Y = b.radius
} else {
b.pos.Y = h - b.radius
}
case b.pos.X <= b.radius || b.pos.X >= w-b.radius:
b.vel.X *= -1.0
bounced = true
if b.pos.X < b.radius {
b.pos.X = b.radius
} else {
b.pos.X = w - b.radius
}
}
for _, a := range balls {
if a != b {
d := a.pos.Sub(b.pos)
if d.Len() > a.radius+b.radius {
continue
}
pen := d.Unit().Scaled(a.radius + b.radius - d.Len())
a.pos = a.pos.Add(pen.Scaled(b.mass / (a.mass + b.mass)))
b.pos = b.pos.Sub(pen.Scaled(a.mass / (a.mass + b.mass)))
u := d.Unit()
v := 2 * (a.vel.Dot(u) - b.vel.Dot(u)) / (a.mass + b.mass)
a.vel = a.vel.Sub(u.Scaled(v * b.mass))
b.vel = b.vel.Add(u.Scaled(v * a.mass))
bounced = true
}
}
if bounced {
b.color = p.next()
b.particles = append(b.particles,
newParticleAt(b.pos, b.vel.Rotated(1).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(2).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(3).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(4).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(5).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(6).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(7).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(8).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(9).Scaled(rand.Float64())),
newParticleAt(b.pos, b.vel.Rotated(10).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(20).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(30).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(40).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(50).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(60).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(70).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(80).Scaled(rand.Float64()+1)),
newParticleAt(b.pos, b.vel.Rotated(90).Scaled(rand.Float64()+1)),
)
}
}
func newPalette(cc []color.Color) *Palette {
colors := []color.RGBA{}
for _, v := range cc {
if c, ok := v.(color.RGBA); ok {
colors = append(colors, c)
}
}
return &Palette{colors, len(colors), 0}
}
type Palette struct {
colors []color.RGBA
size int
index int
}
func (p *Palette) clone() *Palette {
return &Palette{p.colors, p.size, p.index}
}
func (p *Palette) next() color.RGBA {
if p.index++; p.index >= p.size {
p.index = 0
}
return p.colors[p.index]
}
func (p *Palette) color() color.RGBA {
return p.colors[p.index]
}
func (p *Palette) random() color.RGBA {
p.index = rand.Intn(p.size)
return p.colors[p.index]
}
var Colors = []color.Color{
color.RGBA{190, 38, 51, 255},
color.RGBA{224, 111, 139, 255},
color.RGBA{73, 60, 43, 255},
color.RGBA{164, 100, 34, 255},
color.RGBA{235, 137, 49, 255},
color.RGBA{247, 226, 107, 255},
color.RGBA{47, 72, 78, 255},
color.RGBA{68, 137, 26, 255},
color.RGBA{163, 206, 39, 255},
color.RGBA{0, 87, 132, 255},
color.RGBA{49, 162, 242, 255},
color.RGBA{178, 220, 239, 255},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,20 @@
# Conway's Game of Lfe
Created by [Nathan Leniz](https://github.com/terakilobyte).
Inspired by and heavily uses [the doc](https://golang.org/doc/play/life.go)
> The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970. The "game" is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves, or, for advanced "players", by creating patterns with particular properties. The Game has been reprogrammed multiple times in various coding languages.
For more information, please see the [wikipedia](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) article.
## Use
go run main.go -h
-frameRate duration
The framerate in milliseconds (default 33ms)
-size int
The size of each cell (default 5)
-windowSize float
The pixel size of one side of the grid (default 800)
![life](life.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,74 @@
package life
import (
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"golang.org/x/image/colornames"
)
// Shamelessly taken/inspired by https://golang.org/doc/play/life.go
// Grid is the structure in which the cellular automota live
type Grid struct {
h int
cellSize int
Cells [][]bool
}
// NewGrid constructs a new Grid
func NewGrid(h, size int) *Grid {
cells := make([][]bool, h)
for i := 0; i < h; i++ {
cells[i] = make([]bool, h)
}
return &Grid{h: h, cellSize: size, Cells: cells}
}
// Alive returns whether the specified position is alive
func (g *Grid) Alive(x, y int) bool {
x += g.h
x %= g.h
y += g.h
y %= g.h
return g.Cells[y][x]
}
// Set sets the state of a specific location
func (g *Grid) Set(x, y int, state bool) {
g.Cells[y][x] = state
}
// Draw draws the grid
func (g *Grid) Draw(imd *imdraw.IMDraw) {
for i := 0; i < g.h; i++ {
for j := 0; j < g.h; j++ {
if g.Alive(i, j) {
imd.Color = colornames.Black
} else {
imd.Color = colornames.White
}
imd.Push(
pixel.V(float64(i*g.cellSize), float64(j*g.cellSize)),
pixel.V(float64(i*g.cellSize+g.cellSize), float64(j*g.cellSize+g.cellSize)),
)
imd.Rectangle(0)
}
}
}
// Next returns the next state
func (g *Grid) Next(x, y int) bool {
// Count the adjacent cells that are alive.
alive := 0
for i := -1; i <= 1; i++ {
for j := -1; j <= 1; j++ {
if (j != 0 || i != 0) && g.Alive(x+i, y+j) {
alive++
}
}
}
// Return next state according to the game rules:
// exactly 3 neighbors: on,
// exactly 2 neighbors: maintain current state,
// otherwise: off.
return alive == 3 || alive == 2 && g.Alive(x, y)
}

View File

@ -0,0 +1,35 @@
// Package life manages the "game" state
// Shamelessly taken from https://golang.org/doc/play/life.go
package life
import "math/rand"
// Life stores the state of a round of Conway's Game of Life.
type Life struct {
A, b *Grid
h int
}
// NewLife returns a new Life game state with a random initial state.
func NewLife(h, size int) *Life {
a := NewGrid(h, size)
for i := 0; i < (h * h / 2); i++ {
a.Set(rand.Intn(h), rand.Intn(h), true)
}
return &Life{
A: a, b: NewGrid(h, size),
h: h,
}
}
// Step advances the game by one instant, recomputing and updating all cells.
func (l *Life) Step() {
// Update the state of the next field (b) from the current field (a).
for y := 0; y < l.h; y++ {
for x := 0; x < l.h; x++ {
l.b.Set(x, y, l.A.Next(x, y))
}
}
// Swap fields a and b.
l.A, l.b = l.b, l.A
}

View File

@ -0,0 +1,64 @@
package main
import (
"flag"
"math/rand"
"time"
"golang.org/x/image/colornames"
"github.com/faiface/pixel"
"github.com/faiface/pixel/examples/community/game_of_life/life"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
)
var (
size *int
windowSize *float64
frameRate *time.Duration
)
func init() {
rand.Seed(time.Now().UnixNano())
size = flag.Int("size", 5, "The size of each cell")
windowSize = flag.Float64("windowSize", 800, "The pixel size of one side of the grid")
frameRate = flag.Duration("frameRate", 33*time.Millisecond, "The framerate in milliseconds")
flag.Parse()
}
func main() {
pixelgl.Run(run)
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, *windowSize, *windowSize),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
win.Clear(colornames.White)
// since the game board is square, rows and cols will be the same
rows := int(*windowSize) / *size
gridDraw := imdraw.New(nil)
game := life.NewLife(rows, *size)
tick := time.Tick(*frameRate)
for !win.Closed() {
// game loop
select {
case <-tick:
gridDraw.Clear()
game.A.Draw(gridDraw)
gridDraw.Draw(win)
game.Step()
}
win.Update()
}
}

View File

@ -0,0 +1,13 @@
# Isometric view basics
Created by [Sergio Vera](https://github.com/svera).
Isometric view is a display method used to create an illusion of 3D for an otherwise 2D game - sometimes referred to as pseudo 3D or 2.5D.
Implementing an isometric view can be done in many ways, but for the sake of simplicity we'll implement a tile-based approach, which is the most efficient and widely used method.
In the tile-based approach, each visual element is broken down into smaller pieces, called tiles, of a standard size. These tiles will be arranged to form the game world according to pre-determined level data - usually a 2D array.
For a detailed explanation about the maths behind this, read [http://clintbellanger.net/articles/isometric_math/](http://clintbellanger.net/articles/isometric_math/).
![Result](result.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,102 @@
package main
import (
"image"
"os"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
_ "image/png"
)
const (
windowWidth = 800
windowHeight = 800
// sprite tiles are squared, 64x64 size
tileSize = 64
f = 0 // floor identifier
w = 1 // wall identifier
)
var levelData = [][]uint{
{f, f, f, f, f, f}, // This row will be rendered in the lower left part of the screen (closer to the viewer)
{w, f, f, f, f, w},
{w, f, f, f, f, w},
{w, f, f, f, f, w},
{w, f, f, f, f, w},
{w, w, w, w, w, w}, // And this in the upper right
}
var win *pixelgl.Window
var offset = pixel.V(400, 325)
var floorTile, wallTile *pixel.Sprite
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
func run() {
var err error
cfg := pixelgl.WindowConfig{
Title: "Isometric demo",
Bounds: pixel.R(0, 0, windowWidth, windowHeight),
VSync: true,
}
win, err = pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
pic, err := loadPicture("castle.png")
if err != nil {
panic(err)
}
wallTile = pixel.NewSprite(pic, pixel.R(0, 448, tileSize, 512))
floorTile = pixel.NewSprite(pic, pixel.R(0, 128, tileSize, 192))
depthSort()
for !win.Closed() {
win.Update()
}
}
// Draw level data tiles to window, from farthest to closest.
// In order to achieve the depth effect, we need to render tiles up to down, being lower
// closer to the viewer (see painter's algorithm). To do that, we need to process levelData in reverse order,
// so its first row is rendered last, as OpenGL considers its origin to be in the lower left corner of the display.
func depthSort() {
for x := len(levelData) - 1; x >= 0; x-- {
for y := len(levelData[x]) - 1; y >= 0; y-- {
isoCoords := cartesianToIso(pixel.V(float64(x), float64(y)))
mat := pixel.IM.Moved(offset.Add(isoCoords))
// Not really needed, just put to show bigger blocks
mat = mat.ScaledXY(win.Bounds().Center(), pixel.V(2, 2))
tileType := levelData[x][y]
if tileType == f {
floorTile.Draw(win, mat)
} else {
wallTile.Draw(win, mat)
}
}
}
}
func cartesianToIso(pt pixel.Vec) pixel.Vec {
return pixel.V((pt.X-pt.Y)*(tileSize/2), (pt.X+pt.Y)*(tileSize/4))
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 stephen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,19 @@
# Maze generator in Go
Created by [Stephen Chavez](https://github.com/redragonx)
This uses the game engine: Pixel. Install it here: https://github.com/faiface/pixel
I made this to improve my understanding of Go and some game concepts with some basic maze generating algorithms.
Controls: Press 'R' to restart the maze.
Optional command-line arguments: `go run ./maze-generator.go`
- `-w` sets the maze's width in pixels.
- `-h` sets the maze's height in pixels.
- `-c` sets the maze cell's size in pixels.
Code based on the Recursive backtracker algorithm.
- https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
![Screenshot](screenshot.png)

View File

@ -0,0 +1,317 @@
package main
// Code based on the Recursive backtracker algorithm.
// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
// See https://youtu.be/HyK_Q5rrcr4 as an example
// YouTube example ported to Go for the Pixel library.
// Created by Stephen Chavez
import (
"crypto/rand"
"errors"
"flag"
"fmt"
"math/big"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/examples/community/maze/stack"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
"github.com/pkg/profile"
"golang.org/x/image/colornames"
)
var visitedColor = pixel.RGB(0.5, 0, 1).Mul(pixel.Alpha(0.35))
var hightlightColor = pixel.RGB(0.3, 0, 0).Mul(pixel.Alpha(0.45))
var debug = false
type cell struct {
walls [4]bool // Wall order: top, right, bottom, left
row int
col int
visited bool
}
func (c *cell) Draw(imd *imdraw.IMDraw, wallSize int) {
drawCol := c.col * wallSize // x
drawRow := c.row * wallSize // y
imd.Color = colornames.White
if c.walls[0] {
// top line
imd.Push(pixel.V(float64(drawCol), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow)))
imd.Line(3)
}
if c.walls[1] {
// right Line
imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)))
imd.Line(3)
}
if c.walls[2] {
// bottom line
imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow+wallSize)))
imd.Line(3)
}
if c.walls[3] {
// left line
imd.Push(pixel.V(float64(drawCol), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow)))
imd.Line(3)
}
imd.EndShape = imdraw.SharpEndShape
if c.visited {
imd.Color = visitedColor
imd.Push(pixel.V(float64(drawCol), (float64(drawRow))), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)))
imd.Rectangle(0)
}
}
func (c *cell) GetNeighbors(grid []*cell, cols int, rows int) ([]*cell, error) {
neighbors := []*cell{}
j := c.row
i := c.col
top, _ := getCellAt(i, j-1, cols, rows, grid)
right, _ := getCellAt(i+1, j, cols, rows, grid)
bottom, _ := getCellAt(i, j+1, cols, rows, grid)
left, _ := getCellAt(i-1, j, cols, rows, grid)
if top != nil && !top.visited {
neighbors = append(neighbors, top)
}
if right != nil && !right.visited {
neighbors = append(neighbors, right)
}
if bottom != nil && !bottom.visited {
neighbors = append(neighbors, bottom)
}
if left != nil && !left.visited {
neighbors = append(neighbors, left)
}
if len(neighbors) == 0 {
return nil, errors.New("We checked all cells...")
}
return neighbors, nil
}
func (c *cell) GetRandomNeighbor(grid []*cell, cols int, rows int) (*cell, error) {
neighbors, err := c.GetNeighbors(grid, cols, rows)
if neighbors == nil {
return nil, err
}
nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(neighbors))))
if err != nil {
panic(err)
}
randomIndex := nBig.Int64()
return neighbors[randomIndex], nil
}
func (c *cell) hightlight(imd *imdraw.IMDraw, wallSize int) {
x := c.col * wallSize
y := c.row * wallSize
imd.Color = hightlightColor
imd.Push(pixel.V(float64(x), float64(y)), pixel.V(float64(x+wallSize), float64(y+wallSize)))
imd.Rectangle(0)
}
func newCell(col int, row int) *cell {
newCell := new(cell)
newCell.row = row
newCell.col = col
for i := range newCell.walls {
newCell.walls[i] = true
}
return newCell
}
// Creates the inital maze slice for use.
func initGrid(cols, rows int) []*cell {
grid := []*cell{}
for j := 0; j < rows; j++ {
for i := 0; i < cols; i++ {
newCell := newCell(i, j)
grid = append(grid, newCell)
}
}
return grid
}
func setupMaze(cols, rows int) ([]*cell, *stack.Stack, *cell) {
// Make an empty grid
grid := initGrid(cols, rows)
backTrackStack := stack.NewStack(len(grid))
currentCell := grid[0]
return grid, backTrackStack, currentCell
}
func cellIndex(i, j, cols, rows int) int {
if i < 0 || j < 0 || i > cols-1 || j > rows-1 {
return -1
}
return i + j*cols
}
func getCellAt(i int, j int, cols int, rows int, grid []*cell) (*cell, error) {
possibleIndex := cellIndex(i, j, cols, rows)
if possibleIndex == -1 {
return nil, fmt.Errorf("cellIndex: CellIndex is a negative number %d", possibleIndex)
}
return grid[possibleIndex], nil
}
func removeWalls(a *cell, b *cell) {
x := a.col - b.col
if x == 1 {
a.walls[3] = false
b.walls[1] = false
} else if x == -1 {
a.walls[1] = false
b.walls[3] = false
}
y := a.row - b.row
if y == 1 {
a.walls[0] = false
b.walls[2] = false
} else if y == -1 {
a.walls[2] = false
b.walls[0] = false
}
}
func run() {
// unsiged integers, because easier parsing error checks.
// We must convert these to intergers, as done below...
uScreenWidth, uScreenHeight, uWallSize := parseArgs()
var (
// In pixels
// Defualt is 800x800x40 = 20x20 wallgrid
screenWidth = int(uScreenWidth)
screenHeight = int(uScreenHeight)
wallSize = int(uWallSize)
frames = 0
second = time.Tick(time.Second)
grid = []*cell{}
cols = screenWidth / wallSize
rows = screenHeight / wallSize
currentCell = new(cell)
backTrackStack = stack.NewStack(1)
)
// Set game FPS manually
fps := time.Tick(time.Second / 60)
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks! - Maze example",
Bounds: pixel.R(0, 0, float64(screenHeight), float64(screenWidth)),
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
grid, backTrackStack, currentCell = setupMaze(cols, rows)
gridIMDraw := imdraw.New(nil)
for !win.Closed() {
if win.JustReleased(pixelgl.KeyR) {
fmt.Println("R pressed")
grid, backTrackStack, currentCell = setupMaze(cols, rows)
}
win.Clear(colornames.Gray)
gridIMDraw.Clear()
for i := range grid {
grid[i].Draw(gridIMDraw, wallSize)
}
// step 1
// Make the initial cell the current cell and mark it as visited
currentCell.visited = true
currentCell.hightlight(gridIMDraw, wallSize)
// step 2.1
// If the current cell has any neighbours which have not been visited
// Choose a random unvisited cell
nextCell, _ := currentCell.GetRandomNeighbor(grid, cols, rows)
if nextCell != nil && !nextCell.visited {
// step 2.2
// Push the current cell to the stack
backTrackStack.Push(currentCell)
// step 2.3
// Remove the wall between the current cell and the chosen cell
removeWalls(currentCell, nextCell)
// step 2.4
// Make the chosen cell the current cell and mark it as visited
nextCell.visited = true
currentCell = nextCell
} else if backTrackStack.Len() > 0 {
currentCell = backTrackStack.Pop().(*cell)
}
gridIMDraw.Draw(win)
win.Update()
<-fps
updateFPSDisplay(win, &cfg, &frames, grid, second)
}
}
// Parses the maze arguments, all of them are optional.
// Uses uint as implicit error checking :)
func parseArgs() (uint, uint, uint) {
var mazeWidthPtr = flag.Uint("w", 800, "w sets the maze's width in pixels.")
var mazeHeightPtr = flag.Uint("h", 800, "h sets the maze's height in pixels.")
var wallSizePtr = flag.Uint("c", 40, "c sets the maze cell's size in pixels.")
flag.Parse()
// If these aren't default values AND if they're not the same values.
// We should warn the user that the maze will look funny.
if *mazeWidthPtr != 800 || *mazeHeightPtr != 800 {
if *mazeWidthPtr != *mazeHeightPtr {
fmt.Printf("WARNING: maze width: %d and maze height: %d don't match. \n", *mazeWidthPtr, *mazeHeightPtr)
fmt.Println("Maze will look funny because the maze size is bond to the window size!")
}
}
return *mazeWidthPtr, *mazeHeightPtr, *wallSizePtr
}
func updateFPSDisplay(win *pixelgl.Window, cfg *pixelgl.WindowConfig, frames *int, grid []*cell, second <-chan time.Time) {
*frames++
select {
case <-second:
win.SetTitle(fmt.Sprintf("%s | FPS: %d with %d Cells", cfg.Title, *frames, len(grid)))
*frames = 0
default:
}
}
func main() {
if debug {
defer profile.Start().Stop()
}
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,86 @@
package stack
type Stack struct {
top *Element
size int
max int
}
type Element struct {
value interface{}
next *Element
}
func NewStack(max int) *Stack {
return &Stack{max: max}
}
// Return the stack's length
func (s *Stack) Len() int {
return s.size
}
// Return the stack's max
func (s *Stack) Max() int {
return s.max
}
// Push a new element onto the stack
func (s *Stack) Push(value interface{}) {
if s.size+1 > s.max {
if last := s.PopLast(); last == nil {
panic("Unexpected nil in stack")
}
}
s.top = &Element{value, s.top}
s.size++
}
// Remove the top element from the stack and return it's value
// If the stack is empty, return nil
func (s *Stack) Pop() (value interface{}) {
if s.size > 0 {
value, s.top = s.top.value, s.top.next
s.size--
return
}
return nil
}
func (s *Stack) PopLast() (value interface{}) {
if lastElem := s.popLast(s.top); lastElem != nil {
return lastElem.value
}
return nil
}
//Peek returns a top without removing it from list
func (s *Stack) Peek() (value interface{}, exists bool) {
exists = false
if s.size > 0 {
value = s.top.value
exists = true
}
return
}
func (s *Stack) popLast(elem *Element) *Element {
if elem == nil {
return nil
}
// not last because it has next and a grandchild
if elem.next != nil && elem.next.next != nil {
return s.popLast(elem.next)
}
// current elem is second from bottom, as next elem has no child
if elem.next != nil && elem.next.next == nil {
last := elem.next
// make current elem bottom of stack by removing its next element
elem.next = nil
s.size--
return last
}
return nil
}

View File

@ -0,0 +1,9 @@
# Parallax scrolling demo
Created by [Sergio Vera](https://github.com/svera)
This example shows how to implement an infinite side scrolling background with a depth effect, using [parallax scrolling](https://en.wikipedia.org/wiki/Parallax_scrolling). Code is based in the [infinite scrolling background](https://github.com/faiface/pixel/tree/master/examples/community/scrolling-background) demo.
Credits to [Peter Hellberg](https://github.com/peterhellberg) for the improved background images.
![Parallax scrolling background](result.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,74 @@
package main
import (
"image"
"os"
"time"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
const (
windowWidth = 600
windowHeight = 450
foregroundHeight = 149
// This is the scrolling speed (pixels per second)
// Negative values will make background to scroll to the left,
// positive to the right.
backgroundSpeed = -60
foregroundSpeed = -120
)
func run() {
cfg := pixelgl.WindowConfig{
Title: "Parallax scrolling demo",
Bounds: pixel.R(0, 0, windowWidth, windowHeight),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
// Pic must have double the width of the window, as it will scroll to the left or right
picBackground, err := loadPicture("background.png")
if err != nil {
panic(err)
}
picForeground, err := loadPicture("foreground.png")
if err != nil {
panic(err)
}
background := NewScrollingBackground(picBackground, windowWidth, windowHeight, backgroundSpeed)
foreground := NewScrollingBackground(picForeground, windowWidth, foregroundHeight, foregroundSpeed)
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
background.Update(win, dt)
foreground.Update(win, dt)
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -0,0 +1,64 @@
package main
import (
"math"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)
// ScrollingBackground stores all needed information to scroll a background
// to the left or right
type ScrollingBackground struct {
width float64
height float64
displacement float64
speed float64
backgrounds [2]*pixel.Sprite
positions [2]pixel.Vec
}
// NewScrollingBackground construct and returns a new instance of scrollingBackground,
// positioning the background images according to the speed value
func NewScrollingBackground(pic pixel.Picture, width, height, speed float64) *ScrollingBackground {
sb := &ScrollingBackground{
width: width,
height: height,
speed: speed,
backgrounds: [2]*pixel.Sprite{
pixel.NewSprite(pic, pixel.R(0, 0, width, height)),
pixel.NewSprite(pic, pixel.R(width, 0, width*2, height)),
},
}
sb.positionImages()
return sb
}
// If scrolling speed > 0, put second background image ouside the screen,
// at the left side, otherwise put it at the right side.
func (sb *ScrollingBackground) positionImages() {
if sb.speed > 0 {
sb.positions = [2]pixel.Vec{
pixel.V(sb.width/2, sb.height/2),
pixel.V((sb.width/2)-sb.width, sb.height/2),
}
} else {
sb.positions = [2]pixel.Vec{
pixel.V(sb.width/2, sb.height/2),
pixel.V(sb.width+(sb.width/2), sb.height/2),
}
}
}
// Update will move backgrounds certain pixels, depending of the amount of time passed
func (sb *ScrollingBackground) Update(win *pixelgl.Window, dt float64) {
if math.Abs(sb.displacement) >= sb.width {
sb.displacement = 0
sb.positions[0], sb.positions[1] = sb.positions[1], sb.positions[0]
}
d := pixel.V(sb.displacement, 0)
sb.backgrounds[0].Draw(win, pixel.IM.Moved(sb.positions[0].Add(d)))
sb.backgrounds[1].Draw(win, pixel.IM.Moved(sb.positions[1].Add(d)))
sb.displacement += sb.speed * dt
}

View File

@ -0,0 +1,12 @@
# Procedural 1D terrain generator
Created by [Sergio Vera](https://github.com/svera).
This is a demo of a 1D terrain generator using [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise) algorithm.
Press *space* to generate a random terrain.
Uses [Go-Perlin](https://github.com/aquilax/go-perlin).
Texture by [hh316](https://hhh316.deviantart.com/art/Seamless-stone-cliff-face-mountain-texture-377076626).
![Randomly generated 1D terrain](result.png)

View File

@ -0,0 +1,104 @@
package main
import (
"image"
"math/rand"
"os"
"time"
_ "image/jpeg"
perlin "github.com/aquilax/go-perlin"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
const (
width = 800
height = 600
// Top of the mountain must be around the half of the screen height
verticalOffset = height / 2
// Perlin noise provides variations in values between -1 and 1,
// we multiply those so they're visible on screen
scale = 100
waveLength = 100
alpha = 2.
beta = 2.
n = 3
maximumSeedValue = 100
)
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
func main() {
pixelgl.Run(run)
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Procedural terrain 1D",
Bounds: pixel.R(0, 0, width, height),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
pic, err := loadPicture("stone.jpg")
if err != nil {
panic(err)
}
imd := imdraw.New(pic)
drawTerrain(win, imd)
for !win.Closed() {
if win.JustPressed(pixelgl.KeySpace) {
drawTerrain(win, imd)
}
win.Update()
}
}
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
func drawTerrain(win *pixelgl.Window, imd *imdraw.IMDraw) {
var seed = rand.Int63n(maximumSeedValue)
p := perlin.NewPerlin(alpha, beta, n, seed)
imd.Clear()
win.Clear(colornames.Skyblue)
for x := 0.; x < width; x++ {
y := p.Noise1D(x/waveLength)*scale + verticalOffset
renderTexturedLine(x, y, imd)
}
imd.Draw(win)
}
// Render a textured line in position x with a height y.
// Note that the textured line is just a 1 px width rectangle.
// We push the opposite vertices of that rectangle and specify the points of the
// texture we want to apply to them. Pixel will fill the rest of the rectangle interpolating the texture.
func renderTexturedLine(x, y float64, imd *imdraw.IMDraw) {
imd.Intensity = 1.
imd.Picture = pixel.V(x, 0)
imd.Push(pixel.V(x, 0))
imd.Picture = pixel.V(x+1, y)
imd.Push(pixel.V(x+1, y))
imd.Rectangle(0)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

View File

@ -0,0 +1,22 @@
# raycaster
A raycaster made by [Peter Hellberg](https://github.com/peterhellberg/) as part of his [pixel-experiments](https://github.com/peterhellberg/pixel-experiments).
Based on Lodes article on [raycasting](http://lodev.org/cgtutor/raycasting.html).
## Controls
WASD for strafing and arrow keys for rotation.
Place blocks using the number keys.
## Screenshots
![raycaster animation](https://user-images.githubusercontent.com/565124/31828029-798e6620-b5b9-11e7-96b7-fda540755745.gif)
![raycaster screenshot](screenshot.png)
## Links
- https://github.com/peterhellberg/pixel-experiments/tree/master/raycaster
- https://gist.github.com/peterhellberg/835eccabf95800555120cc8f0c9e16c2

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,7 @@
# Infinite scrolling background demo
Created by [Sergio Vera](https://github.com/svera)
This example shows how to implement an infinite side scrolling background.
Credits to [Peter Hellberg](https://github.com/peterhellberg) for the improved background image.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,83 @@
package main
import (
"image"
"os"
"time"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
const (
windowWidth = 600
windowHeight = 450
// This is the scrolling speed
linesPerSecond = 60
)
func run() {
cfg := pixelgl.WindowConfig{
Title: "Scrolling background demo",
Bounds: pixel.R(0, 0, windowWidth, windowHeight),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
// Pic must have double the width of the window, as it will scroll to the left
pic, err := loadPicture("gamebackground.png")
if err != nil {
panic(err)
}
// Backgrounds are made taking the left and right halves of the image
background1 := pixel.NewSprite(pic, pixel.R(0, 0, windowWidth, windowHeight))
background2 := pixel.NewSprite(pic, pixel.R(windowWidth, 0, windowWidth*2, windowHeight))
// In the beginning, vector1 will put background1 filling the whole window, while vector2 will
// put background2 just at the right side of the window, out of view
vector1 := pixel.V(windowWidth/2, windowHeight/2)
vector2 := pixel.V(windowWidth+(windowWidth/2), windowHeight/2)
i := float64(0)
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
// When one of the backgrounds has completely scrolled, we swap displacement vectors,
// so the backgrounds will swap positions too regarding the previous iteration,
// thus making the background endless.
if i <= -windowWidth {
i = 0
vector1, vector2 = vector2, vector1
}
// This delta vector will move the backgrounds to the left
d := pixel.V(-i, 0)
background1.Draw(win, pixel.IM.Moved(vector1.Sub(d)))
background2.Draw(win, pixel.IM.Moved(vector2.Sub(d)))
i -= linesPerSecond * dt
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

View File

@ -0,0 +1,20 @@
# starfield
Classic starfield… with [supposedly accurate stellar colors](http://www.vendian.org/mncharity/dir3/starcolor/)
Made by [Peter Hellberg](https://github.com/peterhellberg/) as part of his [pixel-experiments](https://github.com/peterhellberg/pixel-experiments)
## Controls
Arrow up and down to change speed. Space bar to almost stop.
## Screenshots
![starfield animation](https://user-images.githubusercontent.com/565124/32411599-a5fcba72-c1df-11e7-8730-a570470a4eee.gif)
![starfield screenshot](screenshot.png)
## Links
- https://github.com/peterhellberg/pixel-experiments/tree/master/starfield
- https://gist.github.com/peterhellberg/4018e228cced61a0bb26991e49299c96

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,165 @@
package main
import (
"image/color"
"math/rand"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
)
const w, h = float64(1024), float64(512)
var speed = float64(200)
var stars [1024]*star
func init() {
rand.Seed(4)
for i := 0; i < len(stars); i++ {
stars[i] = newStar()
}
}
type star struct {
pixel.Vec
Z float64
P float64
C color.RGBA
}
func newStar() *star {
return &star{
pixel.V(random(-w, w), random(-h, h)),
random(0, w), 0, Colors[rand.Intn(len(Colors))],
}
}
func (s *star) update(d float64) {
s.P = s.Z
s.Z -= d * speed
if s.Z < 0 {
s.X = random(-w, w)
s.Y = random(-h, h)
s.Z = w
s.P = s.Z
}
}
func (s *star) draw(imd *imdraw.IMDraw) {
p := pixel.V(
scale(s.X/s.Z, 0, 1, 0, w),
scale(s.Y/s.Z, 0, 1, 0, h),
)
o := pixel.V(
scale(s.X/s.P, 0, 1, 0, w),
scale(s.Y/s.P, 0, 1, 0, h),
)
r := scale(s.Z, 0, w, 11, 0)
imd.Color = s.C
if p.Sub(o).Len() > 6 {
imd.Push(p, o)
imd.Line(r)
}
imd.Push(p)
imd.Circle(r, 0)
}
func run() {
win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
Bounds: pixel.R(0, 0, w, h),
VSync: true,
Undecorated: true,
})
if err != nil {
panic(err)
}
imd := imdraw.New(nil)
imd.Precision = 7
imd.SetMatrix(pixel.IM.Moved(win.Bounds().Center()))
last := time.Now()
for !win.Closed() {
win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
if win.Pressed(pixelgl.KeyUp) {
speed += 10
}
if win.Pressed(pixelgl.KeyDown) {
if speed > 10 {
speed -= 10
}
}
if win.Pressed(pixelgl.KeySpace) {
speed = 100
}
d := time.Since(last).Seconds()
last = time.Now()
imd.Clear()
for _, s := range stars {
s.update(d)
s.draw(imd)
}
win.Clear(color.Black)
imd.Draw(win)
win.Update()
}
}
func main() {
pixelgl.Run(run)
}
func random(min, max float64) float64 {
return rand.Float64()*(max-min) + min
}
func scale(unscaledNum, min, max, minAllowed, maxAllowed float64) float64 {
return (maxAllowed-minAllowed)*(unscaledNum-min)/(max-min) + minAllowed
}
// Colors based on stellar types listed at
// http://www.vendian.org/mncharity/dir3/starcolor/
var Colors = []color.RGBA{
color.RGBA{157, 180, 255, 255},
color.RGBA{162, 185, 255, 255},
color.RGBA{167, 188, 255, 255},
color.RGBA{170, 191, 255, 255},
color.RGBA{175, 195, 255, 255},
color.RGBA{186, 204, 255, 255},
color.RGBA{192, 209, 255, 255},
color.RGBA{202, 216, 255, 255},
color.RGBA{228, 232, 255, 255},
color.RGBA{237, 238, 255, 255},
color.RGBA{251, 248, 255, 255},
color.RGBA{255, 249, 249, 255},
color.RGBA{255, 245, 236, 255},
color.RGBA{255, 244, 232, 255},
color.RGBA{255, 241, 223, 255},
color.RGBA{255, 235, 209, 255},
color.RGBA{255, 215, 174, 255},
color.RGBA{255, 198, 144, 255},
color.RGBA{255, 190, 127, 255},
color.RGBA{255, 187, 123, 255},
color.RGBA{255, 187, 123, 255},
}

View File

@ -0,0 +1,29 @@
package main
import (
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
win.Clear(colornames.Skyblue)
for !win.Closed() {
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,56 @@
package main
import (
"image"
"os"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
pic, err := loadPicture("hiking.png")
if err != nil {
panic(err)
}
sprite := pixel.NewSprite(pic, pic.Bounds())
win.Clear(colornames.Greenyellow)
sprite.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
for !win.Closed() {
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,70 @@
package main
import (
"image"
"os"
"time"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
win.SetSmooth(true)
pic, err := loadPicture("hiking.png")
if err != nil {
panic(err)
}
sprite := pixel.NewSprite(pic, pic.Bounds())
angle := 0.0
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
angle += 3 * dt
win.Clear(colornames.Firebrick)
mat := pixel.IM
mat = mat.Rotated(pixel.ZV, angle)
mat = mat.Moved(win.Bounds().Center())
sprite.Draw(win, mat)
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

View File

@ -0,0 +1,102 @@
package main
import (
"image"
"math"
"math/rand"
"os"
"time"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
spritesheet, err := loadPicture("trees.png")
if err != nil {
panic(err)
}
var treesFrames []pixel.Rect
for x := spritesheet.Bounds().Min.X; x < spritesheet.Bounds().Max.X; x += 32 {
for y := spritesheet.Bounds().Min.Y; y < spritesheet.Bounds().Max.Y; y += 32 {
treesFrames = append(treesFrames, pixel.R(x, y, x+32, y+32))
}
}
var (
camPos = pixel.ZV
camSpeed = 500.0
camZoom = 1.0
camZoomSpeed = 1.2
trees []*pixel.Sprite
matrices []pixel.Matrix
)
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos))
win.SetMatrix(cam)
if win.JustPressed(pixelgl.MouseButtonLeft) {
tree := pixel.NewSprite(spritesheet, treesFrames[rand.Intn(len(treesFrames))])
trees = append(trees, tree)
mouse := cam.Unproject(win.MousePosition())
matrices = append(matrices, pixel.IM.Scaled(pixel.ZV, 4).Moved(mouse))
}
if win.Pressed(pixelgl.KeyLeft) {
camPos.X -= camSpeed * dt
}
if win.Pressed(pixelgl.KeyRight) {
camPos.X += camSpeed * dt
}
if win.Pressed(pixelgl.KeyDown) {
camPos.Y -= camSpeed * dt
}
if win.Pressed(pixelgl.KeyUp) {
camPos.Y += camSpeed * dt
}
camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y)
win.Clear(colornames.Forestgreen)
for i, tree := range trees {
tree.Draw(win, matrices[i])
}
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,110 @@
package main
import (
"fmt"
"image"
"math"
"math/rand"
"os"
"time"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
spritesheet, err := loadPicture("trees.png")
if err != nil {
panic(err)
}
batch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet)
var treesFrames []pixel.Rect
for x := spritesheet.Bounds().Min.X; x < spritesheet.Bounds().Max.X; x += 32 {
for y := spritesheet.Bounds().Min.Y; y < spritesheet.Bounds().Max.Y; y += 32 {
treesFrames = append(treesFrames, pixel.R(x, y, x+32, y+32))
}
}
var (
camPos = pixel.ZV
camSpeed = 500.0
camZoom = 1.0
camZoomSpeed = 1.2
)
var (
frames = 0
second = time.Tick(time.Second)
)
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos))
win.SetMatrix(cam)
if win.Pressed(pixelgl.MouseButtonLeft) {
tree := pixel.NewSprite(spritesheet, treesFrames[rand.Intn(len(treesFrames))])
mouse := cam.Unproject(win.MousePosition())
tree.Draw(batch, pixel.IM.Scaled(pixel.ZV, 4).Moved(mouse))
}
if win.Pressed(pixelgl.KeyLeft) {
camPos.X -= camSpeed * dt
}
if win.Pressed(pixelgl.KeyRight) {
camPos.X += camSpeed * dt
}
if win.Pressed(pixelgl.KeyDown) {
camPos.Y -= camSpeed * dt
}
if win.Pressed(pixelgl.KeyUp) {
camPos.Y += camSpeed * dt
}
camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y)
win.Clear(colornames.Forestgreen)
batch.Draw(win)
win.Update()
frames++
select {
case <-second:
win.SetTitle(fmt.Sprintf("%s | FPS: %d", cfg.Title, frames))
frames = 0
default:
}
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,53 @@
package main
import (
"math"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
imd := imdraw.New(nil)
imd.Color = colornames.Blueviolet
imd.EndShape = imdraw.RoundEndShape
imd.Push(pixel.V(100, 100), pixel.V(700, 100))
imd.EndShape = imdraw.SharpEndShape
imd.Push(pixel.V(100, 500), pixel.V(700, 500))
imd.Line(30)
imd.Color = colornames.Limegreen
imd.Push(pixel.V(500, 500))
imd.Circle(300, 50)
imd.Color = colornames.Navy
imd.Push(pixel.V(200, 500), pixel.V(800, 500))
imd.Ellipse(pixel.V(120, 80), 0)
imd.Color = colornames.Red
imd.EndShape = imdraw.RoundEndShape
imd.Push(pixel.V(500, 350))
imd.CircleArc(150, -math.Pi, 0, 30)
for !win.Closed() {
win.Clear(colornames.Aliceblue)
imd.Draw(win)
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

View File

@ -0,0 +1,78 @@
package main
import (
"io/ioutil"
"os"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"github.com/faiface/pixel/text"
"github.com/golang/freetype/truetype"
"golang.org/x/image/colornames"
"golang.org/x/image/font"
)
func loadTTF(path string, size float64) (font.Face, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
bytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
font, err := truetype.Parse(bytes)
if err != nil {
return nil, err
}
return truetype.NewFace(font, &truetype.Options{
Size: size,
GlyphCacheEntries: 1,
}), nil
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
win.SetSmooth(true)
face, err := loadTTF("intuitive.ttf", 80)
if err != nil {
panic(err)
}
atlas := text.NewAtlas(face, text.ASCII)
txt := text.New(pixel.V(50, 500), atlas)
txt.Color = colornames.Lightgrey
fps := time.Tick(time.Second / 120)
for !win.Closed() {
txt.WriteString(win.Typed())
if win.JustPressed(pixelgl.KeyEnter) || win.Repeated(pixelgl.KeyEnter) {
txt.WriteRune('\n')
}
win.Clear(colornames.Darkcyan)
txt.Draw(win, pixel.IM.Moved(win.Bounds().Center().Sub(txt.Bounds().Center())))
win.Update()
<-fps
}
}
func main() {
pixelgl.Run(run)
}

13
examples/lights/README.md Normal file
View File

@ -0,0 +1,13 @@
# Lights
This example demonstrates powerful Porter-Duff composition used to create a nice noisy light effect.
**Use W and S keys** to adjust the level of "dust".
The FPS is limited to 30, because the effect is a little expensive and my computer couldn't handle
60 FPS. If you have a more powerful computer (which is quite likely), peek into the code and disable
the limit.
Credit for the panda art goes to [Ján Štrba](https://www.artstation.com/artist/janstrba).
![Screenshot](screenshot.png)

195
examples/lights/main.go Normal file
View File

@ -0,0 +1,195 @@
package main
import (
"image"
"math"
"os"
"time"
_ "image/jpeg"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
)
func loadPicture(path string) (pixel.Picture, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return pixel.PictureDataFromImage(img), nil
}
type colorlight struct {
color pixel.RGBA
point pixel.Vec
angle float64
radius float64
dust float64
spread float64
imd *imdraw.IMDraw
}
func (cl *colorlight) apply(dst pixel.ComposeTarget, center pixel.Vec, src, noise *pixel.Sprite) {
// create the light arc if not created already
if cl.imd == nil {
imd := imdraw.New(nil)
imd.Color = pixel.Alpha(1)
imd.Push(pixel.ZV)
imd.Color = pixel.Alpha(0)
for angle := -cl.spread / 2; angle <= cl.spread/2; angle += cl.spread / 64 {
imd.Push(pixel.V(1, 0).Rotated(angle))
}
imd.Polygon(0)
cl.imd = imd
}
// draw the light arc
dst.SetMatrix(pixel.IM.Scaled(pixel.ZV, cl.radius).Rotated(pixel.ZV, cl.angle).Moved(cl.point))
dst.SetColorMask(pixel.Alpha(1))
dst.SetComposeMethod(pixel.ComposePlus)
cl.imd.Draw(dst)
// draw the noise inside the light
dst.SetMatrix(pixel.IM)
dst.SetComposeMethod(pixel.ComposeIn)
noise.Draw(dst, pixel.IM.Moved(center))
// draw an image inside the noisy light
dst.SetColorMask(cl.color)
dst.SetComposeMethod(pixel.ComposeIn)
src.Draw(dst, pixel.IM.Moved(center))
// draw the light reflected from the dust
dst.SetMatrix(pixel.IM.Scaled(pixel.ZV, cl.radius).Rotated(pixel.ZV, cl.angle).Moved(cl.point))
dst.SetColorMask(cl.color.Mul(pixel.Alpha(cl.dust)))
dst.SetComposeMethod(pixel.ComposePlus)
cl.imd.Draw(dst)
}
func run() {
pandaPic, err := loadPicture("panda.png")
if err != nil {
panic(err)
}
noisePic, err := loadPicture("noise.png")
if err != nil {
panic(err)
}
cfg := pixelgl.WindowConfig{
Title: "Lights",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
panda := pixel.NewSprite(pandaPic, pandaPic.Bounds())
noise := pixel.NewSprite(noisePic, noisePic.Bounds())
colors := []pixel.RGBA{
pixel.RGB(1, 0, 0),
pixel.RGB(0, 1, 0),
pixel.RGB(0, 0, 1),
pixel.RGB(1/math.Sqrt2, 1/math.Sqrt2, 0),
}
points := []pixel.Vec{
{X: win.Bounds().Min.X, Y: win.Bounds().Min.Y},
{X: win.Bounds().Max.X, Y: win.Bounds().Min.Y},
{X: win.Bounds().Max.X, Y: win.Bounds().Max.Y},
{X: win.Bounds().Min.X, Y: win.Bounds().Max.Y},
}
angles := []float64{
math.Pi / 4,
math.Pi/4 + math.Pi/2,
math.Pi/4 + 2*math.Pi/2,
math.Pi/4 + 3*math.Pi/2,
}
lights := make([]colorlight, 4)
for i := range lights {
lights[i] = colorlight{
color: colors[i],
point: points[i],
angle: angles[i],
radius: 800,
dust: 0.3,
spread: math.Pi / math.E,
}
}
speed := []float64{11.0 / 23, 13.0 / 23, 17.0 / 23, 19.0 / 23}
oneLight := pixelgl.NewCanvas(win.Bounds())
allLight := pixelgl.NewCanvas(win.Bounds())
fps30 := time.Tick(time.Second / 30)
start := time.Now()
for !win.Closed() {
if win.Pressed(pixelgl.KeyW) {
for i := range lights {
lights[i].dust += 0.05
if lights[i].dust > 1 {
lights[i].dust = 1
}
}
}
if win.Pressed(pixelgl.KeyS) {
for i := range lights {
lights[i].dust -= 0.05
if lights[i].dust < 0 {
lights[i].dust = 0
}
}
}
since := time.Since(start).Seconds()
for i := range lights {
lights[i].angle = angles[i] + math.Sin(since*speed[i])*math.Pi/8
}
win.Clear(pixel.RGB(0, 0, 0))
// draw the panda visible outside the light
win.SetColorMask(pixel.Alpha(0.4))
win.SetComposeMethod(pixel.ComposePlus)
panda.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
allLight.Clear(pixel.Alpha(0))
allLight.SetComposeMethod(pixel.ComposePlus)
// accumulate all the lights
for i := range lights {
oneLight.Clear(pixel.Alpha(0))
lights[i].apply(oneLight, oneLight.Bounds().Center(), panda, noise)
oneLight.Draw(allLight, pixel.IM.Moved(allLight.Bounds().Center()))
}
// compose the final result
win.SetColorMask(pixel.Alpha(1))
allLight.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
win.Update()
<-fps30 // maintain 30 fps, because my computer couldn't handle 60 here
}
}
func main() {
pixelgl.Run(run)
}

BIN
examples/lights/noise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

BIN
examples/lights/panda.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,14 @@
# Platformer
This example demostrates a way to put things together and create a simple platformer game with a
Gopher!
Use **arrow keys** to run and jump around. Press **ENTER** to restart. (And hush, hush, secret.
Press TAB for slo-mo!)
The retro feel is, other than from the pixel art spritesheet, achieved by using a 160x120px large
off-screen canvas, drawing everything to it and then stretching it to fit the window.
The Gopher spritesheet comes from excellent [Egon Elbre](https://github.com/egonelbre/gophers).
![Screenshot](screenshot.png)

394
examples/platformer/main.go Normal file
View File

@ -0,0 +1,394 @@
package main
import (
"encoding/csv"
"image"
"image/color"
"io"
"math"
"math/rand"
"os"
"strconv"
"time"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
"github.com/pkg/errors"
"golang.org/x/image/colornames"
)
func loadAnimationSheet(sheetPath, descPath string, frameWidth float64) (sheet pixel.Picture, anims map[string][]pixel.Rect, err error) {
// total hack, nicely format the error at the end, so I don't have to type it every time
defer func() {
if err != nil {
err = errors.Wrap(err, "error loading animation sheet")
}
}()
// open and load the spritesheet
sheetFile, err := os.Open(sheetPath)
if err != nil {
return nil, nil, err
}
defer sheetFile.Close()
sheetImg, _, err := image.Decode(sheetFile)
if err != nil {
return nil, nil, err
}
sheet = pixel.PictureDataFromImage(sheetImg)
// create a slice of frames inside the spritesheet
var frames []pixel.Rect
for x := 0.0; x+frameWidth <= sheet.Bounds().Max.X; x += frameWidth {
frames = append(frames, pixel.R(
x,
0,
x+frameWidth,
sheet.Bounds().H(),
))
}
descFile, err := os.Open(descPath)
if err != nil {
return nil, nil, err
}
defer descFile.Close()
anims = make(map[string][]pixel.Rect)
// load the animation information, name and interval inside the spritesheet
desc := csv.NewReader(descFile)
for {
anim, err := desc.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, nil, err
}
name := anim[0]
start, _ := strconv.Atoi(anim[1])
end, _ := strconv.Atoi(anim[2])
anims[name] = frames[start : end+1]
}
return sheet, anims, nil
}
type platform struct {
rect pixel.Rect
color color.Color
}
func (p *platform) draw(imd *imdraw.IMDraw) {
imd.Color = p.color
imd.Push(p.rect.Min, p.rect.Max)
imd.Rectangle(0)
}
type gopherPhys struct {
gravity float64
runSpeed float64
jumpSpeed float64
rect pixel.Rect
vel pixel.Vec
ground bool
}
func (gp *gopherPhys) update(dt float64, ctrl pixel.Vec, platforms []platform) {
// apply controls
switch {
case ctrl.X < 0:
gp.vel.X = -gp.runSpeed
case ctrl.X > 0:
gp.vel.X = +gp.runSpeed
default:
gp.vel.X = 0
}
// apply gravity and velocity
gp.vel.Y += gp.gravity * dt
gp.rect = gp.rect.Moved(gp.vel.Scaled(dt))
// check collisions against each platform
gp.ground = false
if gp.vel.Y <= 0 {
for _, p := range platforms {
if gp.rect.Max.X <= p.rect.Min.X || gp.rect.Min.X >= p.rect.Max.X {
continue
}
if gp.rect.Min.Y > p.rect.Max.Y || gp.rect.Min.Y < p.rect.Max.Y+gp.vel.Y*dt {
continue
}
gp.vel.Y = 0
gp.rect = gp.rect.Moved(pixel.V(0, p.rect.Max.Y-gp.rect.Min.Y))
gp.ground = true
}
}
// jump if on the ground and the player wants to jump
if gp.ground && ctrl.Y > 0 {
gp.vel.Y = gp.jumpSpeed
}
}
type animState int
const (
idle animState = iota
running
jumping
)
type gopherAnim struct {
sheet pixel.Picture
anims map[string][]pixel.Rect
rate float64
state animState
counter float64
dir float64
frame pixel.Rect
sprite *pixel.Sprite
}
func (ga *gopherAnim) update(dt float64, phys *gopherPhys) {
ga.counter += dt
// determine the new animation state
var newState animState
switch {
case !phys.ground:
newState = jumping
case phys.vel.Len() == 0:
newState = idle
case phys.vel.Len() > 0:
newState = running
}
// reset the time counter if the state changed
if ga.state != newState {
ga.state = newState
ga.counter = 0
}
// determine the correct animation frame
switch ga.state {
case idle:
ga.frame = ga.anims["Front"][0]
case running:
i := int(math.Floor(ga.counter / ga.rate))
ga.frame = ga.anims["Run"][i%len(ga.anims["Run"])]
case jumping:
speed := phys.vel.Y
i := int((-speed/phys.jumpSpeed + 1) / 2 * float64(len(ga.anims["Jump"])))
if i < 0 {
i = 0
}
if i >= len(ga.anims["Jump"]) {
i = len(ga.anims["Jump"]) - 1
}
ga.frame = ga.anims["Jump"][i]
}
// set the facing direction of the gopher
if phys.vel.X != 0 {
if phys.vel.X > 0 {
ga.dir = +1
} else {
ga.dir = -1
}
}
}
func (ga *gopherAnim) draw(t pixel.Target, phys *gopherPhys) {
if ga.sprite == nil {
ga.sprite = pixel.NewSprite(nil, pixel.Rect{})
}
// draw the correct frame with the correct position and direction
ga.sprite.Set(ga.sheet, ga.frame)
ga.sprite.Draw(t, pixel.IM.
ScaledXY(pixel.ZV, pixel.V(
phys.rect.W()/ga.sprite.Frame().W(),
phys.rect.H()/ga.sprite.Frame().H(),
)).
ScaledXY(pixel.ZV, pixel.V(-ga.dir, 1)).
Moved(phys.rect.Center()),
)
}
type goal struct {
pos pixel.Vec
radius float64
step float64
counter float64
cols [5]pixel.RGBA
}
func (g *goal) update(dt float64) {
g.counter += dt
for g.counter > g.step {
g.counter -= g.step
for i := len(g.cols) - 2; i >= 0; i-- {
g.cols[i+1] = g.cols[i]
}
g.cols[0] = randomNiceColor()
}
}
func (g *goal) draw(imd *imdraw.IMDraw) {
for i := len(g.cols) - 1; i >= 0; i-- {
imd.Color = g.cols[i]
imd.Push(g.pos)
imd.Circle(float64(i+1)*g.radius/float64(len(g.cols)), 0)
}
}
func randomNiceColor() pixel.RGBA {
again:
r := rand.Float64()
g := rand.Float64()
b := rand.Float64()
len := math.Sqrt(r*r + g*g + b*b)
if len == 0 {
goto again
}
return pixel.RGB(r/len, g/len, b/len)
}
func run() {
rand.Seed(time.Now().UnixNano())
sheet, anims, err := loadAnimationSheet("sheet.png", "sheet.csv", 12)
if err != nil {
panic(err)
}
cfg := pixelgl.WindowConfig{
Title: "Platformer",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
phys := &gopherPhys{
gravity: -512,
runSpeed: 64,
jumpSpeed: 192,
rect: pixel.R(-6, -7, 6, 7),
}
anim := &gopherAnim{
sheet: sheet,
anims: anims,
rate: 1.0 / 10,
dir: +1,
}
// hardcoded level
platforms := []platform{
{rect: pixel.R(-50, -34, 50, -32)},
{rect: pixel.R(20, 0, 70, 2)},
{rect: pixel.R(-100, 10, -50, 12)},
{rect: pixel.R(120, -22, 140, -20)},
{rect: pixel.R(120, -72, 140, -70)},
{rect: pixel.R(120, -122, 140, -120)},
{rect: pixel.R(-100, -152, 100, -150)},
{rect: pixel.R(-150, -127, -140, -125)},
{rect: pixel.R(-180, -97, -170, -95)},
{rect: pixel.R(-150, -67, -140, -65)},
{rect: pixel.R(-180, -37, -170, -35)},
{rect: pixel.R(-150, -7, -140, -5)},
}
for i := range platforms {
platforms[i].color = randomNiceColor()
}
gol := &goal{
pos: pixel.V(-75, 40),
radius: 18,
step: 1.0 / 7,
}
canvas := pixelgl.NewCanvas(pixel.R(-160/2, -120/2, 160/2, 120/2))
imd := imdraw.New(sheet)
imd.Precision = 32
camPos := pixel.ZV
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
// lerp the camera position towards the gopher
camPos = pixel.Lerp(camPos, phys.rect.Center(), 1-math.Pow(1.0/128, dt))
cam := pixel.IM.Moved(camPos.Scaled(-1))
canvas.SetMatrix(cam)
// slow motion with tab
if win.Pressed(pixelgl.KeyTab) {
dt /= 8
}
// restart the level on pressing enter
if win.JustPressed(pixelgl.KeyEnter) {
phys.rect = phys.rect.Moved(phys.rect.Center().Scaled(-1))
phys.vel = pixel.ZV
}
// control the gopher with keys
ctrl := pixel.ZV
if win.Pressed(pixelgl.KeyLeft) {
ctrl.X--
}
if win.Pressed(pixelgl.KeyRight) {
ctrl.X++
}
if win.JustPressed(pixelgl.KeyUp) {
ctrl.Y = 1
}
// update the physics and animation
phys.update(dt, ctrl, platforms)
gol.update(dt)
anim.update(dt, phys)
// draw the scene to the canvas using IMDraw
canvas.Clear(colornames.Black)
imd.Clear()
for _, p := range platforms {
p.draw(imd)
}
gol.draw(imd)
anim.draw(imd, phys)
imd.Draw(canvas)
// stretch the canvas to the window
win.Clear(colornames.White)
win.SetMatrix(pixel.IM.Scaled(pixel.ZV,
math.Min(
win.Bounds().W()/canvas.Bounds().W(),
win.Bounds().H()/canvas.Bounds().H(),
),
).Moved(win.Bounds().Center()))
canvas.Draw(win, pixel.IM.Moved(canvas.Bounds().Center()))
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,9 @@
Front,0,0
FrontBlink,1,1
LookUp,2,2
Left,3,7
LeftRight,4,6
LeftBlink,7,7
Walk,8,15
Run,16,23
Jump,24,26
1 Front 0 0
2 FrontBlink 1 1
3 LookUp 2 2
4 Left 3 7
5 LeftRight 4 6
6 LeftBlink 7 7
7 Walk 8 15
8 Run 16 23
9 Jump 24 26

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

8
examples/smoke/README.md Normal file
View File

@ -0,0 +1,8 @@
# Smoke
This example implements a smoke particle effect using sprites. It uses a spritesheet with a CSV
description.
The art in the spritesheet comes from [Kenney](https://kenney.nl/).
![Screenshot](screenshot.png)

View File

@ -0,0 +1,25 @@
1543,1146,362,336
396,0,398,364
761,1535,386,342
795,794,351,367
394,1163,386,364
1120,1163,377,348
795,0,368,407
0,0,395,397
1164,0,378,415
781,1163,338,360
1543,0,372,370
1148,1535,393,327
387,1535,373,364
396,365,371,388
0,758,378,404
379,758,378,371
1543,774,360,371
1543,1483,350,398
0,398,382,359
1164,416,356,382
1164,799,369,350
0,1535,386,394
795,408,366,385
1543,371,367,402
0,1163,393,371
1 1543 1146 362 336
2 396 0 398 364
3 761 1535 386 342
4 795 794 351 367
5 394 1163 386 364
6 1120 1163 377 348
7 795 0 368 407
8 0 0 395 397
9 1164 0 378 415
10 781 1163 338 360
11 1543 0 372 370
12 1148 1535 393 327
13 387 1535 373 364
14 396 365 371 388
15 0 758 378 404
16 379 758 378 371
17 1543 774 360 371
18 1543 1483 350 398
19 0 398 382 359
20 1164 416 356 382
21 1164 799 369 350
22 0 1535 386 394
23 795 408 366 385
24 1543 371 367 402
25 0 1163 393 371

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

230
examples/smoke/main.go Normal file
View File

@ -0,0 +1,230 @@
package main
import (
"container/list"
"encoding/csv"
"image"
"io"
"math"
"math/rand"
"os"
"strconv"
"time"
_ "image/png"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
type particle struct {
Sprite *pixel.Sprite
Pos pixel.Vec
Rot, Scale float64
Mask pixel.RGBA
Data interface{}
}
type particles struct {
Generate func() *particle
Update func(dt float64, p *particle) bool
SpawnAvg, SpawnDist float64
parts list.List
spawnTime float64
}
func (p *particles) UpdateAll(dt float64) {
p.spawnTime -= dt
for p.spawnTime <= 0 {
p.parts.PushFront(p.Generate())
p.spawnTime += math.Max(0, p.SpawnAvg+rand.NormFloat64()*p.SpawnDist)
}
for e := p.parts.Front(); e != nil; e = e.Next() {
part := e.Value.(*particle)
if !p.Update(dt, part) {
defer p.parts.Remove(e)
}
}
}
func (p *particles) DrawAll(t pixel.Target) {
for e := p.parts.Front(); e != nil; e = e.Next() {
part := e.Value.(*particle)
part.Sprite.DrawColorMask(
t,
pixel.IM.
Scaled(pixel.ZV, part.Scale).
Rotated(pixel.ZV, part.Rot).
Moved(part.Pos),
part.Mask,
)
}
}
type smokeData struct {
Vel pixel.Vec
Time float64
Life float64
}
type smokeSystem struct {
Sheet pixel.Picture
Rects []pixel.Rect
Orig pixel.Vec
VelBasis []pixel.Vec
VelDist float64
LifeAvg, LifeDist float64
}
func (ss *smokeSystem) Generate() *particle {
sd := new(smokeData)
for _, base := range ss.VelBasis {
c := math.Max(0, 1+rand.NormFloat64()*ss.VelDist)
sd.Vel = sd.Vel.Add(base.Scaled(c))
}
sd.Vel = sd.Vel.Scaled(1 / float64(len(ss.VelBasis)))
sd.Life = math.Max(0, ss.LifeAvg+rand.NormFloat64()*ss.LifeDist)
p := new(particle)
p.Data = sd
p.Pos = ss.Orig
p.Scale = 1
p.Mask = pixel.Alpha(1)
p.Sprite = pixel.NewSprite(ss.Sheet, ss.Rects[rand.Intn(len(ss.Rects))])
return p
}
func (ss *smokeSystem) Update(dt float64, p *particle) bool {
sd := p.Data.(*smokeData)
sd.Time += dt
frac := sd.Time / sd.Life
p.Pos = p.Pos.Add(sd.Vel.Scaled(dt))
p.Scale = 0.5 + frac*1.5
const (
fadeIn = 0.2
fadeOut = 0.4
)
if frac < fadeIn {
p.Mask = pixel.Alpha(math.Pow(frac/fadeIn, 0.75))
} else if frac >= fadeOut {
p.Mask = pixel.Alpha(math.Pow(1-(frac-fadeOut)/(1-fadeOut), 1.5))
} else {
p.Mask = pixel.Alpha(1)
}
return sd.Time < sd.Life
}
func loadSpriteSheet(sheetPath, descriptionPath string) (sheet pixel.Picture, rects []pixel.Rect, err error) {
sheetFile, err := os.Open(sheetPath)
if err != nil {
return nil, nil, err
}
defer sheetFile.Close()
sheetImg, _, err := image.Decode(sheetFile)
if err != nil {
return nil, nil, err
}
sheet = pixel.PictureDataFromImage(sheetImg)
descriptionFile, err := os.Open(descriptionPath)
if err != nil {
return nil, nil, err
}
defer descriptionFile.Close()
description := csv.NewReader(descriptionFile)
for {
record, err := description.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, nil, err
}
x, _ := strconv.ParseFloat(record[0], 64)
y, _ := strconv.ParseFloat(record[1], 64)
w, _ := strconv.ParseFloat(record[2], 64)
h, _ := strconv.ParseFloat(record[3], 64)
y = sheet.Bounds().H() - y - h
rects = append(rects, pixel.R(x, y, x+w, y+h))
}
return sheet, rects, nil
}
func run() {
sheet, rects, err := loadSpriteSheet("blackSmoke.png", "blackSmoke.csv")
if err != nil {
panic(err)
}
cfg := pixelgl.WindowConfig{
Title: "Smoke",
Bounds: pixel.R(0, 0, 1024, 768),
Resizable: true,
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
ss := &smokeSystem{
Rects: rects,
Orig: pixel.ZV,
VelBasis: []pixel.Vec{pixel.V(-100, 100), pixel.V(100, 100), pixel.V(0, 100)},
VelDist: 0.1,
LifeAvg: 7,
LifeDist: 0.5,
}
p := &particles{
Generate: ss.Generate,
Update: ss.Update,
SpawnAvg: 0.3,
SpawnDist: 0.1,
}
batch := pixel.NewBatch(&pixel.TrianglesData{}, sheet)
last := time.Now()
for !win.Closed() {
dt := time.Since(last).Seconds()
last = time.Now()
p.UpdateAll(dt)
win.Clear(colornames.Aliceblue)
orig := win.Bounds().Center()
orig.Y -= win.Bounds().H() / 2
win.SetMatrix(pixel.IM.Moved(orig))
batch.Clear()
p.DrawAll(batch)
batch.Draw(win)
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

View File

@ -0,0 +1,13 @@
# Typewriter
This example demonstrates text drawing and text input facilities by implementing a fancy typewriter
with a red laser cursor. Screen shakes a bit when typing and some letters turn bold or italic
randomly.
ASCII and Latin characters are supported here. Feel free to add support for more characters in the
code (it's easy, but increases load time).
The seemingly buggy letters (one over another) in the screenshot are not bugs, but a result of using
the typewriter backspace functionality.
![Screenshot](screenshot.png)

317
examples/typewriter/main.go Normal file
View File

@ -0,0 +1,317 @@
package main
import (
"image/color"
"math"
"math/rand"
"sync"
"time"
"unicode"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
"github.com/faiface/pixel/text"
"github.com/golang/freetype/truetype"
"golang.org/x/image/colornames"
"golang.org/x/image/font"
"golang.org/x/image/font/gofont/gobold"
"golang.org/x/image/font/gofont/goitalic"
"golang.org/x/image/font/gofont/goregular"
)
func ttfFromBytesMust(b []byte, size float64) font.Face {
ttf, err := truetype.Parse(b)
if err != nil {
panic(err)
}
return truetype.NewFace(ttf, &truetype.Options{
Size: size,
GlyphCacheEntries: 1,
})
}
type typewriter struct {
mu sync.Mutex
regular *text.Text
bold *text.Text
italic *text.Text
offset pixel.Vec
position pixel.Vec
move pixel.Vec
}
func newTypewriter(c color.Color, regular, bold, italic *text.Atlas) *typewriter {
tw := &typewriter{
regular: text.New(pixel.ZV, regular),
bold: text.New(pixel.ZV, bold),
italic: text.New(pixel.ZV, italic),
}
tw.regular.Color = c
tw.bold.Color = c
tw.italic.Color = c
return tw
}
func (tw *typewriter) Ribbon(r rune) {
tw.mu.Lock()
defer tw.mu.Unlock()
dice := rand.Intn(21)
switch {
case 0 <= dice && dice <= 18:
tw.regular.WriteRune(r)
case dice == 19:
tw.bold.Dot = tw.regular.Dot
tw.bold.WriteRune(r)
tw.regular.Dot = tw.bold.Dot
case dice == 20:
tw.italic.Dot = tw.regular.Dot
tw.italic.WriteRune(r)
tw.regular.Dot = tw.italic.Dot
}
}
func (tw *typewriter) Back() {
tw.mu.Lock()
defer tw.mu.Unlock()
tw.regular.Dot = tw.regular.Dot.Sub(pixel.V(tw.regular.Atlas().Glyph(' ').Advance, 0))
}
func (tw *typewriter) Offset(off pixel.Vec) {
tw.mu.Lock()
defer tw.mu.Unlock()
tw.offset = tw.offset.Add(off)
}
func (tw *typewriter) Position() pixel.Vec {
tw.mu.Lock()
defer tw.mu.Unlock()
return tw.position
}
func (tw *typewriter) Move(vel pixel.Vec) {
tw.mu.Lock()
defer tw.mu.Unlock()
tw.move = vel
}
func (tw *typewriter) Dot() pixel.Vec {
tw.mu.Lock()
defer tw.mu.Unlock()
return tw.regular.Dot
}
func (tw *typewriter) Update(dt float64) {
tw.mu.Lock()
defer tw.mu.Unlock()
tw.position = tw.position.Add(tw.move.Scaled(dt))
}
func (tw *typewriter) Draw(t pixel.Target, m pixel.Matrix) {
tw.mu.Lock()
defer tw.mu.Unlock()
m = pixel.IM.Moved(tw.position.Add(tw.offset)).Chained(m)
tw.regular.Draw(t, m)
tw.bold.Draw(t, m)
tw.italic.Draw(t, m)
}
func typeRune(tw *typewriter, r rune) {
tw.Ribbon(r)
if !unicode.IsSpace(r) {
go shake(tw, 3, 17)
}
}
func back(tw *typewriter) {
tw.Back()
}
func shake(tw *typewriter, intensity, friction float64) {
const (
freq = 24
dt = 1.0 / freq
)
ticker := time.NewTicker(time.Second / freq)
defer ticker.Stop()
off := pixel.ZV
for range ticker.C {
tw.Offset(off.Scaled(-1))
if intensity < 0.01*dt {
break
}
off = pixel.V((rand.Float64()-0.5)*intensity*2, (rand.Float64()-0.5)*intensity*2)
intensity -= friction * dt
tw.Offset(off)
}
}
func scroll(tw *typewriter, intensity, speedUp float64) {
const (
freq = 120
dt = 1.0 / freq
)
ticker := time.NewTicker(time.Second / freq)
defer ticker.Stop()
speed := 0.0
for range ticker.C {
if math.Abs(tw.Dot().Y+tw.Position().Y) < 0.01 {
break
}
targetSpeed := -(tw.Dot().Y + tw.Position().Y) * intensity
if speed < targetSpeed {
speed += speedUp * dt
} else {
speed = targetSpeed
}
tw.Move(pixel.V(0, speed))
}
}
type dotlight struct {
tw *typewriter
color color.Color
radius float64
intensity float64
acceleration float64
maxSpeed float64
pos pixel.Vec
vel pixel.Vec
imd *imdraw.IMDraw
}
func newDotlight(tw *typewriter, c color.Color, radius, intensity, acceleration, maxSpeed float64) *dotlight {
return &dotlight{
tw: tw,
color: c,
radius: radius,
intensity: intensity,
acceleration: acceleration,
maxSpeed: maxSpeed,
pos: tw.Dot(),
vel: pixel.ZV,
imd: imdraw.New(nil),
}
}
func (dl *dotlight) Update(dt float64) {
targetVel := dl.tw.Dot().Add(dl.tw.Position()).Sub(dl.pos).Scaled(dl.intensity)
acc := targetVel.Sub(dl.vel).Scaled(dl.acceleration)
dl.vel = dl.vel.Add(acc.Scaled(dt))
if dl.vel.Len() > dl.maxSpeed {
dl.vel = dl.vel.Unit().Scaled(dl.maxSpeed)
}
dl.pos = dl.pos.Add(dl.vel.Scaled(dt))
}
func (dl *dotlight) Draw(t pixel.Target, m pixel.Matrix) {
dl.imd.Clear()
dl.imd.SetMatrix(m)
dl.imd.Color = dl.color
dl.imd.Push(dl.pos)
dl.imd.Color = pixel.Alpha(0)
for i := 0.0; i <= 32; i++ {
angle := i * 2 * math.Pi / 32
dl.imd.Push(dl.pos.Add(pixel.V(dl.radius, 0).Rotated(angle)))
}
dl.imd.Polygon(0)
dl.imd.Draw(t)
}
func run() {
rand.Seed(time.Now().UnixNano())
cfg := pixelgl.WindowConfig{
Title: "Typewriter",
Bounds: pixel.R(0, 0, 1024, 768),
Resizable: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
win.SetSmooth(true)
var (
regular = text.NewAtlas(
ttfFromBytesMust(goregular.TTF, 42),
text.ASCII, text.RangeTable(unicode.Latin),
)
bold = text.NewAtlas(
ttfFromBytesMust(gobold.TTF, 42),
text.ASCII, text.RangeTable(unicode.Latin),
)
italic = text.NewAtlas(
ttfFromBytesMust(goitalic.TTF, 42),
text.ASCII, text.RangeTable(unicode.Latin),
)
bgColor = color.RGBA{
R: 241,
G: 241,
B: 212,
A: 255,
}
fgColor = color.RGBA{
R: 0,
G: 15,
B: 85,
A: 255,
}
tw = newTypewriter(pixel.ToRGBA(fgColor).Scaled(0.9), regular, bold, italic)
dl = newDotlight(tw, colornames.Red, 6, 30, 20, 1600)
)
fps := time.Tick(time.Second / 120)
last := time.Now()
for !win.Closed() {
for _, r := range win.Typed() {
go typeRune(tw, r)
}
if win.JustPressed(pixelgl.KeyTab) || win.Repeated(pixelgl.KeyTab) {
go typeRune(tw, '\t')
}
if win.JustPressed(pixelgl.KeyEnter) || win.Repeated(pixelgl.KeyEnter) {
go typeRune(tw, '\n')
go scroll(tw, 20, 6400)
}
if win.JustPressed(pixelgl.KeyBackspace) || win.Repeated(pixelgl.KeyBackspace) {
go back(tw)
}
dt := time.Since(last).Seconds()
last = time.Now()
tw.Update(dt)
dl.Update(dt)
win.Clear(bgColor)
m := pixel.IM.Moved(pixel.V(32, 32))
tw.Draw(win, m)
dl.Draw(win, m)
win.Update()
<-fps
}
}
func main() {
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

8
examples/xor/README.md Normal file
View File

@ -0,0 +1,8 @@
# Xor
This example demonstrates an unusual Porter-Duff composition method: Xor. (And the capability of
drawing circles.)
Just thought it was cool.
![Screenshot](screenshot.png)

76
examples/xor/main.go Normal file
View File

@ -0,0 +1,76 @@
package main
import (
"math"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)
func run() {
cfg := pixelgl.WindowConfig{
Title: "Xor",
Bounds: pixel.R(0, 0, 1024, 768),
Resizable: true,
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
imd := imdraw.New(nil)
canvas := pixelgl.NewCanvas(win.Bounds())
start := time.Now()
for !win.Closed() {
// in case window got resized, we also need to resize our canvas
canvas.SetBounds(win.Bounds())
offset := math.Sin(time.Since(start).Seconds()) * 300
// clear the canvas to be totally transparent and set the xor compose method
canvas.Clear(pixel.Alpha(0))
canvas.SetComposeMethod(pixel.ComposeXor)
// red circle
imd.Clear()
imd.Color = pixel.RGB(1, 0, 0)
imd.Push(win.Bounds().Center().Add(pixel.V(-offset, 0)))
imd.Circle(200, 0)
imd.Draw(canvas)
// blue circle
imd.Clear()
imd.Color = pixel.RGB(0, 0, 1)
imd.Push(win.Bounds().Center().Add(pixel.V(offset, 0)))
imd.Circle(150, 0)
imd.Draw(canvas)
// yellow circle
imd.Clear()
imd.Color = pixel.RGB(1, 1, 0)
imd.Push(win.Bounds().Center().Add(pixel.V(0, -offset)))
imd.Circle(100, 0)
imd.Draw(canvas)
// magenta circle
imd.Clear()
imd.Color = pixel.RGB(1, 0, 1)
imd.Push(win.Bounds().Center().Add(pixel.V(0, offset)))
imd.Circle(50, 0)
imd.Draw(canvas)
win.Clear(colornames.Green)
canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
win.Update()
}
}
func main() {
pixelgl.Run(run)
}

BIN
examples/xor/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

405
geometry.go Normal file
View File

@ -0,0 +1,405 @@
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 3x2 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]}
}

79
geometry_test.go Normal file
View File

@ -0,0 +1,79 @@
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
View File

@ -1,15 +0,0 @@
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
View File

@ -1,54 +0,0 @@
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,9 +128,7 @@ 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 property.
// TrianglesPicture specifies Triangles with Picture propery.
//
// 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,19 +102,10 @@ 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 within this rectangle.
// Bounds returns the rectangle of the Picture. All data is located witih this rectangle.
// Querying properties outside the rectangle should return default value of that property.
Bounds() Rect
}

View File

@ -1,699 +0,0 @@
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.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

15
math.go
View File

@ -1,15 +0,0 @@
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
}

View File

@ -1,46 +0,0 @@
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))
}
}
}

View File

@ -1,98 +0,0 @@
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,13 +1,10 @@
package pixel_test
import (
"fmt"
"math"
"math/rand"
"testing"
"github.com/faiface/pixel"
"github.com/stretchr/testify/assert"
)
func BenchmarkMatrix(b *testing.B) {
@ -64,86 +61,3 @@ 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))
})
}

View File

@ -1,68 +0,0 @@
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 *glhf.Shader
cmp pixel.ComposeMethod
mat mgl32.Mat3
@ -37,35 +37,30 @@ func NewCanvas(bounds pixel.Rect) *Canvas {
col: mgl32.Vec4{1, 1, 1, 1},
}
c.shader = NewGLShader(baseCanvasFragmentShader)
c.SetBounds(bounds)
var shader *glhf.Shader
mainthread.Call(func() {
var err error
shader, err = glhf.NewShader(
canvasVertexFormat,
canvasUniformFormat,
canvasVertexShader,
canvasFragmentShader,
)
if err != nil {
panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader"))
}
})
c.shader = shader
return c
}
// SetUniform will update the named uniform with the value of any supported underlying
// 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)
}
// 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()
}
// 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: gt,
dst: c,
}
}
return &canvasTriangles{
GLTriangles: NewGLTriangles(c.shader, t),
dst: c,
@ -284,41 +279,29 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
// save the current state vars to avoid race condition
cmp := ct.dst.cmp
smt := ct.dst.smooth
mat := ct.dst.mat
col := ct.dst.col
smt := ct.dst.smooth
mainthread.CallNonBlock(func() {
ct.dst.setGlhfBounds()
setBlendFunc(cmp)
frame := ct.dst.gf.Frame()
shader := ct.shader.s
shader := ct.dst.shader
frame.Begin()
shader.Begin()
ct.shader.uniformDefaults.transform = mat
ct.shader.uniformDefaults.colormask = col
dstBounds := ct.dst.Bounds()
ct.shader.uniformDefaults.bounds = mgl32.Vec4{
shader.SetUniformAttr(canvasBounds, mgl32.Vec4{
float32(dstBounds.Min.X),
float32(dstBounds.Min.Y),
float32(dstBounds.W()),
float32(dstBounds.H()),
}
bx, by, bw, bh := intBounds(bounds)
ct.shader.uniformDefaults.texbounds = mgl32.Vec4{
float32(bx),
float32(by),
float32(bw),
float32(bh),
}
for loc, u := range ct.shader.uniforms {
ct.shader.s.SetUniformAttr(loc, u.Value())
}
})
shader.SetUniformAttr(canvasTransform, mat)
shader.SetUniformAttr(canvasColorMask, col)
if tex == nil {
ct.vs.Begin()
@ -327,6 +310,14 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
} else {
tex.Begin()
bx, by, bw, bh := intBounds(bounds)
shader.SetUniformAttr(canvasTexBounds, mgl32.Vec4{
float32(bx),
float32(by),
float32(bw),
float32(bh),
})
if tex.Smooth() != smt {
tex.SetSmooth(smt)
}
@ -359,3 +350,82 @@ func (cp *canvasPicture) Draw(t pixel.TargetTriangles) {
}
ct.draw(cp.GLPicture.Texture(), cp.GLPicture.Bounds())
}
const (
canvasPosition int = iota
canvasColor
canvasTexCoords
canvasIntensity
)
var canvasVertexFormat = glhf.AttrFormat{
canvasPosition: {Name: "position", Type: glhf.Vec2},
canvasColor: {Name: "color", Type: glhf.Vec4},
canvasTexCoords: {Name: "texCoords", Type: glhf.Vec2},
canvasIntensity: {Name: "intensity", Type: glhf.Float},
}
const (
canvasTransform int = iota
canvasColorMask
canvasBounds
canvasTexBounds
)
var canvasUniformFormat = glhf.AttrFormat{
canvasTransform: {Name: "transform", Type: glhf.Mat3},
canvasColorMask: {Name: "colorMask", Type: glhf.Vec4},
canvasBounds: {Name: "bounds", Type: glhf.Vec4},
canvasTexBounds: {Name: "texBounds", Type: glhf.Vec4},
}
var canvasVertexShader = `
#version 330 core
in vec2 position;
in vec4 color;
in vec2 texCoords;
in float intensity;
out vec4 Color;
out vec2 TexCoords;
out float Intensity;
uniform mat3 transform;
uniform vec4 bounds;
void main() {
vec2 transPos = (transform * vec3(position, 1.0)).xy;
vec2 normPos = (transPos - bounds.xy) / bounds.zw * 2 - vec2(1, 1);
gl_Position = vec4(normPos, 0.0, 1.0);
Color = color;
TexCoords = texCoords;
Intensity = intensity;
}
`
var canvasFragmentShader = `
#version 330 core
in vec4 Color;
in vec2 TexCoords;
in float Intensity;
out vec4 color;
uniform vec4 colorMask;
uniform vec4 texBounds;
uniform sampler2D tex;
void main() {
if (Intensity == 0) {
color = colorMask * Color;
} else {
color = vec4(0, 0, 0, 0);
color += (1 - Intensity) * Color;
vec2 t = (TexCoords - texBounds.xy) / texBounds.zw;
color += Intensity * Color * texture(tex, t);
color *= colorMask;
}
}
`

View File

@ -1,298 +0,0 @@
package pixelgl
import (
"github.com/faiface/glhf"
"github.com/faiface/mainthread"
"github.com/go-gl/mathgl/mgl32"
"github.com/pkg/errors"
)
// 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 {
s *glhf.Shader
vf, uf glhf.AttrFormat
vs, fs string
uniforms []gsUniformAttr
uniformDefaults struct {
transform mgl32.Mat3
colormask mgl32.Vec4
bounds mgl32.Vec4
texbounds mgl32.Vec4
cliprect mgl32.Vec4
}
}
type gsUniformAttr struct {
Name string
Type glhf.AttrType
value interface{}
ispointer bool
}
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
shader, err = glhf.NewShader(
gs.vf,
gs.uf,
gs.vs,
gs.fs,
)
if err != nil {
panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader"))
}
})
gs.s = shader
}
// gets the uniform index from GLShader
func (gs *GLShader) getUniform(Name string) int {
for i, u := range gs.uniforms {
if u.Name == Name {
return i
}
}
return -1
}
// SetUniform appends a custom uniform name and value to the shader.
// if the uniform already exists, it will simply be overwritten.
//
// example:
//
// utime := float32(time.Since(starttime)).Seconds())
// mycanvas.shader.AddUniform("u_time", &utime)
func (gs *GLShader) SetUniform(name string, value interface{}) {
t, p := getAttrType(value)
if loc := gs.getUniform(name); loc > -1 {
gs.uniforms[loc].Name = name
gs.uniforms[loc].Type = t
gs.uniforms[loc].ispointer = p
gs.uniforms[loc].value = value
return
}
gs.uniforms = append(gs.uniforms, gsUniformAttr{
Name: name,
Type: t,
ispointer: p,
value: value,
})
}
// Value returns the attribute's concrete value. If the stored value
// is a pointer, we return the dereferenced value.
func (gu *gsUniformAttr) Value() interface{} {
if !gu.ispointer {
return gu.value
}
switch gu.Type {
case glhf.Vec2:
return *gu.value.(*mgl32.Vec2)
case glhf.Vec3:
return *gu.value.(*mgl32.Vec3)
case glhf.Vec4:
return *gu.value.(*mgl32.Vec4)
case glhf.Mat2:
return *gu.value.(*mgl32.Mat2)
case glhf.Mat23:
return *gu.value.(*mgl32.Mat2x3)
case glhf.Mat24:
return *gu.value.(*mgl32.Mat2x4)
case glhf.Mat3:
return *gu.value.(*mgl32.Mat3)
case glhf.Mat32:
return *gu.value.(*mgl32.Mat3x2)
case glhf.Mat34:
return *gu.value.(*mgl32.Mat3x4)
case glhf.Mat4:
return *gu.value.(*mgl32.Mat4)
case glhf.Mat42:
return *gu.value.(*mgl32.Mat4x2)
case glhf.Mat43:
return *gu.value.(*mgl32.Mat4x3)
case glhf.Int:
return *gu.value.(*int32)
case glhf.Float:
return *gu.value.(*float32)
default:
panic("invalid attrtype")
}
}
// Returns the type identifier for any (supported) attribute variable type
// and whether or not it is a pointer of that type.
func getAttrType(v interface{}) (glhf.AttrType, bool) {
switch v.(type) {
case int32:
return glhf.Int, false
case float32:
return glhf.Float, false
case mgl32.Vec2:
return glhf.Vec2, false
case mgl32.Vec3:
return glhf.Vec3, false
case mgl32.Vec4:
return glhf.Vec4, false
case mgl32.Mat2:
return glhf.Mat2, false
case mgl32.Mat2x3:
return glhf.Mat23, false
case mgl32.Mat2x4:
return glhf.Mat24, false
case mgl32.Mat3:
return glhf.Mat3, false
case mgl32.Mat3x2:
return glhf.Mat32, false
case mgl32.Mat3x4:
return glhf.Mat34, false
case mgl32.Mat4:
return glhf.Mat4, false
case mgl32.Mat4x2:
return glhf.Mat42, false
case mgl32.Mat4x3:
return glhf.Mat43, false
case *mgl32.Vec2:
return glhf.Vec2, true
case *mgl32.Vec3:
return glhf.Vec3, true
case *mgl32.Vec4:
return glhf.Vec4, true
case *mgl32.Mat2:
return glhf.Mat2, true
case *mgl32.Mat2x3:
return glhf.Mat23, true
case *mgl32.Mat2x4:
return glhf.Mat24, true
case *mgl32.Mat3:
return glhf.Mat3, true
case *mgl32.Mat3x2:
return glhf.Mat32, true
case *mgl32.Mat3x4:
return glhf.Mat34, true
case *mgl32.Mat4:
return glhf.Mat4, true
case *mgl32.Mat4x2:
return glhf.Mat42, true
case *mgl32.Mat4x3:
return glhf.Mat43, true
case *int32:
return glhf.Int, true
case *float32:
return glhf.Float, true
default:
panic("invalid AttrType")
}
}
var baseCanvasVertexShader = `
#version 330 core
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;
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;
}
`
var baseCanvasFragmentShader = `
#version 330 core
in vec4 vColor;
in vec2 vTexCoords;
in float vIntensity;
in vec4 vClipRect;
out vec4 fragColor;
uniform vec4 uColorMask;
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 {
fragColor = vec4(0, 0, 0, 0);
fragColor += (1 - vIntensity) * vColor;
vec2 t = (vTexCoords - uTexBounds.xy) / uTexBounds.zw;
fragColor += vIntensity * vColor * texture(uTexture, t);
fragColor *= uColorMask;
}
}
`

View File

@ -15,43 +15,23 @@ import (
type GLTriangles struct {
vs *glhf.VertexSlice
data []float32
shader *GLShader
shader *glhf.Shader
}
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 *GLShader, t pixel.Triangles) *GLTriangles {
func NewGLTriangles(shader *glhf.Shader, t pixel.Triangles) *GLTriangles {
var gt *GLTriangles
mainthread.Call(func() {
gt = &GLTriangles{
vs: glhf.MakeVertexSlice(shader.s, 0, t.Len()),
vs: glhf.MakeVertexSlice(shader, 0, t.Len()),
shader: shader,
}
})
@ -68,7 +48,7 @@ func (gt *GLTriangles) VertexSlice() *glhf.VertexSlice {
}
// Shader returns the GLTriangles's associated shader.
func (gt *GLTriangles) Shader() *GLShader {
func (gt *GLTriangles) Shader() *glhf.Shader {
return gt.shader
}
@ -90,7 +70,6 @@ func (gt *GLTriangles) SetLen(length int) {
1, 1, 1, 1,
0, 0,
0,
0, 0, 0, 0,
)
}
case length < gt.Len():
@ -98,7 +77,7 @@ func (gt *GLTriangles) SetLen(length int) {
default:
return
}
mainthread.Call(func() {
mainthread.CallNonBlock(func() {
gt.vs.Begin()
gt.vs.SetLen(length)
gt.vs.End()
@ -131,22 +110,17 @@ 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+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)
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)
}
return
}
@ -154,34 +128,25 @@ 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+triPosX] = float32(px)
gt.data[i*stride+triPosY] = float32(py)
gt.data[i*stride+0] = float32(px)
gt.data[i*stride+1] = float32(py)
}
}
if t, ok := t.(pixel.TrianglesColor); ok {
for i := 0; i < length; i++ {
col := t.Color(i)
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)
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)
}
}
if t, ok := t.(pixel.TrianglesPicture); ok {
for i := 0; i < length; i++ {
pic, intensity := t.Picture(i)
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)
gt.data[i*stride+6] = float32(pic.X)
gt.data[i*stride+7] = float32(pic.Y)
gt.data[i*stride+8] = float32(intensity)
}
}
}
@ -195,12 +160,6 @@ 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
@ -226,31 +185,19 @@ 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[gt.index(i, triPosX)]
py := gt.data[gt.index(i, triPosY)]
px := gt.data[i*gt.vs.Stride()+0]
py := gt.data[i*gt.vs.Stride()+1]
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[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)]
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]
return pixel.RGBA{
R: float64(r),
G: float64(g),
@ -259,44 +206,10 @@ 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[gt.index(i, triPicX)]
ty := gt.data[gt.index(i, triPicY)]
intensity = float64(gt.data[gt.index(i, triIntensity)])
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])
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)
}

Some files were not shown because too many files have changed in this diff Show More