Compare commits
649 Commits
Author | SHA1 | Date |
---|---|---|
|
0a251bc08b | |
|
d3c72ae210 | |
|
20c056b950 | |
|
9ad345b415 | |
|
174abb179a | |
|
4c172faca7 | |
|
d899a6bbed | |
|
a9abe2ebb7 | |
|
ab135a4965 | |
|
08fc8acdfa | |
|
069cc4e011 | |
|
2c3a9a03bd | |
|
cb394bc26f | |
|
8acf4e5195 | |
|
ccc8deb923 | |
|
e448943963 | |
|
8ce328cb89 | |
|
389317124e | |
|
33024feabf | |
|
d119f130f6 | |
|
b61f150701 | |
|
f079cc25fe | |
|
9e8e09f1d7 | |
|
96863238e9 | |
|
649411a23c | |
|
d940a42454 | |
|
160e665d51 | |
|
1d936eae7b | |
|
15972155d3 | |
|
4964768d4e | |
|
d5761bda94 | |
|
552871e324 | |
|
4a8b06746f | |
|
7fe32e1553 | |
|
6c717101a7 | |
|
881bfeda91 | |
|
b4d312046c | |
|
f6e2f3a8d8 | |
|
4ec3c64512 | |
|
bb0a0f485e | |
|
aae9927b58 | |
|
9307280c91 | |
|
7615c055cf | |
|
80b107c039 | |
|
98f3e740e6 | |
|
798c6c2a48 | |
|
063b8952f9 | |
|
842ae8d470 | |
|
b31c294d4d | |
|
75e7b4120b | |
|
0e9102e224 | |
|
2101dc89e3 | |
|
eae5a2e96b | |
|
24ffdadbf8 | |
|
f931f69d6e | |
|
6e34aae429 | |
|
cef3a7e7a6 | |
|
fd754b5263 | |
|
ef6cafa0cb | |
|
7a67b10cec | |
|
7027267cbf | |
|
ba7fa122b2 | |
|
65ff420b25 | |
|
fd73dd5216 | |
|
775e23efac | |
|
ae6c93ba96 | |
|
3b599e70ec | |
|
7d92e04e63 | |
|
c9681cec8a | |
|
2342984744 | |
|
a8b7779fbe | |
|
7a106fa572 | |
|
e66e94b943 | |
|
d6663ba0a0 | |
|
50e1d8c96b | |
|
2c79e7325a | |
|
865e930023 | |
|
88b1e1c2d3 | |
|
850dd62a98 | |
|
926463aa6f | |
|
543819d066 | |
|
c3e5e4fdb2 | |
|
e79a8c37e6 | |
|
d65a63e962 | |
|
6cef9cf779 | |
|
1f914181d8 | |
|
a61d321615 | |
|
585f7c1f14 | |
|
607e0a11c0 | |
|
506588f40b | |
|
42582c7f8e | |
|
625c4c393d | |
|
5568202e30 | |
|
70eff126da | |
|
5d34c08754 | |
|
c35e0abd1c | |
|
a3f51715d9 | |
|
b5e4674b45 | |
|
9a9f831114 | |
|
6b71245324 | |
|
5254b4eb62 | |
|
76534cb8d0 | |
|
8792d4436c | |
|
d2d1f03c32 | |
|
6a71f07d12 | |
|
79a3bd7c63 | |
|
b7d5e41486 | |
|
5e3b82eeab | |
|
d7884ac96a | |
|
b40ab4939f | |
|
6b4a6905de | |
|
0a8feedd8a | |
|
914e25233f | |
|
197e604220 | |
|
e350f9fef1 | |
|
d8a3c92052 | |
|
d35b25061e | |
|
b9c32bc51f | |
|
0110a3c88a | |
|
3be4e4cb84 | |
|
bd68a95818 | |
|
d181dae9ba | |
|
42b1229128 | |
|
37f588845a | |
|
8909d199d1 | |
|
09ea5585a0 | |
|
8012403b8f | |
|
dbdea628da | |
|
fc543042cc | |
|
ed63c18bb4 | |
|
9cc1cddcf2 | |
|
73ce868aa9 | |
|
effd43bc56 | |
|
fc29ad7886 | |
|
93a24aa6df | |
|
7e3a88ef63 | |
|
484c1775b0 | |
|
a3aba6004d | |
|
e51d4a6676 | |
|
87299dc29b | |
|
0d44d2cc9b | |
|
c4f9faea97 | |
|
18972c2818 | |
|
853405f61c | |
|
8d0c306de9 | |
|
837a4efa80 | |
|
706becb764 | |
|
bb82a87aaf | |
|
f24d465808 | |
|
aeef94f200 | |
|
2aa6d2d212 | |
|
8a9191c283 | |
|
07754109cb | |
|
16d04f467f | |
|
a5c3bec928 | |
|
d513ffd192 | |
|
c5c5127c8d | |
|
109cb03fae | |
|
23171aea6c | |
|
dd5e0d8b09 | |
|
3c6f33365b | |
|
38283d0e55 | |
|
daa8205b8c | |
|
ba6fc7db8a | |
|
285676ca17 | |
|
b18647916f | |
|
de3617a259 | |
|
1f96c1e995 | |
|
a68a4e38b4 | |
|
9e6e6197dd | |
|
159df28dcc | |
|
d48ef44bdc | |
|
9aca3bfe7a | |
|
2cf81cd4df | |
|
3aad3268cd | |
|
9d5c7afe12 | |
|
85b18b567a | |
|
e8ba24ea40 | |
|
d601bc65e4 | |
|
2cb87efb8c | |
|
ccf8e9dcdc | |
|
95ac5e1e2a | |
|
96e0a8f3bf | |
|
f698bae1df | |
|
a6c8b92517 | |
|
9eb2834a10 | |
|
0092d6a577 | |
|
b8bb00a161 | |
|
29b1220ec3 | |
|
364a7a84ae | |
|
bcda85acd2 | |
|
966150a856 | |
|
f3377bb16f | |
|
4795a92b41 | |
|
83c62a0313 | |
|
c3e69c4f35 | |
|
352785e1b8 | |
|
e6392a228d | |
|
3592de858c | |
|
a1d36f8c7e | |
|
98d5b9b417 | |
|
e5ff236d71 | |
|
faf6558294 | |
|
e99ac56daa | |
|
aa7ef59ecd | |
|
eb3d7e0787 | |
|
11e2012ef5 | |
|
8cd352b7a3 | |
|
997f23dfb5 | |
|
04c3ef72a3 | |
|
237d77596f | |
|
c7eac06499 | |
|
55b87ca5b1 | |
|
2478da5d12 | |
|
cca37c750e | |
|
1a275b5929 | |
|
9d07d69429 | |
|
128ec4d4c0 | |
|
1eac5d8dc2 | |
|
a5ea71811e | |
|
d3912a6486 | |
|
0db4348595 | |
|
b2c8bff6eb | |
|
95148365d0 | |
|
50ef216e79 | |
|
09db6d8b38 | |
|
86de5123c0 | |
|
470791a59d | |
|
0475b1ddb4 | |
|
c9328c9260 | |
|
c7836cb3ad | |
|
3d2f083f70 | |
|
771e2e92e2 | |
|
09b50d2296 | |
|
66670d9905 | |
|
489b03138f | |
|
3cc0ec3d30 | |
|
b3246f6234 | |
|
680d16c17b | |
|
8563c28493 | |
|
160bcb9960 | |
|
6f1e3bbbf8 | |
|
cc9f03d393 | |
|
b61602cdf4 | |
|
f21c48599f | |
|
ab70533793 | |
|
0a0c8ff110 | |
|
5abf2d29a6 | |
|
4d30a8fe49 | |
|
ecda96a36f | |
|
520459bc6d | |
|
e225e9a1ef | |
|
13171c409b | |
|
2e275ab0d5 | |
|
23168ee324 | |
|
44d030b9ca | |
|
b06a31baa3 | |
|
4c6d061455 | |
|
620c551f70 | |
|
16722d55e1 | |
|
ee24eeb67d | |
|
5072f34b91 | |
|
d4530ca9fe | |
|
3b63b7eff9 | |
|
91c16c34da | |
|
e8a3621140 | |
|
730d33a341 | |
|
42cc2a0376 | |
|
742d1226d8 | |
|
0a054f35fd | |
|
392fe90b11 | |
|
2e0da4f44a | |
|
b0ed22e0ec | |
|
0aec9fead0 | |
|
1e19db5b25 | |
|
a0713598da | |
|
3b366d1edd | |
|
cabaee680e | |
|
8650692efa | |
|
ba4f417559 | |
|
ecd686769c | |
|
2cce50832d | |
|
c70d7575ce | |
|
4eba5e37ae | |
|
0cddb56114 | |
|
4b6cf201f7 | |
|
cff8697c97 | |
|
d0ceea6849 | |
|
56cd321ab8 | |
|
8efed1de5c | |
|
9a32601c6b | |
|
fdc068e855 | |
|
9b8d4c7461 | |
|
a56f7fa422 | |
|
0d5ba92fbe | |
|
20b05d9ec2 | |
|
d102169151 | |
|
f1ad821f1a | |
|
3be890ea80 | |
|
a46f6f6cf4 | |
|
9e0f11abbb | |
|
8d20deea05 | |
|
e7a625f5d2 | |
|
d3f6331240 | |
|
ee54171247 | |
|
2b79131104 | |
|
4c817d7c20 | |
|
3bed5a8449 | |
|
a1bbfa3020 | |
|
026878de07 | |
|
52b4384240 | |
|
676509d856 | |
|
bdd9349b37 | |
|
c18b8f1c29 | |
|
7b509e1d7d | |
|
4a0b2f5107 | |
|
17e407f0c1 | |
|
0c2d627cfa | |
|
749271768b | |
|
dc545043c1 | |
|
f4edf628b1 | |
|
8f9e91e617 | |
|
d7dcc8d167 | |
|
e9dfd22ffa | |
|
fa10844351 | |
|
a25a444cbf | |
|
45642083a2 | |
|
55d3a6ab00 | |
|
87980dd647 | |
|
88243fbfa4 | |
|
c1ead8e0a0 | |
|
578343e58c | |
|
ebabf74263 | |
|
abfdf18fb6 | |
|
49ee5d6e77 | |
|
74ef0e3256 | |
|
a8270e58e0 | |
|
446247e369 | |
|
2a1ab90ad5 | |
|
9817425914 | |
|
2e38a287de | |
|
1ea9235b6f | |
|
f2f9fd315d | |
|
d6a898e82f | |
|
3cf59b1044 | |
|
a7bcddc921 | |
|
db33885652 | |
|
baabf3adac | |
|
bb1d0b2d10 | |
|
4498635b26 | |
|
c871238e9f | |
|
b157c890d6 | |
|
8d8d5cc9db | |
|
ad738cddf8 | |
|
2dff742a8b | |
|
d4ae092923 | |
|
9436339e0b | |
|
42e7d07767 | |
|
295498ab3e | |
|
cb5a468ba2 | |
|
98be0b43c8 | |
|
6c69d9b06b | |
|
a151e31929 | |
|
08308279ca | |
|
2a32545253 | |
|
eedeb85b14 | |
|
3c78161418 | |
|
5e2a944a48 | |
|
e554b1e3c1 | |
|
cc4c905b49 | |
|
48e37828c8 | |
|
78a2373870 | |
|
721a10ef10 | |
|
c87741300d | |
|
62cefc262e | |
|
ce09bb1114 | |
|
7c5e5588e2 | |
|
977f5710f5 | |
|
e3d056d36f | |
|
3e3a9aaa48 | |
|
96df742331 | |
|
4d94c04476 | |
|
9a6fbad6ce | |
|
7624d11cfc | |
|
35308e4ebf | |
|
52b52e4eca | |
|
d325ce3198 | |
|
8ad3d94d7e | |
|
1db65503e3 | |
|
217fbdb30c | |
|
3a40ec4435 | |
|
82afa289f4 | |
|
502347d987 | |
|
55c94b6cc5 | |
|
81dbcb5bd1 | |
|
78674d35b9 | |
|
d1953900cc | |
|
35a24edbda | |
|
ce53548e84 | |
|
72b34b78c4 | |
|
81de53181c | |
|
3851f37503 | |
|
a748a0cdce | |
|
eb429bea68 | |
|
7ecd50bd45 | |
|
c6b821bd67 | |
|
f97894ad37 | |
|
95b9f23076 | |
|
0d3d384378 | |
|
555a1cb8e4 | |
|
a2ba6d090d | |
|
4b70585f1a | |
|
ee258c1d13 | |
|
8e06ab198e | |
|
5c00d43168 | |
|
033fca94e0 | |
|
0eb5bf4c06 | |
|
238400db27 | |
|
7858a55088 | |
|
140b050545 | |
|
6c2bffed37 | |
|
bb2ffdec2c | |
|
8d9485af7e | |
|
cc1e4c9381 | |
|
7518c708d3 | |
|
00cab859c1 | |
|
f542f75202 | |
|
6dd3a9a643 | |
|
538d42f9a6 | |
|
e7a7b16be2 | |
|
c91a8fd6e9 | |
|
777c7f1717 | |
|
e64d97efb1 | |
|
da52aa25ec | |
|
69d4a37e5e | |
|
2b9b4d07c4 | |
|
d41492df91 | |
|
72a4dd03e7 | |
|
5723022b5a | |
|
f3e2b915bd | |
|
c142c3eb0a | |
|
a1542703d5 | |
|
019c0f83e1 | |
|
ada24fe35c | |
|
c3ce0517f1 | |
|
be247e49f5 | |
|
dd1ddd15b3 | |
|
96f9ed3622 | |
|
e41c8b585b | |
|
2c9c36ba28 | |
|
03f6b2d854 | |
|
95f90d3b0d | |
|
7f4bc89dc4 | |
|
578db5e284 | |
|
ad2b94bfbc | |
|
983146ea91 | |
|
19b10859ad | |
|
886e7fa22a | |
|
99a43ec1a9 | |
|
12df203229 | |
|
d4cd1c33e2 | |
|
51cd0314d5 | |
|
6a9211310a | |
|
ee5d49dbd3 | |
|
daedc45ea9 | |
|
678da34fc3 | |
|
f68301dcd8 | |
|
cfdc8beb81 | |
|
1fd110ce4c | |
|
3665eaf702 | |
|
c0766504e3 | |
|
5f2ced88ee | |
|
ce8687b80f | |
|
c6e7a83467 | |
|
b6533006e7 | |
|
9d1fc5bd1f | |
|
c321515d3c | |
|
1586e600a0 | |
|
9bd9df98f2 | |
|
793d1e6a9a | |
|
f0c42c6e56 | |
|
fa2c741fcf | |
|
28aad855b8 | |
|
f179118d9e | |
|
4a0a68effc | |
|
4dc0fb6f8b | |
|
bbde3a6109 | |
|
a056444410 | |
|
a5603e8379 | |
|
b8a287a654 | |
|
ce8408054d | |
|
523e6d3e9a | |
|
fcfeb200b6 | |
|
8221ab58bc | |
|
9b7b8d5a0e | |
|
85ef4290b8 | |
|
37cd58f0ae | |
|
aa50147c35 | |
|
022f25895a | |
|
6735475b44 | |
|
fdddde2780 | |
|
fb51cd9ecb | |
|
be2434cfa8 | |
|
9062f1eae9 | |
|
c9319763d7 | |
|
9554cd9c20 | |
|
ef86fe9b20 | |
|
101637439e | |
|
b0e2bd1035 | |
|
e1f364e5d1 | |
|
ef5de4c8c3 | |
|
ee6871c7b8 | |
|
fc8eafe3d5 | |
|
81e2e645bd | |
|
067d9f48d9 | |
|
3035fcac9c | |
|
c5df68f8bb | |
|
c1f3267176 | |
|
e1dba0eb54 | |
|
e7a7ac4026 | |
|
847b48292b | |
|
cfa9180bb7 | |
|
6ce4094935 | |
|
c0378a703e | |
|
b41a436dce | |
|
68008f163a | |
|
75d68a6963 | |
|
ad606d2d0a | |
|
a5414dbb55 | |
|
f52e3db155 | |
|
b333cd2e0b | |
|
8797440f38 | |
|
75a25a0df6 | |
|
af0330d453 | |
|
1619373062 | |
|
1bb5353ec7 | |
|
7dc94990e4 | |
|
91448dcd68 | |
|
48f3d5cb3e | |
|
e112598b5c | |
|
0d3fd03a40 | |
|
2a060fe944 | |
|
5f24ce9ccb | |
|
90b9fcbf23 | |
|
e3632860b0 | |
|
b463f91a6f | |
|
a9d735dafe | |
|
ec4f5e3663 | |
|
650263a314 | |
|
4de5df6980 | |
|
a730295ce8 | |
|
4e7e3956db | |
|
ac4239754f | |
|
8ae8551fda | |
|
2c1528a927 | |
|
ce6b0a47c5 | |
|
b6620c9775 | |
|
2fa80f7d42 | |
|
c12d22757d | |
|
31e5a6ad34 | |
|
b7f67ee1f5 | |
|
cadd699cf5 | |
|
5545a9cecc | |
|
b991a8267d | |
|
8643f6d0d0 | |
|
ca86b961f2 | |
|
4cff721955 | |
|
5371f8a2dc | |
|
8ea09ac04c | |
|
219fb230fb | |
|
1246119135 | |
|
75ab42464d | |
|
0850a249c9 | |
|
ad8f6b8c5a | |
|
f1db09ce02 | |
|
66acc3b742 | |
|
68abc0fffd | |
|
5329a93709 | |
|
e0d0d41037 | |
|
8b53137331 | |
|
8135f7b3b9 | |
|
c2935d3c5b | |
|
2a258ce7a5 | |
|
3eaf900a1c | |
|
6f3d899404 | |
|
7725b2d9e1 | |
|
c2c106d34d | |
|
56e5dcf593 | |
|
91ecf9ee1b | |
|
fbe1db6fd5 | |
|
d037091f5a | |
|
b4ba58d433 | |
|
ece4dd1d1a | |
|
812c2b5d9e | |
|
639bd44303 | |
|
d214312c06 | |
|
7b7b663f7d | |
|
acb14d8c9a | |
|
00bd0fe945 | |
|
86ecc75efb | |
|
aa22fe0dca | |
|
663727731c | |
|
ac61175aea | |
|
fc2a7a8afc | |
|
9d48c4a828 | |
|
73cb8ef789 | |
|
2b83dae1f4 | |
|
79b6412a14 | |
|
f0303c27af | |
|
7939b1fdb7 | |
|
8030595373 | |
|
9472b91565 | |
|
4abba37aa3 | |
|
4ce6e2b3c4 | |
|
0bfc4cd068 | |
|
b2819732fc | |
|
87b57f21cb | |
|
cb9e936395 | |
|
bce553aeec | |
|
592eb4b8ef | |
|
af6241d010 | |
|
02e009054d | |
|
41e5e8ca77 | |
|
3e65588e00 | |
|
5d82c3cdb9 | |
|
8489bdad27 | |
|
c2633848f6 | |
|
8379e17cac | |
|
5be03c8beb | |
|
3068153419 | |
|
c1d000859e | |
|
8da51853be | |
|
bb7b98eda8 | |
|
9238e701a7 | |
|
ed8abc4dc0 | |
|
e0615451b4 | |
|
f4916a4272 | |
|
7d31dd8d6f | |
|
e6af9eb3c2 | |
|
5097bc9cac | |
|
8db95af845 | |
|
b41e117ac6 | |
|
49338251d2 | |
|
b135012115 | |
|
1a2ca3264d | |
|
46c8784df0 | |
|
f9482d311f | |
|
1b32c1a04b | |
|
0b3cfac547 |
29
.travis.yml
|
@ -1,4 +1,6 @@
|
|||
language: go
|
||||
# https://github.com/golang/go/issues/31293
|
||||
dist: xenial
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
|
@ -11,13 +13,22 @@ addons:
|
|||
- libxi-dev
|
||||
- libopenal-dev
|
||||
- libasound2-dev
|
||||
go:
|
||||
- 1.8
|
||||
- 1.7.4
|
||||
- tip
|
||||
install:
|
||||
- go get -t ./...
|
||||
script:
|
||||
- go test -i -race ./...
|
||||
- go test -v -race ./...
|
||||
- libgl1-mesa-dev
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
go:
|
||||
- tip
|
||||
- 1.12.x
|
||||
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent the default install action
|
||||
# "go get -t -v ./..." from happening here because we want it to happen
|
||||
# inside script step.
|
||||
|
||||
script:
|
||||
- go test -v -race -mod=readonly ./...
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
- Add AnchorPos struct and functions #252
|
||||
- Add Clipboard Support
|
||||
- Fix SIGSEGV on text.NewAtlas if glyph absent
|
||||
- Use slice for range in Drawer.Dirty(), to improve performance
|
||||
- GLTriangle's fragment shader is used when rendered by the Canvas.
|
||||
- Add MSAA support
|
||||
|
||||
## [v0.10.0] 2020-08-22
|
||||
- Add AnchorPos struct and functions
|
||||
- Gamepad API added
|
||||
- Support setting an initial window position
|
||||
- Support hiding the window initially
|
||||
- Support creating maximized windows
|
||||
- Support waiting for events to reduce CPU load
|
||||
- Adding clipping rectangle support in GLTriangles
|
||||
|
||||
## [v0.10.0-beta] 2020-05-10
|
||||
- Add `WindowConfig.TransparentFramebuffer` option to support window transparency onto the background
|
||||
- Fixed Line intersects failing on lines passing through (0, 0)
|
||||
|
||||
## [v0.10.0-alpha] 2020-05-08
|
||||
- Upgrade to GLFW 3.3! :tada:
|
||||
- Closes https://github.com/faiface/pixel/issues/137
|
||||
- Add support for glfw's DisableCursor
|
||||
- Closes https://github.com/faiface/pixel/issues/213
|
||||
|
||||
## [v0.9.0] - 2020-05-02
|
||||
- Added feature from https://github.com/faiface/pixel/pull/219
|
||||
- Exposing Window.SwapBuffers so buffers can be swapped without polling input
|
||||
- Add more examples
|
||||
- Add position as out variable from vertex shader
|
||||
- Add experimental joystick support
|
||||
- Add mouse cursor operations
|
||||
- Add `Vec.Floor(…)` function
|
||||
- Add circle geometry
|
||||
- Fix `Matrix.Unproject(…)` for rotated matrix
|
||||
- Add 2D Line geometry
|
||||
- Add floating point round error correction
|
||||
- Performance improvements
|
||||
- Fix race condition in `NewGLTriangles(…)`
|
||||
- Add `TriangleData` benchmarks and improvements
|
||||
- Add zero rectangle variable for utility and consistency
|
||||
- Add support for Go Modules
|
||||
- Add `NoIconify` and `AlwaysOnTop` window hints
|
||||
|
||||
|
||||
## [v0.8.0] - 2018-10-10
|
||||
Changelog for this and older versions can be found on the corresponding [GitHub
|
||||
releases](https://github.com/faiface/pixel/releases).
|
||||
|
||||
[Unreleased]: https://github.com/faiface/pixel/compare/v0.10.0...HEAD
|
||||
[v0.10.0]: https://github.com/faiface/pixel/compare/v0.10.0-beta...v0.10.0
|
||||
[v0.10.0-beta]: https://github.com/faiface/pixel/compare/v0.10.0-alpha...v0.10.0-beta
|
||||
[v0.10.0-alpha]: https://github.com/faiface/pixel/compare/v0.9.0...v0.10.0-alpha
|
||||
[v0.9.0]: https://github.com/faiface/pixel/compare/v0.8.0...v0.9.0
|
||||
[v0.8.0]: https://github.com/faiface/pixel/releases/tag/v0.8.0
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
## Here are a few ways you can contribute
|
||||
|
||||
1. **Make a community example** and place it inside the [examples/community](examples/community) folder.
|
||||
1. **Make a community example** and place it inside the `community` folder of the [examples repository][examples].
|
||||
2. **Add tests**. There only few tests in Pixel at the moment. Take a look at them and make some similar.
|
||||
3. **Add a small feature or an improvement**. Feel like some small feature is missing? Just make a PR. Be ready that I might reject it, though, if I don't find it particularly appealing.
|
||||
4. **Join the big development** by joining the discussion at the [Gitter](https://gitter.im/pixellib/Lobby), where we can discuss bigger changes and implement them after that.
|
||||
4. **Join the big development** by joining the discussion on our [Discord Server](https://discord.gg/q2DK4MP), where we can discuss bigger changes and implement them after that.
|
||||
|
||||
## How to make a pull request
|
||||
|
||||
Go gives you a nice surprise when attempting to make a PR on Github. The thing is, that when user _xyz_ forks Pixel on Github, it ends up in _github.com/xyz/pixel_, which fucks up your import paths. Here's how you deal with that: https://www.reddit.com/r/golang/comments/2jdcw1/how_do_you_deal_with_github_forking/.
|
||||
|
||||
[examples]: https://github.com/faiface/pixel-examples/tree/master/community
|
||||
|
|
57
README.md
|
@ -1,5 +1,12 @@
|
|||
# Pixel [](https://travis-ci.org/faiface/pixel) [](https://godoc.org/github.com/faiface/pixel) [](https://goreportcard.com/report/github.com/faiface/pixel) [](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
# \*\*\*\*\*NOTICE\*\*\*\*\*
|
||||
|
||||
This repo is not under active development anymore and has been archived. Continued development has been migrated to [Pixel2](https://github.com/gopxl/pixel). A big thank you to [faiface](https://github.com/faiface) for creating this awesome library and for all the hard work put into it. We encourage old and new users to check out the new repo and contribute to it.
|
||||
|
||||
|
||||
|
||||
<p align="center"><img src="logo/LOGOTYPE-HORIZONTAL-BLUE.png"></p>
|
||||
|
||||
# Pixel [](https://travis-ci.org/faiface/pixel) [](https://godoc.org/github.com/faiface/pixel) [](https://goreportcard.com/report/github.com/faiface/pixel) [](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://discord.gg/q2DK4MP)
|
||||
|
||||
A hand-crafted 2D game library in Go. Take a look into the [features](#features) to see what it can
|
||||
do.
|
||||
|
@ -8,8 +15,18 @@ do.
|
|||
go get github.com/faiface/pixel
|
||||
```
|
||||
|
||||
If you are using Modules (Go 1.11 or higher) and want a mutable copy of the source code:
|
||||
|
||||
```
|
||||
git clone https://github.com/faiface/pixel # clone outside of $GOPATH
|
||||
cd pixel
|
||||
go install ./...
|
||||
```
|
||||
|
||||
See [requirements](#requirements) for the list of libraries necessary for compilation.
|
||||
|
||||
All significant changes are documented in [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
## Tutorial
|
||||
|
||||
The [Wiki of this repo](https://github.com/faiface/pixel/wiki) contains an extensive tutorial
|
||||
|
@ -22,32 +39,33 @@ 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
|
||||
## [Examples](https://github.com/faiface/pixel-examples)
|
||||
|
||||
The [examples](https://github.com/faiface/pixel/tree/master/examples) directory contains a few
|
||||
The [examples](https://github.com/faiface/pixel-examples) repository 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 examples/platformer
|
||||
$ cd pixel-examples/platformer
|
||||
$ go run main.go
|
||||
```
|
||||
|
||||
Here are some screenshots from the examples!
|
||||
|
||||
| [Lights](examples/lights) | [Platformer](examples/platformer) |
|
||||
| [Lights](https://github.com/faiface/pixel-examples/blob/master/lights) | [Platformer](https://github.com/faiface/pixel-examples/blob/master/platformer) |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
| [Smoke](examples/smoke) | [Typewriter](examples/typewriter) |
|
||||
| [Smoke](https://github.com/faiface/pixel-examples/blob/master/smoke) | [Typewriter](https://github.com/faiface/pixel-examples/blob/master/typewriter) |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
| [Raycaster](examples/community/raycaster) | [Starfield](examples/community/starfield) |
|
||||
| [Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster) | [Gizmo](https://github.com/Lallassu/gizmo) |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -93,6 +111,14 @@ Here's the list of the main features in Pixel. Although Pixel is still under hea
|
|||
- Small codebase, ~5K lines of code, including the backend [glhf](https://github.com/faiface/glhf)
|
||||
package
|
||||
|
||||
## Related repositories
|
||||
|
||||
Here are some packages which use Pixel:
|
||||
- [TilePix](https://github.com/bcvery1/tilepix) Makes handling TMX files built with [Tiled](https://www.mapeditor.org/) trivially easy to work with using Pixel.
|
||||
- [spriteplus](https://github.com/cebarks/spriteplus) Basic `SpriteSheet` and `Animation` implementations
|
||||
- [PixelUI](https://github.com/dusk125/pixelui) Imgui-based GUIs for Pixel
|
||||
- [pixelutils](https://github.com/dusk125/pixelutils) Variety of game related utilities (sprite packer, id generator, ticker, sprite loader, voronoia diagrams)
|
||||
|
||||
## Missing features
|
||||
|
||||
Pixel is in development and still missing few critical features. Here're the most critical ones.
|
||||
|
@ -103,8 +129,9 @@ Pixel is in development and still missing few critical features. Here're the mos
|
|||
- ~~Advanced window manipulation (cursor hiding, window icon, ...)~~
|
||||
- Better support for Hi-DPI displays
|
||||
- Mobile (and perhaps HTML5?) backend
|
||||
- More advanced graphical effects (e.g. blur)
|
||||
- ~~More advanced graphical effects (e.g. blur)~~ (solved with the addition of GLSL effects)
|
||||
- Tests and benchmarks
|
||||
- Vulkan support
|
||||
|
||||
**Implementing these features will get us to the 1.0 release.** Contribute, so that it's as soon as
|
||||
possible!
|
||||
|
@ -125,7 +152,7 @@ The OpenGL version used is **OpenGL 3.3**.
|
|||
headers and libraries.
|
||||
- On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev` and `xorg-dev` packages.
|
||||
- On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel
|
||||
libXinerama-devel mesa-libGL-devel libXi-devel` packages.
|
||||
libXinerama-devel mesa-libGL-devel libXi-devel libXxf86vm-devel` packages.
|
||||
- See [here](http://www.glfw.org/docs/latest/compile.html#compile_deps) for full details.
|
||||
|
||||
**The combination of Go 1.8, macOS and latest XCode seems to be problematic** as mentioned in issue
|
||||
|
@ -134,6 +161,8 @@ The OpenGL version used is **OpenGL 3.3**.
|
|||
|
||||
## Contributing
|
||||
|
||||
Join us in the [Discord Chat!](https://discord.gg/q2DK4MP)
|
||||
|
||||
Pixel is in, let's say, mid-stage of development. Many of the important features are here, some are
|
||||
missing. That's why **contributions are very important and welcome!** All alone, I will be able to
|
||||
finish the library, but it'll take a lot of time. With your help, it'll take much less. I encourage
|
||||
|
@ -151,10 +180,6 @@ better result.
|
|||
|
||||
Take a look at [CONTRIBUTING.md](CONTRIBUTING.md) for further information.
|
||||
|
||||
For any kind of discussion, feel free to use our
|
||||
[Gitter](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
community.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
|
2
batch.go
|
@ -26,7 +26,7 @@ var _ BasicTarget = (*Batch)(nil)
|
|||
//
|
||||
// Note, that if the container does not support TrianglesColor, color masking will not work.
|
||||
func NewBatch(container Triangles, pic Picture) *Batch {
|
||||
b := &Batch{cont: Drawer{Triangles: container, Picture: pic}}
|
||||
b := &Batch{cont: Drawer{Triangles: container, Picture: pic, Cached: true}}
|
||||
b.SetMatrix(IM)
|
||||
b.SetColorMask(Alpha(1))
|
||||
return b
|
||||
|
|
|
@ -0,0 +1,334 @@
|
|||
package pixel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Circle is a 2D circle. It is defined by two properties:
|
||||
// - Center vector
|
||||
// - Radius float64
|
||||
type Circle struct {
|
||||
Center Vec
|
||||
Radius float64
|
||||
}
|
||||
|
||||
// C returns a new Circle with the given radius and center coordinates.
|
||||
//
|
||||
// Note that a negative radius is valid.
|
||||
func C(center Vec, radius float64) Circle {
|
||||
return Circle{
|
||||
Center: center,
|
||||
Radius: radius,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of the Circle.
|
||||
//
|
||||
// c := pixel.C(10.1234, pixel.ZV)
|
||||
// c.String() // returns "Circle(10.12, Vec(0, 0))"
|
||||
// fmt.Println(c) // Circle(10.12, Vec(0, 0))
|
||||
func (c Circle) String() string {
|
||||
return fmt.Sprintf("Circle(%s, %.2f)", c.Center, c.Radius)
|
||||
}
|
||||
|
||||
// Norm returns the Circle in normalized form - this sets the radius to its absolute value.
|
||||
//
|
||||
// c := pixel.C(-10, pixel.ZV)
|
||||
// c.Norm() // returns pixel.Circle{pixel.Vec{0, 0}, 10}
|
||||
func (c Circle) Norm() Circle {
|
||||
return Circle{
|
||||
Center: c.Center,
|
||||
Radius: math.Abs(c.Radius),
|
||||
}
|
||||
}
|
||||
|
||||
// Area returns the area of the Circle.
|
||||
func (c Circle) Area() float64 {
|
||||
return math.Pi * math.Pow(c.Radius, 2)
|
||||
}
|
||||
|
||||
// Moved returns the Circle moved by the given vector delta.
|
||||
func (c Circle) Moved(delta Vec) Circle {
|
||||
return Circle{
|
||||
Center: c.Center.Add(delta),
|
||||
Radius: c.Radius,
|
||||
}
|
||||
}
|
||||
|
||||
// Resized returns the Circle resized by the given delta. The Circles center is use as the anchor.
|
||||
//
|
||||
// c := pixel.C(pixel.ZV, 10)
|
||||
// c.Resized(-5) // returns pixel.Circle{pixel.Vec{0, 0}, 5}
|
||||
// c.Resized(25) // returns pixel.Circle{pixel.Vec{0, 0}, 35}
|
||||
func (c Circle) Resized(radiusDelta float64) Circle {
|
||||
return Circle{
|
||||
Center: c.Center,
|
||||
Radius: c.Radius + radiusDelta,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains checks whether a vector `u` is contained within this Circle (including it's perimeter).
|
||||
func (c Circle) Contains(u Vec) bool {
|
||||
toCenter := c.Center.To(u)
|
||||
return c.Radius >= toCenter.Len()
|
||||
}
|
||||
|
||||
// Formula returns the values of h and k, for the equation of the circle: (x-h)^2 + (y-k)^2 = r^2
|
||||
// where r is the radius of the circle.
|
||||
func (c Circle) Formula() (h, k float64) {
|
||||
return c.Center.X, c.Center.Y
|
||||
}
|
||||
|
||||
// maxCircle will return the larger circle based on the radius.
|
||||
func maxCircle(c, d Circle) Circle {
|
||||
if c.Radius < d.Radius {
|
||||
return d
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// minCircle will return the smaller circle based on the radius.
|
||||
func minCircle(c, d Circle) Circle {
|
||||
if c.Radius < d.Radius {
|
||||
return c
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Union returns the minimal Circle which covers both `c` and `d`.
|
||||
func (c Circle) Union(d Circle) Circle {
|
||||
biggerC := maxCircle(c.Norm(), d.Norm())
|
||||
smallerC := minCircle(c.Norm(), d.Norm())
|
||||
|
||||
// Get distance between centers
|
||||
dist := c.Center.To(d.Center).Len()
|
||||
|
||||
// If the bigger Circle encompasses the smaller one, we have the result
|
||||
if dist+smallerC.Radius <= biggerC.Radius {
|
||||
return biggerC
|
||||
}
|
||||
|
||||
// Calculate radius for encompassing Circle
|
||||
r := (dist + biggerC.Radius + smallerC.Radius) / 2
|
||||
|
||||
// Calculate center for encompassing Circle
|
||||
theta := .5 + (biggerC.Radius-smallerC.Radius)/(2*dist)
|
||||
center := Lerp(smallerC.Center, biggerC.Center, theta)
|
||||
|
||||
return Circle{
|
||||
Center: center,
|
||||
Radius: r,
|
||||
}
|
||||
}
|
||||
|
||||
// Intersect returns the maximal Circle which is covered by both `c` and `d`.
|
||||
//
|
||||
// If `c` and `d` don't overlap, this function returns a zero-sized circle at the centerpoint between the two Circle's
|
||||
// centers.
|
||||
func (c Circle) Intersect(d Circle) Circle {
|
||||
// Check if one of the circles encompasses the other; if so, return that one
|
||||
biggerC := maxCircle(c.Norm(), d.Norm())
|
||||
smallerC := minCircle(c.Norm(), d.Norm())
|
||||
|
||||
if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius {
|
||||
return biggerC
|
||||
}
|
||||
|
||||
// Calculate the midpoint between the two radii
|
||||
// Distance between centers
|
||||
dist := c.Center.To(d.Center).Len()
|
||||
// Difference between radii
|
||||
diff := dist - (c.Radius + d.Radius)
|
||||
// Distance from c.Center to the weighted midpoint
|
||||
distToMidpoint := c.Radius + 0.5*diff
|
||||
// Weighted midpoint
|
||||
center := Lerp(c.Center, d.Center, distToMidpoint/dist)
|
||||
|
||||
// No need to calculate radius if the circles do not overlap
|
||||
if c.Center.To(d.Center).Len() >= c.Radius+d.Radius {
|
||||
return C(center, 0)
|
||||
}
|
||||
|
||||
radius := c.Center.To(d.Center).Len() - (c.Radius + d.Radius)
|
||||
|
||||
return Circle{
|
||||
Center: center,
|
||||
Radius: math.Abs(radius),
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectLine will return the shortest Vec such that if the Circle is moved by the Vec returned, the Line and Rect no
|
||||
// longer intersect.
|
||||
func (c Circle) IntersectLine(l Line) Vec {
|
||||
return l.IntersectCircle(c).Scaled(-1)
|
||||
}
|
||||
|
||||
// IntersectRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle
|
||||
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
|
||||
// the perimeters touch.
|
||||
//
|
||||
// This function will return a non-zero vector if:
|
||||
// - The Rect contains the Circle, partially or fully
|
||||
// - The Circle contains the Rect, partially of fully
|
||||
func (c Circle) IntersectRect(r Rect) Vec {
|
||||
// Checks if the c.Center is not in the diagonal quadrants of the rectangle
|
||||
if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) {
|
||||
// 'grow' the Rect by c.Radius in each orthagonal
|
||||
grown := Rect{Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius))}
|
||||
if !grown.Contains(c.Center) {
|
||||
// c.Center not close enough to overlap, return zero-vector
|
||||
return ZV
|
||||
}
|
||||
|
||||
// Get minimum distance to travel out of Rect
|
||||
rToC := r.Center().To(c.Center)
|
||||
h := c.Radius - math.Abs(rToC.X) + (r.W() / 2)
|
||||
v := c.Radius - math.Abs(rToC.Y) + (r.H() / 2)
|
||||
|
||||
if rToC.X < 0 {
|
||||
h = -h
|
||||
}
|
||||
if rToC.Y < 0 {
|
||||
v = -v
|
||||
}
|
||||
|
||||
// No intersect
|
||||
if h == 0 && v == 0 {
|
||||
return ZV
|
||||
}
|
||||
|
||||
if math.Abs(h) > math.Abs(v) {
|
||||
// Vertical distance shorter
|
||||
return V(0, v)
|
||||
}
|
||||
return V(h, 0)
|
||||
} else {
|
||||
// The center is in the diagonal quadrants
|
||||
|
||||
// Helper points to make code below easy to read.
|
||||
rectTopLeft := V(r.Min.X, r.Max.Y)
|
||||
rectBottomRight := V(r.Max.X, r.Min.Y)
|
||||
|
||||
// Check for overlap.
|
||||
if !(c.Contains(r.Min) || c.Contains(r.Max) || c.Contains(rectTopLeft) || c.Contains(rectBottomRight)) {
|
||||
// No overlap.
|
||||
return ZV
|
||||
}
|
||||
|
||||
var centerToCorner Vec
|
||||
if c.Center.To(r.Min).Len() <= c.Radius {
|
||||
// Closest to bottom-left
|
||||
centerToCorner = c.Center.To(r.Min)
|
||||
}
|
||||
if c.Center.To(r.Max).Len() <= c.Radius {
|
||||
// Closest to top-right
|
||||
centerToCorner = c.Center.To(r.Max)
|
||||
}
|
||||
if c.Center.To(rectTopLeft).Len() <= c.Radius {
|
||||
// Closest to top-left
|
||||
centerToCorner = c.Center.To(rectTopLeft)
|
||||
}
|
||||
if c.Center.To(rectBottomRight).Len() <= c.Radius {
|
||||
// Closest to bottom-right
|
||||
centerToCorner = c.Center.To(rectBottomRight)
|
||||
}
|
||||
|
||||
cornerToCircumferenceLen := c.Radius - centerToCorner.Len()
|
||||
|
||||
return centerToCorner.Unit().Scaled(cornerToCircumferenceLen)
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectionPoints returns all the points where the Circle intersects with the line provided. This can be zero, one or
|
||||
// two points, depending on the location of the shapes. The points of intersection will be returned in order of
|
||||
// closest-to-l.A to closest-to-l.B.
|
||||
func (c Circle) IntersectionPoints(l Line) []Vec {
|
||||
cContainsA := c.Contains(l.A)
|
||||
cContainsB := c.Contains(l.B)
|
||||
|
||||
// Special case for both endpoint being contained within the circle
|
||||
if cContainsA && cContainsB {
|
||||
return []Vec{}
|
||||
}
|
||||
|
||||
// Get closest point on the line to this circles' center
|
||||
closestToCenter := l.Closest(c.Center)
|
||||
|
||||
// If the distance to the closest point is greater than the radius, there are no points of intersection
|
||||
if closestToCenter.To(c.Center).Len() > c.Radius {
|
||||
return []Vec{}
|
||||
}
|
||||
|
||||
// If the distance to the closest point is equal to the radius, the line is tangent and the closest point is the
|
||||
// point at which it touches the circle.
|
||||
if closestToCenter.To(c.Center).Len() == c.Radius {
|
||||
return []Vec{closestToCenter}
|
||||
}
|
||||
|
||||
// Special case for endpoint being on the circles' center
|
||||
if c.Center == l.A || c.Center == l.B {
|
||||
otherEnd := l.B
|
||||
if c.Center == l.B {
|
||||
otherEnd = l.A
|
||||
}
|
||||
intersect := c.Center.Add(c.Center.To(otherEnd).Unit().Scaled(c.Radius))
|
||||
return []Vec{intersect}
|
||||
}
|
||||
|
||||
// This means the distance to the closest point is less than the radius, so there is at least one intersection,
|
||||
// possibly two.
|
||||
|
||||
// If one of the end points exists within the circle, there is only one intersection
|
||||
if cContainsA || cContainsB {
|
||||
containedPoint := l.A
|
||||
otherEnd := l.B
|
||||
if cContainsB {
|
||||
containedPoint = l.B
|
||||
otherEnd = l.A
|
||||
}
|
||||
|
||||
// Use trigonometry to get the length of the line between the contained point and the intersection point.
|
||||
// The following is used to describe the triangle formed:
|
||||
// - a is the side between contained point and circle center
|
||||
// - b is the side between the center and the intersection point (radius)
|
||||
// - c is the side between the contained point and the intersection point
|
||||
// The captials of these letters are used as the angles opposite the respective sides.
|
||||
// a and b are known
|
||||
a := containedPoint.To(c.Center).Len()
|
||||
b := c.Radius
|
||||
// B can be calculated by subtracting the angle of b (to the x-axis) from the angle of c (to the x-axis)
|
||||
B := containedPoint.To(c.Center).Angle() - containedPoint.To(otherEnd).Angle()
|
||||
// Using the Sin rule we can get A
|
||||
A := math.Asin((a * math.Sin(B)) / b)
|
||||
// Using the rule that there are 180 degrees (or Pi radians) in a triangle, we can now get C
|
||||
C := math.Pi - A + B
|
||||
// If C is zero, the line segment is in-line with the center-intersect line.
|
||||
var c float64
|
||||
if C == 0 {
|
||||
c = b - a
|
||||
} else {
|
||||
// Using the Sine rule again, we can now get c
|
||||
c = (a * math.Sin(C)) / math.Sin(A)
|
||||
}
|
||||
// Travelling from the contained point to the other end by length of a will provide the intersection point.
|
||||
return []Vec{
|
||||
containedPoint.Add(containedPoint.To(otherEnd).Unit().Scaled(c)),
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise the endpoints exist outside of the circle, and the line segment intersects in two locations.
|
||||
// The vector formed by going from the closest point to the center of the circle will be perpendicular to the line;
|
||||
// this forms a right-angled triangle with the intersection points, with the radius as the hypotenuse.
|
||||
// Calculate the other triangles' sides' length.
|
||||
a := math.Sqrt(math.Pow(c.Radius, 2) - math.Pow(closestToCenter.To(c.Center).Len(), 2))
|
||||
|
||||
// Travelling in both directions from the closest point by length of a will provide the two intersection points.
|
||||
first := closestToCenter.Add(closestToCenter.To(l.A).Unit().Scaled(a))
|
||||
second := closestToCenter.Add(closestToCenter.To(l.B).Unit().Scaled(a))
|
||||
|
||||
if first.To(l.A).Len() < second.To(l.A).Len() {
|
||||
return []Vec{first, second}
|
||||
}
|
||||
return []Vec{second, first}
|
||||
}
|
|
@ -0,0 +1,466 @@
|
|||
package pixel_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
func TestC(t *testing.T) {
|
||||
type args struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "C(): positive radius",
|
||||
args: args{radius: 10, center: pixel.ZV},
|
||||
want: pixel.Circle{Radius: 10, Center: pixel.ZV},
|
||||
},
|
||||
{
|
||||
name: "C(): zero radius",
|
||||
args: args{radius: 0, center: pixel.ZV},
|
||||
want: pixel.Circle{Radius: 0, Center: pixel.ZV},
|
||||
},
|
||||
{
|
||||
name: "C(): negative radius",
|
||||
args: args{radius: -5, center: pixel.ZV},
|
||||
want: pixel.Circle{Radius: -5, Center: pixel.ZV},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := pixel.C(tt.args.center, tt.args.radius); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("C() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_String(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Circle.String(): positive radius",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), 10.00)",
|
||||
},
|
||||
{
|
||||
name: "Circle.String(): zero radius",
|
||||
fields: fields{radius: 0, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), 0.00)",
|
||||
},
|
||||
{
|
||||
name: "Circle.String(): negative radius",
|
||||
fields: fields{radius: -5, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), -5.00)",
|
||||
},
|
||||
{
|
||||
name: "Circle.String(): irrational radius",
|
||||
fields: fields{radius: math.Pi, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), 3.14)",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.String(); got != tt.want {
|
||||
t.Errorf("Circle.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Norm(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Norm(): positive radius",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
want: pixel.C(pixel.ZV, 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Norm(): zero radius",
|
||||
fields: fields{radius: 0, center: pixel.ZV},
|
||||
want: pixel.C(pixel.ZV, 0),
|
||||
},
|
||||
{
|
||||
name: "Circle.Norm(): negative radius",
|
||||
fields: fields{radius: -5, center: pixel.ZV},
|
||||
want: pixel.C(pixel.ZV, 5),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Norm(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Norm() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Area(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want float64
|
||||
}{
|
||||
{
|
||||
name: "Circle.Area(): positive radius",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
want: 100 * math.Pi,
|
||||
},
|
||||
{
|
||||
name: "Circle.Area(): zero radius",
|
||||
fields: fields{radius: 0, center: pixel.ZV},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "Circle.Area(): negative radius",
|
||||
fields: fields{radius: -5, center: pixel.ZV},
|
||||
want: 25 * math.Pi,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Area(); got != tt.want {
|
||||
t.Errorf("Circle.Area() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Moved(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
delta pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Moved(): positive movement",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{delta: pixel.V(10, 20)},
|
||||
want: pixel.C(pixel.V(10, 20), 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Moved(): zero movement",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{delta: pixel.ZV},
|
||||
want: pixel.C(pixel.V(0, 0), 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Moved(): negative movement",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{delta: pixel.V(-5, -10)},
|
||||
want: pixel.C(pixel.V(-5, -10), 10),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Moved() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Resized(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
radiusDelta float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Resized(): positive delta",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{radiusDelta: 5},
|
||||
want: pixel.C(pixel.V(0, 0), 15),
|
||||
},
|
||||
{
|
||||
name: "Circle.Resized(): zero delta",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{radiusDelta: 0},
|
||||
want: pixel.C(pixel.V(0, 0), 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Resized(): negative delta",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{radiusDelta: -5},
|
||||
want: pixel.C(pixel.V(0, 0), 5),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Resized(tt.args.radiusDelta); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Resized() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Contains(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
u pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Circle.Contains(): point on cicles' center",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{u: pixel.ZV},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Circle.Contains(): point offcenter",
|
||||
fields: fields{radius: 10, center: pixel.V(5, 0)},
|
||||
args: args{u: pixel.ZV},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Circle.Contains(): point on circumference",
|
||||
fields: fields{radius: 10, center: pixel.V(10, 0)},
|
||||
args: args{u: pixel.ZV},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Circle.Contains(): point outside circle",
|
||||
fields: fields{radius: 10, center: pixel.V(15, 0)},
|
||||
args: args{u: pixel.ZV},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Contains(tt.args.u); got != tt.want {
|
||||
t.Errorf("Circle.Contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Union(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
d pixel.Circle
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Union(): overlapping circles",
|
||||
fields: fields{radius: 5, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.ZV, 5)},
|
||||
want: pixel.C(pixel.ZV, 5),
|
||||
},
|
||||
{
|
||||
name: "Circle.Union(): separate circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(0, 2), 1)},
|
||||
want: pixel.C(pixel.V(0, 1), 2),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Union(tt.args.d); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Union() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Intersect(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
d pixel.Circle
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Intersect(): intersecting circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(1, 0), 1)},
|
||||
want: pixel.C(pixel.V(0.5, 0), 1),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): non-intersecting circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(3, 3), 1)},
|
||||
want: pixel.C(pixel.V(1.5, 1.5), 0),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): first circle encompassing second",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(3, 3), 1)},
|
||||
want: pixel.C(pixel.ZV, 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): second circle encompassing first",
|
||||
fields: fields{radius: 1, center: pixel.V(-1, -4)},
|
||||
args: args{d: pixel.C(pixel.ZV, 10)},
|
||||
want: pixel.C(pixel.ZV, 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): matching circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.ZV, 1)},
|
||||
want: pixel.C(pixel.ZV, 1),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(
|
||||
tt.fields.center,
|
||||
tt.fields.radius,
|
||||
)
|
||||
if got := c.Intersect(tt.args.d); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Intersect() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_IntersectPoints(t *testing.T) {
|
||||
type fields struct {
|
||||
Center pixel.Vec
|
||||
Radius float64
|
||||
}
|
||||
type args struct {
|
||||
l pixel.Line
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want []pixel.Vec
|
||||
}{
|
||||
{
|
||||
name: "Line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(2, 2), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{pixel.V(1.292, 1.292), pixel.V(2.707, 2.707)},
|
||||
},
|
||||
{
|
||||
name: "Line intersects circle at one point",
|
||||
fields: fields{Center: pixel.V(-0.5, -0.5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{pixel.V(0.207, 0.207)},
|
||||
},
|
||||
{
|
||||
name: "Line endpoint is circle center",
|
||||
fields: fields{Center: pixel.V(0, 0), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{pixel.V(0.707, 0.707)},
|
||||
},
|
||||
{
|
||||
name: "Both line endpoints within circle",
|
||||
fields: fields{Center: pixel.V(0, 0), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0.2, 0.2), pixel.V(0.5, 0.5))},
|
||||
want: []pixel.Vec{},
|
||||
},
|
||||
{
|
||||
name: "Line does not intersect circle",
|
||||
fields: fields{Center: pixel.V(10, 0), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: []pixel.Vec{},
|
||||
},
|
||||
{
|
||||
name: "Horizontal line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(0, 5), pixel.V(10, 5))},
|
||||
want: []pixel.Vec{pixel.V(4, 5), pixel.V(6, 5)},
|
||||
},
|
||||
{
|
||||
name: "Vertical line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(5, 0), pixel.V(5, 10))},
|
||||
want: []pixel.Vec{pixel.V(5, 4), pixel.V(5, 6)},
|
||||
},
|
||||
{
|
||||
name: "Left and down line intersects circle at two points",
|
||||
fields: fields{Center: pixel.V(5, 5), Radius: 1},
|
||||
args: args{pixel.L(pixel.V(10, 10), pixel.V(0, 0))},
|
||||
want: []pixel.Vec{pixel.V(5.707, 5.707), pixel.V(4.292, 4.292)},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.Circle{
|
||||
Center: tt.fields.Center,
|
||||
Radius: tt.fields.Radius,
|
||||
}
|
||||
got := c.IntersectionPoints(tt.args.l)
|
||||
for i, v := range got {
|
||||
if !closeEnough(v.X, tt.want[i].X, 2) || !closeEnough(v.Y, tt.want[i].Y, 2) {
|
||||
t.Errorf("Circle.IntersectPoints() = %v, want %v", v, tt.want[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
51
data.go
|
@ -8,6 +8,16 @@ import (
|
|||
"math"
|
||||
)
|
||||
|
||||
// zeroValueTriangleData is the default value of a TriangleData element
|
||||
var zeroValueTriangleData = struct {
|
||||
Position Vec
|
||||
Color RGBA
|
||||
Picture Vec
|
||||
Intensity float64
|
||||
ClipRect Rect
|
||||
IsClipped bool
|
||||
}{Color: RGBA{1, 1, 1, 1}}
|
||||
|
||||
// TrianglesData specifies a list of Triangles vertices with three common properties:
|
||||
// TrianglesPosition, TrianglesColor and TrianglesPicture.
|
||||
type TrianglesData []struct {
|
||||
|
@ -15,6 +25,8 @@ type TrianglesData []struct {
|
|||
Color RGBA
|
||||
Picture Vec
|
||||
Intensity float64
|
||||
ClipRect Rect
|
||||
IsClipped bool
|
||||
}
|
||||
|
||||
// MakeTrianglesData creates TrianglesData of length len initialized with default property values.
|
||||
|
@ -22,9 +34,11 @@ type TrianglesData []struct {
|
|||
// Prefer this function to make(TrianglesData, len), because make zeros them, while this function
|
||||
// does the correct intialization.
|
||||
func MakeTrianglesData(len int) *TrianglesData {
|
||||
td := &TrianglesData{}
|
||||
td.SetLen(len)
|
||||
return td
|
||||
td := make(TrianglesData, len)
|
||||
for i := 0; i < len; i++ {
|
||||
td[i] = zeroValueTriangleData
|
||||
}
|
||||
return &td
|
||||
}
|
||||
|
||||
// Len returns the number of vertices in TrianglesData.
|
||||
|
@ -40,12 +54,7 @@ func (td *TrianglesData) SetLen(len int) {
|
|||
if len > td.Len() {
|
||||
needAppend := len - td.Len()
|
||||
for i := 0; i < needAppend; i++ {
|
||||
*td = append(*td, struct {
|
||||
Position Vec
|
||||
Color RGBA
|
||||
Picture Vec
|
||||
Intensity float64
|
||||
}{Color: RGBA{1, 1, 1, 1}})
|
||||
*td = append(*td, zeroValueTriangleData)
|
||||
}
|
||||
}
|
||||
if len < td.Len() {
|
||||
|
@ -82,6 +91,11 @@ func (td *TrianglesData) updateData(t Triangles) {
|
|||
(*td)[i].Picture, (*td)[i].Intensity = t.Picture(i)
|
||||
}
|
||||
}
|
||||
if t, ok := t.(TrianglesClipped); ok {
|
||||
for i := range *td {
|
||||
(*td)[i].ClipRect, (*td)[i].IsClipped = t.ClipRect(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update copies vertex properties from the supplied Triangles into this TrianglesData.
|
||||
|
@ -96,10 +110,9 @@ func (td *TrianglesData) Update(t Triangles) {
|
|||
|
||||
// Copy returns an exact independent copy of this TrianglesData.
|
||||
func (td *TrianglesData) Copy() Triangles {
|
||||
copyTd := TrianglesData{}
|
||||
copyTd.SetLen(td.Len())
|
||||
copyTd := MakeTrianglesData(td.Len())
|
||||
copyTd.Update(td)
|
||||
return ©Td
|
||||
return copyTd
|
||||
}
|
||||
|
||||
// Position returns the position property of i-th vertex.
|
||||
|
@ -117,6 +130,11 @@ func (td *TrianglesData) Picture(i int) (pic Vec, intensity float64) {
|
|||
return (*td)[i].Picture, (*td)[i].Intensity
|
||||
}
|
||||
|
||||
// ClipRect returns the clipping rectangle property of the i-th vertex.
|
||||
func (td *TrianglesData) ClipRect(i int) (rect Rect, has bool) {
|
||||
return (*td)[i].ClipRect, (*td)[i].IsClipped
|
||||
}
|
||||
|
||||
// PictureData specifies an in-memory rectangular area of pixels and implements Picture and
|
||||
// PictureColor.
|
||||
//
|
||||
|
@ -165,13 +183,8 @@ 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 {
|
||||
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)
|
||||
}
|
||||
rgba := image.NewRGBA(img.Bounds())
|
||||
draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)
|
||||
|
||||
verticalFlip(rgba)
|
||||
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
package pixel_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
func BenchmarkMakeTrianglesData(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
len int
|
||||
}{
|
||||
{
|
||||
name: "Small slice",
|
||||
len: 10,
|
||||
},
|
||||
{
|
||||
name: "Large slice",
|
||||
len: 10000,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = pixel.MakeTrianglesData(tt.len)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_Len(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
}{
|
||||
{
|
||||
name: "Small slice",
|
||||
tData: pixel.MakeTrianglesData(10),
|
||||
},
|
||||
{
|
||||
name: "Large slice",
|
||||
tData: pixel.MakeTrianglesData(10000),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tt.tData.Len()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_SetLen(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
nextLenFunc func(int, int) (int, int)
|
||||
}{
|
||||
{
|
||||
name: "Stay same size",
|
||||
tData: pixel.MakeTrianglesData(50),
|
||||
nextLenFunc: func(i, j int) (int, int) { return 50, 0 },
|
||||
},
|
||||
{
|
||||
name: "Change size",
|
||||
tData: pixel.MakeTrianglesData(50),
|
||||
nextLenFunc: func(i, j int) (int, int) {
|
||||
// 0 is shrink
|
||||
if j == 0 {
|
||||
next := i - 1
|
||||
if next < 1 {
|
||||
return 2, 1
|
||||
}
|
||||
return next, 0
|
||||
}
|
||||
|
||||
// other than 0 is grow
|
||||
next := i + 1
|
||||
if next == 100 {
|
||||
return next, 0
|
||||
}
|
||||
return next, 1
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
var newLen int
|
||||
var c int
|
||||
for i := 0; i < b.N; i++ {
|
||||
newLen, c = tt.nextLenFunc(newLen, c)
|
||||
tt.tData.SetLen(newLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_Slice(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
}{
|
||||
{
|
||||
name: "Basic slice",
|
||||
tData: pixel.MakeTrianglesData(100),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tt.tData.Slice(25, 50)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_Update(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
t pixel.Triangles
|
||||
}{
|
||||
{
|
||||
name: "Small Triangles",
|
||||
tData: pixel.MakeTrianglesData(20),
|
||||
t: pixel.MakeTrianglesData(20),
|
||||
},
|
||||
{
|
||||
name: "Large Triangles",
|
||||
tData: pixel.MakeTrianglesData(10000),
|
||||
t: pixel.MakeTrianglesData(10000),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
tt.tData.Update(tt.t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_Copy(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
}{
|
||||
{
|
||||
name: "Small copy",
|
||||
tData: pixel.MakeTrianglesData(20),
|
||||
},
|
||||
{
|
||||
name: "Large copy",
|
||||
tData: pixel.MakeTrianglesData(10000),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tt.tData.Copy()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_Position(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
position int
|
||||
}{
|
||||
{
|
||||
name: "Getting beginning position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: "Getting middle position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 500,
|
||||
},
|
||||
{
|
||||
name: "Getting end position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 999,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tt.tData.Position(tt.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_Color(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
position int
|
||||
}{
|
||||
{
|
||||
name: "Getting beginning position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: "Getting middle position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 500,
|
||||
},
|
||||
{
|
||||
name: "Getting end position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 999,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tt.tData.Color(tt.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_Picture(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
position int
|
||||
}{
|
||||
{
|
||||
name: "Getting beginning position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: "Getting middle position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 500,
|
||||
},
|
||||
{
|
||||
name: "Getting end position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 999,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = tt.tData.Picture(tt.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrianglesData_ClipRect(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tData *pixel.TrianglesData
|
||||
position int
|
||||
}{
|
||||
{
|
||||
name: "Getting beginning position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: "Getting middle position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 500,
|
||||
},
|
||||
{
|
||||
name: "Getting end position",
|
||||
tData: pixel.MakeTrianglesData(1000),
|
||||
position: 999,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = tt.tData.ClipRect(tt.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
14
drawer.go
|
@ -23,9 +23,11 @@ package pixel
|
|||
type Drawer struct {
|
||||
Triangles Triangles
|
||||
Picture Picture
|
||||
Cached bool
|
||||
|
||||
targets map[Target]*drawerTarget
|
||||
inited bool
|
||||
targets map[Target]*drawerTarget
|
||||
allTargets []*drawerTarget
|
||||
inited bool
|
||||
}
|
||||
|
||||
type drawerTarget struct {
|
||||
|
@ -46,7 +48,7 @@ func (d *Drawer) lazyInit() {
|
|||
func (d *Drawer) Dirty() {
|
||||
d.lazyInit()
|
||||
|
||||
for _, t := range d.targets {
|
||||
for _, t := range d.allTargets {
|
||||
t.clean = false
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +70,7 @@ func (d *Drawer) Draw(t Target) {
|
|||
pics: make(map[Picture]TargetPicture),
|
||||
}
|
||||
d.targets[t] = dt
|
||||
d.allTargets = append(d.allTargets, dt)
|
||||
}
|
||||
|
||||
if dt.tris == nil {
|
||||
|
@ -89,7 +92,10 @@ func (d *Drawer) Draw(t Target) {
|
|||
pic := dt.pics[d.Picture]
|
||||
if pic == nil {
|
||||
pic = t.MakePicture(d.Picture)
|
||||
dt.pics[d.Picture] = pic
|
||||
|
||||
if d.Cached {
|
||||
dt.pics[d.Picture] = pic
|
||||
}
|
||||
}
|
||||
|
||||
pic.Draw(dt.tris)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# 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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Links
|
||||
|
||||
- https://github.com/peterhellberg/pixel-experiments/tree/master/bouncing
|
||||
- https://gist.github.com/peterhellberg/674f32a15a7d2d249e634ce781f333e8
|
|
@ -1,303 +0,0 @@
|
|||
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},
|
||||
}
|
Before Width: | Height: | Size: 6.9 KiB |
|
@ -1,20 +0,0 @@
|
|||
# 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)
|
||||
|
||||

|
Before Width: | Height: | Size: 86 KiB |
|
@ -1,74 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# 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/).
|
||||
|
||||

|
Before Width: | Height: | Size: 26 KiB |
|
@ -1,102 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 126 KiB |
|
@ -1,21 +0,0 @@
|
|||
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.
|
|
@ -1,19 +0,0 @@
|
|||
# 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
|
||||
|
||||

|
|
@ -1,317 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 14 KiB |
|
@ -1,86 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
# 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.
|
||||
|
||||

|
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 25 KiB |
|
@ -1,74 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 91 KiB |
|
@ -1,64 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# 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).
|
||||
|
||||

|
|
@ -1,104 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 759 KiB |
Before Width: | Height: | Size: 368 KiB |
|
@ -1,22 +0,0 @@
|
|||
# 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 Lode’s 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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Links
|
||||
|
||||
- https://github.com/peterhellberg/pixel-experiments/tree/master/raycaster
|
||||
- https://gist.github.com/peterhellberg/835eccabf95800555120cc8f0c9e16c2
|
Before Width: | Height: | Size: 47 KiB |
|
@ -1,7 +0,0 @@
|
|||
# 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.
|
Before Width: | Height: | Size: 56 KiB |
|
@ -1,83 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
# 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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Links
|
||||
|
||||
- https://github.com/peterhellberg/pixel-experiments/tree/master/starfield
|
||||
- https://gist.github.com/peterhellberg/4018e228cced61a0bb26991e49299c96
|
Before Width: | Height: | Size: 6.0 KiB |
|
@ -1,165 +0,0 @@
|
|||
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},
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 68 KiB |
|
@ -1,56 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 68 KiB |
|
@ -1,70 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 2.5 KiB |
|
@ -1,110 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 2.5 KiB |
|
@ -1,53 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# 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).
|
||||
|
||||

|
|
@ -1,195 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 838 KiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.1 MiB |
|
@ -1,14 +0,0 @@
|
|||
# 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).
|
||||
|
||||

|
|
@ -1,394 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 8.3 KiB |
|
@ -1,9 +0,0 @@
|
|||
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
|
|
Before Width: | Height: | Size: 530 B |
|
@ -1,8 +0,0 @@
|
|||
# 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/).
|
||||
|
||||

|
|
@ -1,25 +0,0 @@
|
|||
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
|
|
Before Width: | Height: | Size: 3.0 MiB |
|
@ -1,230 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 381 KiB |
|
@ -1,13 +0,0 @@
|
|||
# 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.
|
||||
|
||||

|
|
@ -1,317 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 156 KiB |
|
@ -1,8 +0,0 @@
|
|||
# Xor
|
||||
|
||||
This example demonstrates an unusual Porter-Duff composition method: Xor. (And the capability of
|
||||
drawing circles.)
|
||||
|
||||
Just thought it was cool.
|
||||
|
||||

|
|
@ -1,76 +0,0 @@
|
|||
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)
|
||||
}
|
Before Width: | Height: | Size: 10 KiB |
405
geometry.go
|
@ -1,405 +0,0 @@
|
|||
package pixel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Clamp returns x clamped to the interval [min, max].
|
||||
//
|
||||
// If x is less than min, min is returned. If x is more than max, max is returned. Otherwise, x is
|
||||
// returned.
|
||||
func Clamp(x, min, max float64) float64 {
|
||||
if x < min {
|
||||
return min
|
||||
}
|
||||
if x > max {
|
||||
return max
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Vec is a 2D vector type with X and Y coordinates.
|
||||
//
|
||||
// Create vectors with the V constructor:
|
||||
//
|
||||
// u := pixel.V(1, 2)
|
||||
// v := pixel.V(8, -3)
|
||||
//
|
||||
// Use various methods to manipulate them:
|
||||
//
|
||||
// w := u.Add(v)
|
||||
// fmt.Println(w) // Vec(9, -1)
|
||||
// fmt.Println(u.Sub(v)) // Vec(-7, 5)
|
||||
// u = pixel.V(2, 3)
|
||||
// v = pixel.V(8, 1)
|
||||
// if u.X < 0 {
|
||||
// fmt.Println("this won't happen")
|
||||
// }
|
||||
// x := u.Unit().Dot(v.Unit())
|
||||
type Vec struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
// ZV is a zero vector.
|
||||
var ZV = Vec{0, 0}
|
||||
|
||||
// V returns a new 2D vector with the given coordinates.
|
||||
func V(x, y float64) Vec {
|
||||
return Vec{x, y}
|
||||
}
|
||||
|
||||
// Unit returns a vector of length 1 facing the given angle.
|
||||
func Unit(angle float64) Vec {
|
||||
return Vec{1, 0}.Rotated(angle)
|
||||
}
|
||||
|
||||
// String returns the string representation of the vector u.
|
||||
//
|
||||
// u := pixel.V(4.5, -1.3)
|
||||
// u.String() // returns "Vec(4.5, -1.3)"
|
||||
// fmt.Println(u) // Vec(4.5, -1.3)
|
||||
func (u Vec) String() string {
|
||||
return fmt.Sprintf("Vec(%v, %v)", u.X, u.Y)
|
||||
}
|
||||
|
||||
// XY returns the components of the vector in two return values.
|
||||
func (u Vec) XY() (x, y float64) {
|
||||
return u.X, u.Y
|
||||
}
|
||||
|
||||
// Add returns the sum of vectors u and v.
|
||||
func (u Vec) Add(v Vec) Vec {
|
||||
return Vec{
|
||||
u.X + v.X,
|
||||
u.Y + v.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Sub returns the difference betweeen vectors u and v.
|
||||
func (u Vec) Sub(v Vec) Vec {
|
||||
return Vec{
|
||||
u.X - v.X,
|
||||
u.Y - v.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// To returns the vector from u to v. Equivalent to v.Sub(u).
|
||||
func (u Vec) To(v Vec) Vec {
|
||||
return Vec{
|
||||
v.X - u.X,
|
||||
v.Y - u.Y,
|
||||
}
|
||||
}
|
||||
|
||||
// Scaled returns the vector u multiplied by c.
|
||||
func (u Vec) Scaled(c float64) Vec {
|
||||
return Vec{u.X * c, u.Y * c}
|
||||
}
|
||||
|
||||
// ScaledXY returns the vector u multiplied by the vector v component-wise.
|
||||
func (u Vec) ScaledXY(v Vec) Vec {
|
||||
return Vec{u.X * v.X, u.Y * v.Y}
|
||||
}
|
||||
|
||||
// Len returns the length of the vector u.
|
||||
func (u Vec) Len() float64 {
|
||||
return math.Hypot(u.X, u.Y)
|
||||
}
|
||||
|
||||
// Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi].
|
||||
func (u Vec) Angle() float64 {
|
||||
return math.Atan2(u.Y, u.X)
|
||||
}
|
||||
|
||||
// Unit returns a vector of length 1 facing the direction of u (has the same angle).
|
||||
func (u Vec) Unit() Vec {
|
||||
if u.X == 0 && u.Y == 0 {
|
||||
return Vec{1, 0}
|
||||
}
|
||||
return u.Scaled(1 / u.Len())
|
||||
}
|
||||
|
||||
// Rotated returns the vector u rotated by the given angle in radians.
|
||||
func (u Vec) Rotated(angle float64) Vec {
|
||||
sin, cos := math.Sincos(angle)
|
||||
return Vec{
|
||||
u.X*cos - u.Y*sin,
|
||||
u.X*sin + u.Y*cos,
|
||||
}
|
||||
}
|
||||
|
||||
// Normal returns a vector normal to u. Equivalent to u.Rotated(math.Pi / 2), but faster.
|
||||
func (u Vec) Normal() Vec {
|
||||
return Vec{-u.Y, u.X}
|
||||
}
|
||||
|
||||
// Dot returns the dot product of vectors u and v.
|
||||
func (u Vec) Dot(v Vec) float64 {
|
||||
return u.X*v.X + u.Y*v.Y
|
||||
}
|
||||
|
||||
// Cross return the cross product of vectors u and v.
|
||||
func (u Vec) Cross(v Vec) float64 {
|
||||
return u.X*v.Y - v.X*u.Y
|
||||
}
|
||||
|
||||
// Project returns a projection (or component) of vector u in the direction of vector v.
|
||||
//
|
||||
// Behaviour is undefined if v is a zero vector.
|
||||
func (u Vec) Project(v Vec) Vec {
|
||||
len := u.Dot(v) / v.Len()
|
||||
return v.Unit().Scaled(len)
|
||||
}
|
||||
|
||||
// Map applies the function f to both x and y components of the vector u and returns the modified
|
||||
// vector.
|
||||
//
|
||||
// u := pixel.V(10.5, -1.5)
|
||||
// v := u.Map(math.Floor) // v is Vec(10, -2), both components of u floored
|
||||
func (u Vec) Map(f func(float64) float64) Vec {
|
||||
return Vec{
|
||||
f(u.X),
|
||||
f(u.Y),
|
||||
}
|
||||
}
|
||||
|
||||
// Lerp returns a linear interpolation between vectors a and b.
|
||||
//
|
||||
// This function basically returns a point along the line between a and b and t chooses which one.
|
||||
// If t is 0, then a will be returned, if t is 1, b will be returned. Anything between 0 and 1 will
|
||||
// return the appropriate point between a and b and so on.
|
||||
func Lerp(a, b Vec, t float64) Vec {
|
||||
return a.Scaled(1 - t).Add(b.Scaled(t))
|
||||
}
|
||||
|
||||
// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
|
||||
// points, Min and Max.
|
||||
//
|
||||
// The invariant should hold, that Max's components are greater or equal than Min's components
|
||||
// respectively.
|
||||
type Rect struct {
|
||||
Min, Max Vec
|
||||
}
|
||||
|
||||
// R returns a new Rect with given the Min and Max coordinates.
|
||||
//
|
||||
// Note that the returned rectangle is not automatically normalized.
|
||||
func R(minX, minY, maxX, maxY float64) Rect {
|
||||
return Rect{
|
||||
Min: Vec{minX, minY},
|
||||
Max: Vec{maxX, maxY},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of the Rect.
|
||||
//
|
||||
// r := pixel.R(100, 50, 200, 300)
|
||||
// r.String() // returns "Rect(100, 50, 200, 300)"
|
||||
// fmt.Println(r) // Rect(100, 50, 200, 300)
|
||||
func (r Rect) String() string {
|
||||
return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)
|
||||
}
|
||||
|
||||
// Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
|
||||
func (r Rect) Norm() Rect {
|
||||
return Rect{
|
||||
Min: Vec{
|
||||
math.Min(r.Min.X, r.Max.X),
|
||||
math.Min(r.Min.Y, r.Max.Y),
|
||||
},
|
||||
Max: Vec{
|
||||
math.Max(r.Min.X, r.Max.X),
|
||||
math.Max(r.Min.Y, r.Max.Y),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// W returns the width of the Rect.
|
||||
func (r Rect) W() float64 {
|
||||
return r.Max.X - r.Min.X
|
||||
}
|
||||
|
||||
// H returns the height of the Rect.
|
||||
func (r Rect) H() float64 {
|
||||
return r.Max.Y - r.Min.Y
|
||||
}
|
||||
|
||||
// Size returns the vector of width and height of the Rect.
|
||||
func (r Rect) Size() Vec {
|
||||
return V(r.W(), r.H())
|
||||
}
|
||||
|
||||
// Area returns the area of r. If r is not normalized, area may be negative.
|
||||
func (r Rect) Area() float64 {
|
||||
return r.W() * r.H()
|
||||
}
|
||||
|
||||
// Center returns the position of the center of the Rect.
|
||||
func (r Rect) Center() Vec {
|
||||
return Lerp(r.Min, r.Max, 0.5)
|
||||
}
|
||||
|
||||
// Moved returns the Rect moved (both Min and Max) by the given vector delta.
|
||||
func (r Rect) Moved(delta Vec) Rect {
|
||||
return Rect{
|
||||
Min: r.Min.Add(delta),
|
||||
Max: r.Max.Add(delta),
|
||||
}
|
||||
}
|
||||
|
||||
// Resized returns the Rect resized to the given size while keeping the position of the given
|
||||
// anchor.
|
||||
//
|
||||
// r.Resized(r.Min, size) // resizes while keeping the position of the lower-left corner
|
||||
// r.Resized(r.Max, size) // same with the top-right corner
|
||||
// r.Resized(r.Center(), size) // resizes around the center
|
||||
//
|
||||
// This function does not make sense for resizing a rectangle of zero area and will panic. Use
|
||||
// ResizedMin in the case of zero area.
|
||||
func (r Rect) Resized(anchor, size Vec) Rect {
|
||||
if r.W()*r.H() == 0 {
|
||||
panic(fmt.Errorf("(%T).Resize: zero area", r))
|
||||
}
|
||||
fraction := Vec{size.X / r.W(), size.Y / r.H()}
|
||||
return Rect{
|
||||
Min: anchor.Add(r.Min.Sub(anchor).ScaledXY(fraction)),
|
||||
Max: anchor.Add(r.Max.Sub(anchor).ScaledXY(fraction)),
|
||||
}
|
||||
}
|
||||
|
||||
// ResizedMin returns the Rect resized to the given size while keeping the position of the Rect's
|
||||
// Min.
|
||||
//
|
||||
// Sizes of zero area are safe here.
|
||||
func (r Rect) ResizedMin(size Vec) Rect {
|
||||
return Rect{
|
||||
Min: r.Min,
|
||||
Max: r.Min.Add(size),
|
||||
}
|
||||
}
|
||||
|
||||
// Contains checks whether a vector u is contained within this Rect (including it's borders).
|
||||
func (r Rect) Contains(u Vec) bool {
|
||||
return r.Min.X <= u.X && u.X <= r.Max.X && r.Min.Y <= u.Y && u.Y <= r.Max.Y
|
||||
}
|
||||
|
||||
// Union returns the minimal Rect which covers both r and s. Rects r and s must be normalized.
|
||||
func (r Rect) Union(s Rect) Rect {
|
||||
return R(
|
||||
math.Min(r.Min.X, s.Min.X),
|
||||
math.Min(r.Min.Y, s.Min.Y),
|
||||
math.Max(r.Max.X, s.Max.X),
|
||||
math.Max(r.Max.Y, s.Max.Y),
|
||||
)
|
||||
}
|
||||
|
||||
// Intersect returns the maximal Rect which is covered by both r and s. Rects r and s must be normalized.
|
||||
//
|
||||
// If r and s don't overlap, this function returns R(0, 0, 0, 0).
|
||||
func (r Rect) Intersect(s Rect) Rect {
|
||||
t := R(
|
||||
math.Max(r.Min.X, s.Min.X),
|
||||
math.Max(r.Min.Y, s.Min.Y),
|
||||
math.Min(r.Max.X, s.Max.X),
|
||||
math.Min(r.Max.Y, s.Max.Y),
|
||||
)
|
||||
if t.Min.X >= t.Max.X || t.Min.Y >= t.Max.Y {
|
||||
return Rect{}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Matrix is a 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]}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package pixel_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
type rectTestTransform struct {
|
||||
name string
|
||||
f func(pixel.Rect) pixel.Rect
|
||||
}
|
||||
|
||||
func TestResizeRect(t *testing.T) {
|
||||
|
||||
// rectangles
|
||||
squareAroundOrigin := pixel.R(-10, -10, 10, 10)
|
||||
squareAround2020 := pixel.R(10, 10, 30, 30)
|
||||
rectangleAroundOrigin := pixel.R(-20, -10, 20, 10)
|
||||
rectangleAround2020 := pixel.R(0, 10, 40, 30)
|
||||
|
||||
// resize transformations
|
||||
resizeByHalfAroundCenter := rectTestTransform{"by half around center", func(rect pixel.Rect) pixel.Rect {
|
||||
return rect.Resized(rect.Center(), rect.Size().Scaled(0.5))
|
||||
}}
|
||||
resizeByHalfAroundMin := rectTestTransform{"by half around Min", func(rect pixel.Rect) pixel.Rect {
|
||||
return rect.Resized(rect.Min, rect.Size().Scaled(0.5))
|
||||
}}
|
||||
resizeByHalfAroundMax := rectTestTransform{"by half around Max", func(rect pixel.Rect) pixel.Rect {
|
||||
return rect.Resized(rect.Max, rect.Size().Scaled(0.5))
|
||||
}}
|
||||
resizeByHalfAroundMiddleOfLeftSide := rectTestTransform{"by half around middle of left side", func(rect pixel.Rect) pixel.Rect {
|
||||
return rect.Resized(pixel.V(rect.Min.X, rect.Center().Y), rect.Size().Scaled(0.5))
|
||||
}}
|
||||
resizeByHalfAroundOrigin := rectTestTransform{"by half around the origin", func(rect pixel.Rect) pixel.Rect {
|
||||
return rect.Resized(pixel.ZV, rect.Size().Scaled(0.5))
|
||||
}}
|
||||
|
||||
testCases := []struct {
|
||||
input pixel.Rect
|
||||
transform rectTestTransform
|
||||
answer pixel.Rect
|
||||
}{
|
||||
{squareAroundOrigin, resizeByHalfAroundCenter, pixel.R(-5, -5, 5, 5)},
|
||||
{squareAround2020, resizeByHalfAroundCenter, pixel.R(15, 15, 25, 25)},
|
||||
{rectangleAroundOrigin, resizeByHalfAroundCenter, pixel.R(-10, -5, 10, 5)},
|
||||
{rectangleAround2020, resizeByHalfAroundCenter, pixel.R(10, 15, 30, 25)},
|
||||
|
||||
{squareAroundOrigin, resizeByHalfAroundMin, pixel.R(-10, -10, 0, 0)},
|
||||
{squareAround2020, resizeByHalfAroundMin, pixel.R(10, 10, 20, 20)},
|
||||
{rectangleAroundOrigin, resizeByHalfAroundMin, pixel.R(-20, -10, 0, 0)},
|
||||
{rectangleAround2020, resizeByHalfAroundMin, pixel.R(0, 10, 20, 20)},
|
||||
|
||||
{squareAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 10, 10)},
|
||||
{squareAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 30, 30)},
|
||||
{rectangleAroundOrigin, resizeByHalfAroundMax, pixel.R(0, 0, 20, 10)},
|
||||
{rectangleAround2020, resizeByHalfAroundMax, pixel.R(20, 20, 40, 30)},
|
||||
|
||||
{squareAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-10, -5, 0, 5)},
|
||||
{squareAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(10, 15, 20, 25)},
|
||||
{rectangleAroundOrigin, resizeByHalfAroundMiddleOfLeftSide, pixel.R(-20, -5, 0, 5)},
|
||||
{rectangleAround2020, resizeByHalfAroundMiddleOfLeftSide, pixel.R(0, 15, 20, 25)},
|
||||
|
||||
{squareAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-5, -5, 5, 5)},
|
||||
{squareAround2020, resizeByHalfAroundOrigin, pixel.R(5, 5, 15, 15)},
|
||||
{rectangleAroundOrigin, resizeByHalfAroundOrigin, pixel.R(-10, -5, 10, 5)},
|
||||
{rectangleAround2020, resizeByHalfAroundOrigin, pixel.R(0, 5, 20, 15)},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(fmt.Sprintf("Resize %v %s", testCase.input, testCase.transform.name), func(t *testing.T) {
|
||||
testResult := testCase.transform.f(testCase.input)
|
||||
if testResult != testCase.answer {
|
||||
t.Errorf("Got: %v, wanted: %v\n", testResult, testCase.answer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
module github.com/faiface/pixel
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369
|
||||
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3
|
||||
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be
|
||||
github.com/go-gl/mathgl v1.0.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/image v0.5.0
|
||||
)
|
|
@ -0,0 +1,54 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369 h1:gv4BgP50atccdK/1tZHDyP6rMwiiutR2HPreR/OyLzI=
|
||||
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369/go.mod h1:dDdUO+G9ZnJ9sc8nIUvhLkE45k8PEKW6+A3TdWsfpV0=
|
||||
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q=
|
||||
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M=
|
||||
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 h1:8q7+xl2D2qHPLTII1t4vSMNP2VKwDcn+Avf2WXvdB1A=
|
||||
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
||||
github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be h1:UVW91pfMB1GRQfVwC7//RGVbqX6Ea8jURmJhlANak1M=
|
||||
github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
|
||||
github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -128,7 +128,9 @@ func (imd *IMDraw) Draw(t pixel.Target) {
|
|||
// Push adds some points to the IM queue. All Pushed points will have the same properties except for
|
||||
// the position.
|
||||
func (imd *IMDraw) Push(pts ...pixel.Vec) {
|
||||
// Assert that Color is of type pixel.RGBA,
|
||||
if _, ok := imd.Color.(pixel.RGBA); !ok {
|
||||
// otherwise cast it
|
||||
imd.Color = pixel.ToRGBA(imd.Color)
|
||||
}
|
||||
opts := point{
|
||||
|
|
13
interface.go
|
@ -92,7 +92,7 @@ type TrianglesColor interface {
|
|||
Color(i int) RGBA
|
||||
}
|
||||
|
||||
// TrianglesPicture specifies Triangles with Picture propery.
|
||||
// TrianglesPicture specifies Triangles with Picture property.
|
||||
//
|
||||
// The first value returned from Picture method is Picture coordinates. The second one specifies the
|
||||
// weight of the Picture. Value of 0 means, that Picture should be completely ignored, 1 means that
|
||||
|
@ -102,10 +102,19 @@ type TrianglesPicture interface {
|
|||
Picture(i int) (pic Vec, intensity float64)
|
||||
}
|
||||
|
||||
// TrianglesClipped specifies Triangles with Clipping Rectangle property.
|
||||
//
|
||||
// The first value returned from ClipRect method is the clipping rectangle. The second one specifies
|
||||
// if the triangle is clipped.
|
||||
type TrianglesClipped interface {
|
||||
Triangles
|
||||
ClipRect(i int) (rect Rect, is bool)
|
||||
}
|
||||
|
||||
// Picture represents a rectangular area of raster data, such as a color. It has Bounds which
|
||||
// specify the rectangle where data is located.
|
||||
type Picture interface {
|
||||
// Bounds returns the rectangle of the Picture. All data is located witih this rectangle.
|
||||
// Bounds returns the rectangle of the Picture. All data is located within this rectangle.
|
||||
// Querying properties outside the rectangle should return default value of that property.
|
||||
Bounds() Rect
|
||||
}
|
||||
|
|
|
@ -0,0 +1,699 @@
|
|||
package pixel_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
func TestLine_Bounds(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want pixel.Rect
|
||||
}{
|
||||
{
|
||||
name: "Positive slope",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
want: pixel.R(0, 0, 10, 10),
|
||||
},
|
||||
{
|
||||
name: "Negative slope",
|
||||
fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)},
|
||||
want: pixel.R(0, 0, 10, 10),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.Bounds(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.Bounds() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Center(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want pixel.Vec
|
||||
}{
|
||||
{
|
||||
name: "Positive slope",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
want: pixel.V(5, 5),
|
||||
},
|
||||
{
|
||||
name: "Negative slope",
|
||||
fields: fields{A: pixel.V(10, 10), B: pixel.V(0, 0)},
|
||||
want: pixel.V(5, 5),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.Center(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.Center() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Closest(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
v pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Vec
|
||||
}{
|
||||
{
|
||||
name: "Point on line",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{v: pixel.V(5, 5)},
|
||||
want: pixel.V(5, 5),
|
||||
},
|
||||
{
|
||||
name: "Point on next to line",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{v: pixel.V(0, 10)},
|
||||
want: pixel.V(5, 5),
|
||||
},
|
||||
{
|
||||
name: "Point on next to vertical line",
|
||||
fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)},
|
||||
args: args{v: pixel.V(6, 5)},
|
||||
want: pixel.V(5, 5),
|
||||
},
|
||||
{
|
||||
name: "Point on next to horizontal line",
|
||||
fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)},
|
||||
args: args{v: pixel.V(5, 6)},
|
||||
want: pixel.V(5, 5),
|
||||
},
|
||||
{
|
||||
name: "Point far from line",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{v: pixel.V(80, -70)},
|
||||
want: pixel.V(5, 5),
|
||||
},
|
||||
{
|
||||
name: "Point on inline with line",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{v: pixel.V(20, 20)},
|
||||
want: pixel.V(10, 10),
|
||||
},
|
||||
{
|
||||
name: "Vertical line",
|
||||
fields: fields{A: pixel.V(0, -10), B: pixel.V(0, 10)},
|
||||
args: args{v: pixel.V(-1, 0)},
|
||||
want: pixel.V(0, 0),
|
||||
},
|
||||
{
|
||||
name: "Horizontal line",
|
||||
fields: fields{A: pixel.V(-10, 0), B: pixel.V(10, 0)},
|
||||
args: args{v: pixel.V(0, -1)},
|
||||
want: pixel.V(0, 0),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.Closest(tt.args.v); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.Closest() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Contains(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
v pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Point on line",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{v: pixel.V(5, 5)},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Point on negative sloped line",
|
||||
fields: fields{A: pixel.V(0, 10), B: pixel.V(10, 0)},
|
||||
args: args{v: pixel.V(5, 5)},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Point not on line",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{v: pixel.V(0, 10)},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.Contains(tt.args.v); got != tt.want {
|
||||
t.Errorf("Line.Contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Formula(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantM float64
|
||||
wantB float64
|
||||
}{
|
||||
{
|
||||
name: "Getting formula - 45 degs",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
wantM: 1,
|
||||
wantB: 0,
|
||||
},
|
||||
{
|
||||
name: "Getting formula - 90 degs",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)},
|
||||
wantM: math.Inf(1),
|
||||
wantB: math.NaN(),
|
||||
},
|
||||
{
|
||||
name: "Getting formula - 0 degs",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 0)},
|
||||
wantM: 0,
|
||||
wantB: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
gotM, gotB := l.Formula()
|
||||
if gotM != tt.wantM {
|
||||
t.Errorf("Line.Formula() gotM = %v, want %v", gotM, tt.wantM)
|
||||
}
|
||||
if gotB != tt.wantB {
|
||||
if math.IsNaN(tt.wantB) && !math.IsNaN(gotB) {
|
||||
t.Errorf("Line.Formula() gotB = %v, want %v", gotB, tt.wantB)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Intersect(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
k pixel.Line
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Vec
|
||||
want1 bool
|
||||
}{
|
||||
{
|
||||
name: "Lines intersect",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{k: pixel.L(pixel.V(0, 10), pixel.V(10, 0))},
|
||||
want: pixel.V(5, 5),
|
||||
want1: true,
|
||||
},
|
||||
{
|
||||
name: "Lines intersect 2",
|
||||
fields: fields{A: pixel.V(5, 1), B: pixel.V(1, 1)},
|
||||
args: args{k: pixel.L(pixel.V(2, 0), pixel.V(2, 3))},
|
||||
want: pixel.V(2, 1),
|
||||
want1: true,
|
||||
},
|
||||
{
|
||||
name: "Line intersect with vertical",
|
||||
fields: fields{A: pixel.V(5, 0), B: pixel.V(5, 10)},
|
||||
args: args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: pixel.V(5, 5),
|
||||
want1: true,
|
||||
},
|
||||
{
|
||||
name: "Line intersect with horizontal",
|
||||
fields: fields{A: pixel.V(0, 5), B: pixel.V(10, 5)},
|
||||
args: args{k: pixel.L(pixel.V(0, 0), pixel.V(10, 10))},
|
||||
want: pixel.V(5, 5),
|
||||
want1: true,
|
||||
},
|
||||
{
|
||||
name: "Lines don't intersect",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{k: pixel.L(pixel.V(0, 10), pixel.V(1, 20))},
|
||||
want: pixel.ZV,
|
||||
want1: false,
|
||||
},
|
||||
{
|
||||
name: "Lines don't intersect 2",
|
||||
fields: fields{A: pixel.V(1, 1), B: pixel.V(1, 5)},
|
||||
args: args{k: pixel.L(pixel.V(-5, 0), pixel.V(-2, 2))},
|
||||
want: pixel.ZV,
|
||||
want1: false,
|
||||
},
|
||||
{
|
||||
name: "Lines don't intersect 3",
|
||||
fields: fields{A: pixel.V(2, 0), B: pixel.V(2, 3)},
|
||||
args: args{k: pixel.L(pixel.V(1, 5), pixel.V(5, 5))},
|
||||
want: pixel.ZV,
|
||||
want1: false,
|
||||
},
|
||||
{
|
||||
name: "Lines parallel",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{k: pixel.L(pixel.V(0, 1), pixel.V(10, 11))},
|
||||
want: pixel.ZV,
|
||||
want1: false,
|
||||
}, {
|
||||
name: "Lines intersect",
|
||||
fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)},
|
||||
args: args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255))},
|
||||
want: pixel.V(849.1666666666666, 255),
|
||||
want1: true,
|
||||
},
|
||||
{
|
||||
name: "Lines intersect",
|
||||
fields: fields{A: pixel.V(600, 600), B: pixel.V(925, 150)},
|
||||
args: args{k: pixel.L(pixel.V(740, 255), pixel.V(925, 255.0001))},
|
||||
want: pixel.V(849.1666240490657, 255.000059008986),
|
||||
want1: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
got, got1 := l.Intersect(tt.args.k)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.Intersect() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("Line.Intersect() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_IntersectCircle(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
c pixel.Circle
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Vec
|
||||
}{
|
||||
{
|
||||
name: "Cirle intersects",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(6, 4), 2)},
|
||||
want: pixel.V(0.5857864376269049, -0.5857864376269049),
|
||||
},
|
||||
{
|
||||
name: "Cirle doesn't intersects",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(0, 5), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.IntersectCircle(tt.args.c); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.IntersectCircle() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_IntersectRect(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
r pixel.Rect
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Vec
|
||||
}{
|
||||
{
|
||||
name: "Line through rect vertically",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 10)},
|
||||
args: args{r: pixel.R(-1, 1, 5, 5)},
|
||||
want: pixel.V(-1, 0),
|
||||
},
|
||||
{
|
||||
name: "Line through rect horizontally",
|
||||
fields: fields{A: pixel.V(0, 1), B: pixel.V(10, 1)},
|
||||
args: args{r: pixel.R(1, 0, 5, 5)},
|
||||
want: pixel.V(0, -1),
|
||||
},
|
||||
{
|
||||
name: "Line through rect diagonally bottom and left edges",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{r: pixel.R(0, 2, 3, 3)},
|
||||
want: pixel.V(-1, 1),
|
||||
},
|
||||
{
|
||||
name: "Line through rect diagonally top and right edges",
|
||||
fields: fields{A: pixel.V(10, 0), B: pixel.V(0, 10)},
|
||||
args: args{r: pixel.R(5, 0, 8, 3)},
|
||||
want: pixel.V(-2.5, -2.5),
|
||||
},
|
||||
{
|
||||
name: "Line with not rect intersect",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{r: pixel.R(20, 20, 21, 21)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
{
|
||||
name: "Line intersects at 0,0",
|
||||
fields: fields{A: pixel.V(0, -10), B: pixel.V(0, 10)},
|
||||
args: args{r: pixel.R(-1, 0, 2, 2)},
|
||||
want: pixel.V(-1, 0),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.IntersectRect(tt.args.r); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.IntersectRect() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Len(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want float64
|
||||
}{
|
||||
{
|
||||
name: "End right-up of start",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(3, 4)},
|
||||
want: 5,
|
||||
},
|
||||
{
|
||||
name: "End left-up of start",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, 4)},
|
||||
want: 5,
|
||||
},
|
||||
{
|
||||
name: "End right-down of start",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(3, -4)},
|
||||
want: 5,
|
||||
},
|
||||
{
|
||||
name: "End left-down of start",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(-3, -4)},
|
||||
want: 5,
|
||||
},
|
||||
{
|
||||
name: "End same as start",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(0, 0)},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.Len(); got != tt.want {
|
||||
t.Errorf("Line.Len() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Rotated(t *testing.T) {
|
||||
// round returns the nearest integer, rounding ties away from zero.
|
||||
// This is required because `math.Round` wasn't introduced until Go1.10
|
||||
round := func(x float64) float64 {
|
||||
t := math.Trunc(x)
|
||||
if math.Abs(x-t) >= 0.5 {
|
||||
return t + math.Copysign(1, x)
|
||||
}
|
||||
return t
|
||||
}
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
around pixel.Vec
|
||||
angle float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Line
|
||||
}{
|
||||
{
|
||||
name: "Rotating around line center",
|
||||
fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
|
||||
args: args{around: pixel.V(2, 2), angle: math.Pi},
|
||||
want: pixel.L(pixel.V(3, 3), pixel.V(1, 1)),
|
||||
},
|
||||
{
|
||||
name: "Rotating around x-y origin",
|
||||
fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
|
||||
args: args{around: pixel.V(0, 0), angle: math.Pi},
|
||||
want: pixel.L(pixel.V(-1, -1), pixel.V(-3, -3)),
|
||||
},
|
||||
{
|
||||
name: "Rotating around line end",
|
||||
fields: fields{A: pixel.V(1, 1), B: pixel.V(3, 3)},
|
||||
args: args{around: pixel.V(1, 1), angle: math.Pi},
|
||||
want: pixel.L(pixel.V(1, 1), pixel.V(-1, -1)),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
// Have to round the results, due to floating-point in accuracies. Results are correct to approximately
|
||||
// 10 decimal places.
|
||||
got := l.Rotated(tt.args.around, tt.args.angle)
|
||||
if round(got.A.X) != tt.want.A.X ||
|
||||
round(got.B.X) != tt.want.B.X ||
|
||||
round(got.A.Y) != tt.want.A.Y ||
|
||||
round(got.B.Y) != tt.want.B.Y {
|
||||
t.Errorf("Line.Rotated() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_Scaled(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
scale float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Line
|
||||
}{
|
||||
{
|
||||
name: "Scaling by 1",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{scale: 1},
|
||||
want: pixel.L(pixel.V(0, 0), pixel.V(10, 10)),
|
||||
},
|
||||
{
|
||||
name: "Scaling by >1",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{scale: 2},
|
||||
want: pixel.L(pixel.V(-5, -5), pixel.V(15, 15)),
|
||||
},
|
||||
{
|
||||
name: "Scaling by <1",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{scale: 0.5},
|
||||
want: pixel.L(pixel.V(2.5, 2.5), pixel.V(7.5, 7.5)),
|
||||
},
|
||||
{
|
||||
name: "Scaling by -1",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{scale: -1},
|
||||
want: pixel.L(pixel.V(10, 10), pixel.V(0, 0)),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.Scaled(tt.args.scale); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.Scaled() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_ScaledXY(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
around pixel.Vec
|
||||
scale float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Line
|
||||
}{
|
||||
{
|
||||
name: "Scaling by 1 around origin",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{around: pixel.ZV, scale: 1},
|
||||
want: pixel.L(pixel.V(0, 0), pixel.V(10, 10)),
|
||||
},
|
||||
{
|
||||
name: "Scaling by >1 around origin",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{around: pixel.ZV, scale: 2},
|
||||
want: pixel.L(pixel.V(0, 0), pixel.V(20, 20)),
|
||||
},
|
||||
{
|
||||
name: "Scaling by <1 around origin",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{around: pixel.ZV, scale: 0.5},
|
||||
want: pixel.L(pixel.V(0, 0), pixel.V(5, 5)),
|
||||
},
|
||||
{
|
||||
name: "Scaling by -1 around origin",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(10, 10)},
|
||||
args: args{around: pixel.ZV, scale: -1},
|
||||
want: pixel.L(pixel.V(0, 0), pixel.V(-10, -10)),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.ScaledXY(tt.args.around, tt.args.scale); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Line.ScaledXY() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLine_String(t *testing.T) {
|
||||
type fields struct {
|
||||
A pixel.Vec
|
||||
B pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Getting string",
|
||||
fields: fields{A: pixel.V(0, 0), B: pixel.V(1, 1)},
|
||||
want: "Line(Vec(0, 0), Vec(1, 1))",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := pixel.Line{
|
||||
A: tt.fields.A,
|
||||
B: tt.fields.B,
|
||||
}
|
||||
if got := l.String(); got != tt.want {
|
||||
t.Errorf("Line.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,15 @@
|
|||
package pixel
|
||||
|
||||
// Clamp returns x clamped to the interval [min, max].
|
||||
//
|
||||
// If x is less than min, min is returned. If x is more than max, max is returned. Otherwise, x is
|
||||
// returned.
|
||||
func Clamp(x, min, max float64) float64 {
|
||||
if x < min {
|
||||
return min
|
||||
}
|
||||
if x > max {
|
||||
return max
|
||||
}
|
||||
return x
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package pixel_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
// closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them.
|
||||
// Effectively this compares two floats to a given decimal point.
|
||||
// Example:
|
||||
// closeEnough(100.125342432, 100.125, 2) == true
|
||||
// closeEnough(math.Pi, 3.14, 2) == true
|
||||
// closeEnough(0.1234, 0.1245, 3) == false
|
||||
func closeEnough(got, expected float64, decimalAccuracy int) bool {
|
||||
gotShifted := got * math.Pow10(decimalAccuracy)
|
||||
expectedShifted := expected * math.Pow10(decimalAccuracy)
|
||||
|
||||
return math.Trunc(gotShifted) == math.Trunc(expectedShifted)
|
||||
}
|
||||
|
||||
type clampTest struct {
|
||||
number float64
|
||||
min float64
|
||||
max float64
|
||||
expected float64
|
||||
}
|
||||
|
||||
func TestClamp(t *testing.T) {
|
||||
tests := []clampTest{
|
||||
{number: 1, min: 0, max: 5, expected: 1},
|
||||
{number: 2, min: 0, max: 5, expected: 2},
|
||||
{number: 8, min: 0, max: 5, expected: 5},
|
||||
{number: -5, min: 0, max: 5, expected: 0},
|
||||
{number: -5, min: -4, max: 5, expected: -4},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
result := pixel.Clamp(tc.number, tc.min, tc.max)
|
||||
if result != tc.expected {
|
||||
t.Error(fmt.Sprintf("Clamping %v with min %v and max %v should have given %v, but gave %v", tc.number, tc.min, tc.max, tc.expected, result))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package pixel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
|
||||
// as movement, scaling and rotations.
|
||||
//
|
||||
// Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For
|
||||
// example:
|
||||
//
|
||||
// pixel.IM.Moved(pixel.V(100, 200)).Rotated(pixel.ZV, math.Pi/2)
|
||||
//
|
||||
// This code creates a Matrix that first moves everything by 100 units horizontally and 200 units
|
||||
// vertically and then rotates everything by 90 degrees around the origin.
|
||||
//
|
||||
// Layout is:
|
||||
// [0] [2] [4]
|
||||
// [1] [3] [5]
|
||||
// 0 0 1 (implicit row)
|
||||
type Matrix [6]float64
|
||||
|
||||
// IM stands for identity matrix. Does nothing, no transformation.
|
||||
var IM = Matrix{1, 0, 0, 1, 0, 0}
|
||||
|
||||
// String returns a string representation of the Matrix.
|
||||
//
|
||||
// m := pixel.IM
|
||||
// fmt.Println(m) // Matrix(1 0 0 | 0 1 0)
|
||||
func (m Matrix) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Matrix(%v %v %v | %v %v %v)",
|
||||
m[0], m[2], m[4],
|
||||
m[1], m[3], m[5],
|
||||
)
|
||||
}
|
||||
|
||||
// Moved moves everything by the delta vector.
|
||||
func (m Matrix) Moved(delta Vec) Matrix {
|
||||
m[4], m[5] = m[4]+delta.X, m[5]+delta.Y
|
||||
return m
|
||||
}
|
||||
|
||||
// ScaledXY scales everything around a given point by the scale factor in each axis respectively.
|
||||
func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
|
||||
m[4], m[5] = m[4]-around.X, m[5]-around.Y
|
||||
m[0], m[2], m[4] = m[0]*scale.X, m[2]*scale.X, m[4]*scale.X
|
||||
m[1], m[3], m[5] = m[1]*scale.Y, m[3]*scale.Y, m[5]*scale.Y
|
||||
m[4], m[5] = m[4]+around.X, m[5]+around.Y
|
||||
return m
|
||||
}
|
||||
|
||||
// Scaled scales everything around a given point by the scale factor.
|
||||
func (m Matrix) Scaled(around Vec, scale float64) Matrix {
|
||||
return m.ScaledXY(around, V(scale, scale))
|
||||
}
|
||||
|
||||
// Rotated rotates everything around a given point by the given angle in radians.
|
||||
func (m Matrix) Rotated(around Vec, angle float64) Matrix {
|
||||
sint, cost := math.Sincos(angle)
|
||||
m[4], m[5] = m[4]-around.X, m[5]-around.Y
|
||||
m = m.Chained(Matrix{cost, sint, -sint, cost, 0, 0})
|
||||
m[4], m[5] = m[4]+around.X, m[5]+around.Y
|
||||
return m
|
||||
}
|
||||
|
||||
// Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied
|
||||
// after the transformations of this Matrix.
|
||||
func (m Matrix) Chained(next Matrix) Matrix {
|
||||
return Matrix{
|
||||
next[0]*m[0] + next[2]*m[1],
|
||||
next[1]*m[0] + next[3]*m[1],
|
||||
next[0]*m[2] + next[2]*m[3],
|
||||
next[1]*m[2] + next[3]*m[3],
|
||||
next[0]*m[4] + next[2]*m[5] + next[4],
|
||||
next[1]*m[4] + next[3]*m[5] + next[5],
|
||||
}
|
||||
}
|
||||
|
||||
// Project applies all transformations added to the Matrix to a vector u and returns the result.
|
||||
//
|
||||
// Time complexity is O(1).
|
||||
func (m Matrix) Project(u Vec) Vec {
|
||||
return Vec{m[0]*u.X + m[2]*u.Y + m[4], m[1]*u.X + m[3]*u.Y + m[5]}
|
||||
}
|
||||
|
||||
// Unproject does the inverse operation to Project.
|
||||
//
|
||||
// Time complexity is O(1).
|
||||
func (m Matrix) Unproject(u Vec) Vec {
|
||||
det := m[0]*m[3] - m[2]*m[1]
|
||||
return Vec{
|
||||
(m[3]*(u.X-m[4]) - m[2]*(u.Y-m[5])) / det,
|
||||
(-m[1]*(u.X-m[4]) + m[0]*(u.Y-m[5])) / det,
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
package pixel_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkMatrix(b *testing.B) {
|
||||
|
@ -61,3 +64,86 @@ func BenchmarkMatrix(b *testing.B) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMatrix_Unproject(t *testing.T) {
|
||||
const delta = 1e-15
|
||||
t.Run("for rotated matrix", func(t *testing.T) {
|
||||
matrix := pixel.IM.
|
||||
Rotated(pixel.ZV, math.Pi/2)
|
||||
unprojected := matrix.Unproject(pixel.V(0, 1))
|
||||
assert.InDelta(t, unprojected.X, 1, delta)
|
||||
assert.InDelta(t, unprojected.Y, 0, delta)
|
||||
})
|
||||
t.Run("for moved matrix", func(t *testing.T) {
|
||||
matrix := pixel.IM.
|
||||
Moved(pixel.V(1, 2))
|
||||
unprojected := matrix.Unproject(pixel.V(2, 5))
|
||||
assert.InDelta(t, unprojected.X, 1, delta)
|
||||
assert.InDelta(t, unprojected.Y, 3, delta)
|
||||
})
|
||||
t.Run("for scaled matrix", func(t *testing.T) {
|
||||
matrix := pixel.IM.
|
||||
Scaled(pixel.ZV, 2)
|
||||
unprojected := matrix.Unproject(pixel.V(2, 4))
|
||||
assert.InDelta(t, unprojected.X, 1, delta)
|
||||
assert.InDelta(t, unprojected.Y, 2, delta)
|
||||
})
|
||||
t.Run("for scaled, rotated and moved matrix", func(t *testing.T) {
|
||||
matrix := pixel.IM.
|
||||
Scaled(pixel.ZV, 2).
|
||||
Rotated(pixel.ZV, math.Pi/2).
|
||||
Moved(pixel.V(2, 2))
|
||||
unprojected := matrix.Unproject(pixel.V(-2, 6))
|
||||
assert.InDelta(t, unprojected.X, 2, delta)
|
||||
assert.InDelta(t, unprojected.Y, 2, delta)
|
||||
})
|
||||
t.Run("for rotated and moved matrix", func(t *testing.T) {
|
||||
matrix := pixel.IM.
|
||||
Rotated(pixel.ZV, math.Pi/2).
|
||||
Moved(pixel.V(1, 1))
|
||||
unprojected := matrix.Unproject(pixel.V(1, 2))
|
||||
assert.InDelta(t, unprojected.X, 1, delta)
|
||||
assert.InDelta(t, unprojected.Y, 0, delta)
|
||||
})
|
||||
t.Run("for projected vertices using all kinds of matrices", func(t *testing.T) {
|
||||
namedMatrices := map[string]pixel.Matrix{
|
||||
"IM": pixel.IM,
|
||||
"Scaled": pixel.IM.Scaled(pixel.ZV, 0.5),
|
||||
"Scaled x 2": pixel.IM.Scaled(pixel.ZV, 2),
|
||||
"Rotated": pixel.IM.Rotated(pixel.ZV, math.Pi/4),
|
||||
"Moved": pixel.IM.Moved(pixel.V(0.5, 1)),
|
||||
"Moved 2": pixel.IM.Moved(pixel.V(-1, -0.5)),
|
||||
"Scaled and Rotated": pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4),
|
||||
"Scaled, Rotated and Moved": pixel.IM.Scaled(pixel.ZV, 0.5).Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)),
|
||||
"Rotated and Moved": pixel.IM.Rotated(pixel.ZV, math.Pi/4).Moved(pixel.V(1, 2)),
|
||||
}
|
||||
vertices := [...]pixel.Vec{
|
||||
pixel.V(0, 0),
|
||||
pixel.V(5, 0),
|
||||
pixel.V(5, 10),
|
||||
pixel.V(0, 10),
|
||||
pixel.V(-5, 10),
|
||||
pixel.V(-5, 0),
|
||||
pixel.V(-5, -10),
|
||||
pixel.V(0, -10),
|
||||
pixel.V(5, -10),
|
||||
}
|
||||
for matrixName, matrix := range namedMatrices {
|
||||
for _, vertex := range vertices {
|
||||
testCase := fmt.Sprintf("for matrix %s and vertex %v", matrixName, vertex)
|
||||
t.Run(testCase, func(t *testing.T) {
|
||||
projected := matrix.Project(vertex)
|
||||
unprojected := matrix.Unproject(projected)
|
||||
assert.InDelta(t, vertex.X, unprojected.X, delta)
|
||||
assert.InDelta(t, vertex.Y, unprojected.Y, delta)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("for singular matrix", func(t *testing.T) {
|
||||
matrix := pixel.Matrix{0, 0, 0, 0, 0, 0}
|
||||
unprojected := matrix.Unproject(pixel.ZV)
|
||||
assert.True(t, math.IsNaN(unprojected.X))
|
||||
assert.True(t, math.IsNaN(unprojected.Y))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package pixel_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
_ "image/png"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/pixel/pixelgl"
|
||||
)
|
||||
|
||||
// onePixelImage is the byte representation of a 1x1 solid white png file
|
||||
var onePixelImage = []byte{
|
||||
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 2,
|
||||
0, 0, 0, 144, 119, 83, 222, 0, 0, 1, 130, 105, 67, 67, 80, 73, 67, 67, 32, 112, 114, 111, 102, 105, 108, 101, 0,
|
||||
0, 40, 145, 125, 145, 59, 72, 3, 65, 20, 69, 143, 73, 68, 17, 37, 133, 41, 68, 44, 182, 80, 43, 5, 81, 17, 75,
|
||||
141, 66, 16, 34, 132, 168, 96, 212, 194, 221, 141, 137, 66, 118, 13, 187, 9, 54, 150, 130, 109, 192, 194, 79,
|
||||
227, 175, 176, 177, 214, 214, 194, 86, 16, 4, 63, 32, 54, 182, 86, 138, 54, 18, 214, 55, 73, 32, 65, 140, 3,
|
||||
195, 28, 238, 188, 123, 121, 243, 6, 124, 71, 25, 211, 114, 3, 3, 96, 217, 57, 39, 30, 9, 107, 243, 137, 5, 173,
|
||||
233, 149, 0, 65, 90, 104, 0, 221, 116, 179, 227, 177, 88, 148, 186, 235, 235, 94, 213, 193, 93, 191, 202, 170,
|
||||
95, 247, 231, 106, 75, 174, 184, 38, 52, 104, 194, 99, 102, 214, 201, 9, 47, 11, 143, 108, 228, 178, 138, 247,
|
||||
132, 67, 230, 170, 158, 20, 62, 23, 238, 115, 164, 65, 225, 71, 165, 27, 101, 126, 83, 156, 46, 177, 79, 101,
|
||||
134, 156, 217, 248, 132, 112, 72, 88, 75, 215, 176, 81, 195, 230, 170, 99, 9, 15, 11, 119, 39, 45, 91, 242, 125,
|
||||
243, 101, 78, 42, 222, 84, 108, 101, 242, 102, 165, 79, 245, 194, 214, 21, 123, 110, 70, 233, 178, 187, 136, 48,
|
||||
197, 52, 49, 52, 12, 242, 172, 145, 33, 71, 191, 156, 182, 40, 46, 113, 185, 15, 215, 241, 119, 150, 252, 49,
|
||||
113, 25, 226, 90, 195, 20, 199, 36, 235, 88, 232, 37, 63, 234, 15, 126, 207, 214, 77, 13, 13, 150, 147, 90, 195,
|
||||
208, 248, 226, 121, 31, 61, 208, 180, 3, 197, 130, 231, 125, 31, 123, 94, 241, 4, 252, 207, 112, 101, 87, 253,
|
||||
235, 71, 48, 250, 41, 122, 161, 170, 117, 31, 66, 112, 11, 46, 174, 171, 154, 177, 11, 151, 219, 208, 241, 148,
|
||||
213, 29, 189, 36, 249, 101, 251, 82, 41, 120, 63, 147, 111, 74, 64, 251, 45, 180, 44, 150, 231, 86, 185, 231,
|
||||
244, 1, 102, 101, 86, 209, 27, 216, 63, 128, 222, 180, 100, 47, 213, 121, 119, 115, 237, 220, 254, 173, 169,
|
||||
204, 239, 7, 178, 211, 114, 90, 10, 150, 157, 65, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 46, 35, 0, 0, 46, 35, 1,
|
||||
120, 165, 63, 118, 0, 0, 0, 7, 116, 73, 77, 69, 7, 227, 4, 15, 10, 5, 36, 189, 4, 224, 88, 0, 0, 0, 25, 116, 69,
|
||||
88, 116, 67, 111, 109, 109, 101, 110, 116, 0, 67, 114, 101, 97, 116, 101, 100, 32, 119, 105, 116, 104, 32, 71,
|
||||
73, 77, 80, 87, 129, 14, 23, 0, 0, 0, 12, 73, 68, 65, 84, 8, 215, 99, 120, 241, 226, 61, 0, 5, 123, 2, 192, 194,
|
||||
77, 211, 95, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pixelgl.Run(func() {
|
||||
os.Exit(m.Run())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSprite_Draw(t *testing.T) {
|
||||
img, _, err := image.Decode(bytes.NewReader(onePixelImage))
|
||||
if err != nil {
|
||||
t.Fatalf("Could not decode image: %v", err)
|
||||
}
|
||||
pic := pixel.PictureDataFromImage(img)
|
||||
|
||||
sprite := pixel.NewSprite(pic, pic.Bounds())
|
||||
|
||||
cfg := pixelgl.WindowConfig{
|
||||
Title: "testing",
|
||||
Bounds: pixel.R(0, 0, 150, 150),
|
||||
Invisible: true,
|
||||
}
|
||||
|
||||
win, err := pixelgl.NewWindow(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create window: %v", err)
|
||||
}
|
||||
|
||||
sprite.Draw(win, pixel.IM)
|
||||
}
|
|
@ -17,7 +17,7 @@ import (
|
|||
// It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor.
|
||||
type Canvas struct {
|
||||
gf *GLFrame
|
||||
shader *glhf.Shader
|
||||
shader *GLShader
|
||||
|
||||
cmp pixel.ComposeMethod
|
||||
mat mgl32.Mat3
|
||||
|
@ -37,30 +37,35 @@ 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,
|
||||
|
@ -128,7 +133,7 @@ func (c *Canvas) SetBounds(bounds pixel.Rect) {
|
|||
c.sprite = pixel.NewSprite(nil, pixel.Rect{})
|
||||
}
|
||||
c.sprite.Set(c, c.Bounds())
|
||||
//c.sprite.SetMatrix(pixel.IM.Moved(c.Bounds().Center()))
|
||||
// c.sprite.SetMatrix(pixel.IM.Moved(c.Bounds().Center()))
|
||||
}
|
||||
|
||||
// Bounds returns the rectangular bounds of the Canvas.
|
||||
|
@ -279,29 +284,41 @@ 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.dst.shader
|
||||
shader := ct.shader.s
|
||||
|
||||
frame.Begin()
|
||||
shader.Begin()
|
||||
|
||||
ct.shader.uniformDefaults.transform = mat
|
||||
ct.shader.uniformDefaults.colormask = col
|
||||
dstBounds := ct.dst.Bounds()
|
||||
shader.SetUniformAttr(canvasBounds, mgl32.Vec4{
|
||||
ct.shader.uniformDefaults.bounds = mgl32.Vec4{
|
||||
float32(dstBounds.Min.X),
|
||||
float32(dstBounds.Min.Y),
|
||||
float32(dstBounds.W()),
|
||||
float32(dstBounds.H()),
|
||||
})
|
||||
shader.SetUniformAttr(canvasTransform, mat)
|
||||
shader.SetUniformAttr(canvasColorMask, col)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
if tex == nil {
|
||||
ct.vs.Begin()
|
||||
|
@ -310,14 +327,6 @@ 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)
|
||||
}
|
||||
|
@ -350,82 +359,3 @@ func (cp *canvasPicture) Draw(t pixel.TargetTriangles) {
|
|||
}
|
||||
ct.draw(cp.GLPicture.Texture(), cp.GLPicture.Bounds())
|
||||
}
|
||||
|
||||
const (
|
||||
canvasPosition int = iota
|
||||
canvasColor
|
||||
canvasTexCoords
|
||||
canvasIntensity
|
||||
)
|
||||
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
`
|
|
@ -15,23 +15,43 @@ import (
|
|||
type GLTriangles struct {
|
||||
vs *glhf.VertexSlice
|
||||
data []float32
|
||||
shader *glhf.Shader
|
||||
shader *GLShader
|
||||
}
|
||||
|
||||
var (
|
||||
_ pixel.TrianglesPosition = (*GLTriangles)(nil)
|
||||
_ pixel.TrianglesColor = (*GLTriangles)(nil)
|
||||
_ pixel.TrianglesPicture = (*GLTriangles)(nil)
|
||||
_ pixel.TrianglesClipped = (*GLTriangles)(nil)
|
||||
)
|
||||
|
||||
// The following is a helper so that the indices of
|
||||
// each of these items is easier to see/debug.
|
||||
const (
|
||||
triPosX = iota
|
||||
triPosY
|
||||
triColorR
|
||||
triColorG
|
||||
triColorB
|
||||
triColorA
|
||||
triPicX
|
||||
triPicY
|
||||
triIntensity
|
||||
triClipMinX
|
||||
triClipMinY
|
||||
triClipMaxX
|
||||
triClipMaxY
|
||||
trisAttrLen
|
||||
)
|
||||
|
||||
// NewGLTriangles returns GLTriangles initialized with the data from the supplied Triangles.
|
||||
//
|
||||
// Only draw the Triangles using the provided Shader.
|
||||
func NewGLTriangles(shader *glhf.Shader, t pixel.Triangles) *GLTriangles {
|
||||
func NewGLTriangles(shader *GLShader, t pixel.Triangles) *GLTriangles {
|
||||
var gt *GLTriangles
|
||||
mainthread.Call(func() {
|
||||
gt = &GLTriangles{
|
||||
vs: glhf.MakeVertexSlice(shader, 0, t.Len()),
|
||||
vs: glhf.MakeVertexSlice(shader.s, 0, t.Len()),
|
||||
shader: shader,
|
||||
}
|
||||
})
|
||||
|
@ -48,7 +68,7 @@ func (gt *GLTriangles) VertexSlice() *glhf.VertexSlice {
|
|||
}
|
||||
|
||||
// Shader returns the GLTriangles's associated shader.
|
||||
func (gt *GLTriangles) Shader() *glhf.Shader {
|
||||
func (gt *GLTriangles) Shader() *GLShader {
|
||||
return gt.shader
|
||||
}
|
||||
|
||||
|
@ -70,6 +90,7 @@ func (gt *GLTriangles) SetLen(length int) {
|
|||
1, 1, 1, 1,
|
||||
0, 0,
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
)
|
||||
}
|
||||
case length < gt.Len():
|
||||
|
@ -77,7 +98,7 @@ func (gt *GLTriangles) SetLen(length int) {
|
|||
default:
|
||||
return
|
||||
}
|
||||
mainthread.CallNonBlock(func() {
|
||||
mainthread.Call(func() {
|
||||
gt.vs.Begin()
|
||||
gt.vs.SetLen(length)
|
||||
gt.vs.End()
|
||||
|
@ -110,17 +131,22 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
|
|||
col = (*t)[i].Color
|
||||
tx, ty = (*t)[i].Picture.XY()
|
||||
in = (*t)[i].Intensity
|
||||
rec = (*t)[i].ClipRect
|
||||
)
|
||||
d := gt.data[i*stride : i*stride+9]
|
||||
d[0] = float32(px)
|
||||
d[1] = float32(py)
|
||||
d[2] = float32(col.R)
|
||||
d[3] = float32(col.G)
|
||||
d[4] = float32(col.B)
|
||||
d[5] = float32(col.A)
|
||||
d[6] = float32(tx)
|
||||
d[7] = float32(ty)
|
||||
d[8] = float32(in)
|
||||
d := gt.data[i*stride : i*stride+trisAttrLen]
|
||||
d[triPosX] = float32(px)
|
||||
d[triPosY] = float32(py)
|
||||
d[triColorR] = float32(col.R)
|
||||
d[triColorG] = float32(col.G)
|
||||
d[triColorB] = float32(col.B)
|
||||
d[triColorA] = float32(col.A)
|
||||
d[triPicX] = float32(tx)
|
||||
d[triPicY] = float32(ty)
|
||||
d[triIntensity] = float32(in)
|
||||
d[triClipMinX] = float32(rec.Min.X)
|
||||
d[triClipMinY] = float32(rec.Min.Y)
|
||||
d[triClipMaxX] = float32(rec.Max.X)
|
||||
d[triClipMaxY] = float32(rec.Max.Y)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -128,25 +154,34 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
|
|||
if t, ok := t.(pixel.TrianglesPosition); ok {
|
||||
for i := 0; i < length; i++ {
|
||||
px, py := t.Position(i).XY()
|
||||
gt.data[i*stride+0] = float32(px)
|
||||
gt.data[i*stride+1] = float32(py)
|
||||
gt.data[i*stride+triPosX] = float32(px)
|
||||
gt.data[i*stride+triPosY] = float32(py)
|
||||
}
|
||||
}
|
||||
if t, ok := t.(pixel.TrianglesColor); ok {
|
||||
for i := 0; i < length; i++ {
|
||||
col := t.Color(i)
|
||||
gt.data[i*stride+2] = float32(col.R)
|
||||
gt.data[i*stride+3] = float32(col.G)
|
||||
gt.data[i*stride+4] = float32(col.B)
|
||||
gt.data[i*stride+5] = float32(col.A)
|
||||
gt.data[i*stride+triColorR] = float32(col.R)
|
||||
gt.data[i*stride+triColorG] = float32(col.G)
|
||||
gt.data[i*stride+triColorB] = float32(col.B)
|
||||
gt.data[i*stride+triColorA] = float32(col.A)
|
||||
}
|
||||
}
|
||||
if t, ok := t.(pixel.TrianglesPicture); ok {
|
||||
for i := 0; i < length; i++ {
|
||||
pic, intensity := t.Picture(i)
|
||||
gt.data[i*stride+6] = float32(pic.X)
|
||||
gt.data[i*stride+7] = float32(pic.Y)
|
||||
gt.data[i*stride+8] = float32(intensity)
|
||||
gt.data[i*stride+triPicX] = float32(pic.X)
|
||||
gt.data[i*stride+triPicY] = float32(pic.Y)
|
||||
gt.data[i*stride+triIntensity] = float32(intensity)
|
||||
}
|
||||
}
|
||||
if t, ok := t.(pixel.TrianglesClipped); ok {
|
||||
for i := 0; i < length; i++ {
|
||||
rect, _ := t.ClipRect(i)
|
||||
gt.data[i*stride+triClipMinX] = float32(rect.Min.X)
|
||||
gt.data[i*stride+triClipMinY] = float32(rect.Min.Y)
|
||||
gt.data[i*stride+triClipMaxX] = float32(rect.Max.X)
|
||||
gt.data[i*stride+triClipMaxY] = float32(rect.Max.Y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +195,12 @@ func (gt *GLTriangles) Update(t pixel.Triangles) {
|
|||
}
|
||||
gt.updateData(t)
|
||||
|
||||
// Copy the verteces down to the glhf.VertexData
|
||||
gt.CopyVertices()
|
||||
}
|
||||
|
||||
// CopyVertices copies the GLTriangle data down to the vertex data.
|
||||
func (gt *GLTriangles) CopyVertices() {
|
||||
// this code is supposed to copy the vertex data and CallNonBlock the update if
|
||||
// the data is small enough, otherwise it'll block and not copy the data
|
||||
if len(gt.data) < 256 { // arbitrary heurestic constant
|
||||
|
@ -185,19 +226,31 @@ func (gt *GLTriangles) Copy() pixel.Triangles {
|
|||
return NewGLTriangles(gt.shader, gt)
|
||||
}
|
||||
|
||||
// index is a helper function that returns the index in the data
|
||||
// slice given the i-th vertex and the item index.
|
||||
func (gt *GLTriangles) index(i, idx int) int {
|
||||
return i*gt.vs.Stride() + idx
|
||||
}
|
||||
|
||||
// Position returns the Position property of the i-th vertex.
|
||||
func (gt *GLTriangles) Position(i int) pixel.Vec {
|
||||
px := gt.data[i*gt.vs.Stride()+0]
|
||||
py := gt.data[i*gt.vs.Stride()+1]
|
||||
px := gt.data[gt.index(i, triPosX)]
|
||||
py := gt.data[gt.index(i, triPosY)]
|
||||
return pixel.V(float64(px), float64(py))
|
||||
}
|
||||
|
||||
// SetPosition sets the position property of the i-th vertex.
|
||||
func (gt *GLTriangles) SetPosition(i int, p pixel.Vec) {
|
||||
gt.data[gt.index(i, triPosX)] = float32(p.X)
|
||||
gt.data[gt.index(i, triPosY)] = float32(p.Y)
|
||||
}
|
||||
|
||||
// Color returns the Color property of the i-th vertex.
|
||||
func (gt *GLTriangles) Color(i int) pixel.RGBA {
|
||||
r := gt.data[i*gt.vs.Stride()+2]
|
||||
g := gt.data[i*gt.vs.Stride()+3]
|
||||
b := gt.data[i*gt.vs.Stride()+4]
|
||||
a := gt.data[i*gt.vs.Stride()+5]
|
||||
r := gt.data[gt.index(i, triColorR)]
|
||||
g := gt.data[gt.index(i, triColorG)]
|
||||
b := gt.data[gt.index(i, triColorB)]
|
||||
a := gt.data[gt.index(i, triColorA)]
|
||||
return pixel.RGBA{
|
||||
R: float64(r),
|
||||
G: float64(g),
|
||||
|
@ -206,10 +259,44 @@ func (gt *GLTriangles) Color(i int) pixel.RGBA {
|
|||
}
|
||||
}
|
||||
|
||||
// SetColor sets the color property of the i-th vertex.
|
||||
func (gt *GLTriangles) SetColor(i int, c pixel.RGBA) {
|
||||
gt.data[gt.index(i, triColorR)] = float32(c.R)
|
||||
gt.data[gt.index(i, triColorG)] = float32(c.G)
|
||||
gt.data[gt.index(i, triColorB)] = float32(c.B)
|
||||
gt.data[gt.index(i, triColorA)] = float32(c.A)
|
||||
}
|
||||
|
||||
// Picture returns the Picture property of the i-th vertex.
|
||||
func (gt *GLTriangles) Picture(i int) (pic pixel.Vec, intensity float64) {
|
||||
tx := gt.data[i*gt.vs.Stride()+6]
|
||||
ty := gt.data[i*gt.vs.Stride()+7]
|
||||
intensity = float64(gt.data[i*gt.vs.Stride()+8])
|
||||
tx := gt.data[gt.index(i, triPicX)]
|
||||
ty := gt.data[gt.index(i, triPicY)]
|
||||
intensity = float64(gt.data[gt.index(i, triIntensity)])
|
||||
return pixel.V(float64(tx), float64(ty)), intensity
|
||||
}
|
||||
|
||||
// SetPicture sets the picture property of the i-th vertex.
|
||||
func (gt *GLTriangles) SetPicture(i int, pic pixel.Vec, intensity float64) {
|
||||
gt.data[gt.index(i, triPicX)] = float32(pic.X)
|
||||
gt.data[gt.index(i, triPicY)] = float32(pic.Y)
|
||||
gt.data[gt.index(i, triIntensity)] = float32(intensity)
|
||||
}
|
||||
|
||||
// ClipRect returns the Clipping rectangle property of the i-th vertex.
|
||||
func (gt *GLTriangles) ClipRect(i int) (rect pixel.Rect, is bool) {
|
||||
mx := gt.data[gt.index(i, triClipMinX)]
|
||||
my := gt.data[gt.index(i, triClipMinY)]
|
||||
ax := gt.data[gt.index(i, triClipMaxX)]
|
||||
ay := gt.data[gt.index(i, triClipMaxY)]
|
||||
rect = pixel.R(float64(mx), float64(my), float64(ax), float64(ay))
|
||||
is = rect.Area() != 0.0
|
||||
return
|
||||
}
|
||||
|
||||
// SetClipRect sets the Clipping rectangle property of the i-th vertex.
|
||||
func (gt *GLTriangles) SetClipRect(i int, rect pixel.Rect) {
|
||||
gt.data[gt.index(i, triClipMinX)] = float32(rect.Min.X)
|
||||
gt.data[gt.index(i, triClipMinY)] = float32(rect.Min.Y)
|
||||
gt.data[gt.index(i, triClipMaxX)] = float32(rect.Max.X)
|
||||
gt.data[gt.index(i, triClipMaxY)] = float32(rect.Max.Y)
|
||||
}
|
||||
|
|