From ae338bd0fd14a4f9d15deec80212e8eef4979fb7 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Fri, 16 Aug 2019 17:25:55 +0200 Subject: [PATCH 01/29] Implement noise generator with a rendering test --- index.html | 11 ++++ modules/generator.js | 146 +++++++++++++++++++++++++++++++++++++++++++ style.css | 28 +++++++++ 3 files changed, 185 insertions(+) create mode 100644 index.html create mode 100644 modules/generator.js create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 00000000..0781e499 --- /dev/null +++ b/index.html @@ -0,0 +1,11 @@ + + + + + + + +
+
+
+ \ No newline at end of file diff --git a/modules/generator.js b/modules/generator.js new file mode 100644 index 00000000..9e269b4f --- /dev/null +++ b/modules/generator.js @@ -0,0 +1,146 @@ +// import PatternEditor from "./patternEditor.js"; +// import { perlin2, seed } from "./noise.js"; + +class NoiseLayer { + constructor(w, h) { + this.width = w; + this.height = h; + this.patternTree = JSON.parse('{"0":{"0":{"0":{"0":{"0":{"0":{"0":{"0":{"0":{"score":1}}},"1":{"1":{"1":{"score":1}}}}},"1":{"0":{"0":{"0":{"0":{"score":-1}}}}}},"1":{"1":{"1":{"1":{"1":{"1":{"score":-1}}}}}}},"1":{"0":{"0":{"1":{"0":{"0":{"1":{"score":1}}},"1":{"1":{"1":{"score":1}}}}}}}},"1":{"1":{"0":{"1":{"1":{"0":{"1":{"1":{"score":-1}}}}}}}}},"1":{"0":{"0":{"1":{"0":{"0":{"1":{"0":{"0":{"score":1}},"1":{"1":{"score":1}}}}}}}},"1":{"0":{"1":{"1":{"0":{"1":{"1":{"0":{"score":-1}}}}}}},"1":{"0":{"0":{"0":{"0":{"0":{"0":{"score":1}}}},"1":{"0":{"0":{"1":{"score":1}}}}}},"1":{"0":{"0":{"1":{"0":{"0":{"score":1}}}},"1":{"1":{"1":{"1":{"score":-1}}}}},"1":{"1":{"0":{"0":{"0":{"score":-1}}},"1":{"1":{"1":{"score":-1}}}}}}}}}}'); + + this.noiseOffset = 0; + + this.data = new Array(this.width * this.height); + this.accumulator = new Array(this.data.length); + + this.initWidthRandomValues(); + } + + doSimulationStep() { + for (let i = 0; i < this.accumulator.length; i++) { + this.accumulator[i] = 0; + } + + // unfortunately we hard-code size right now (It's just a prototype...) + const pw = 3; + for (let x = 0; x < this.width - (pw - 1); x++) + for (let y = 0; y < this.height - (pw - 1); y++) { + // find bad matches, find good matches + + // get neighborhood + let data = []; + for (let dy = 0; dy < pw; dy++) + for (let dx = 0; dx < pw; dx++) { + let index = this.width * (y + dy) + x + dx; + data.push(this.data[index]); + } + let score = this.getMatchScore(data); + if (score != 0) { + for (let dy = 0; dy < pw; dy++) + for (let dx = 0; dx < pw; dx++) { + let index = this.width * (y + dy) + x + dx; + this.accumulator[index] += score; + } + } + } + + for (let y = 0; y < this.height; y++) + for (let x = 0; x < this.width; x++) { + let index = y * this.width + x; + let mutate = this.accumulator[index] <= 0; + this.data[index] = mutate ? this.sampleNoise(x, y) : this.data[index]; + } + } + + getMatchScore(data) { + let node = this.patternTree; + for (let i = 0; i < data.length; i++) { + if (!node[data[i]]) { + return 0; + } + node = node[data[i]]; + } + + return node.score; + } + + initWidthRandomValues() { + for (let y = 0; y < this.height; y++) + for (let x = 0; x < this.width; x++) { + // seed(Date.now()); // TODO implement seed? + let index = y * this.width + x; + this.data[index] = this.sampleNoise(x, y); + } + } + + sampleNoise(x, y) { + return Math.random() < 0.5 + this.noiseOffset ? 1 : 0; + } +} + +// TODO separate logic for drawing and world gen! (and generally clean up this hacky code) + +export default class Generator { + constructor(size) { + let layers = [1, 1, 1, 1].map(f => new NoiseLayer(f * size, f * size)); + // simulate noise and overlay + for (let layer of layers) { + for (let i = 0; i < 100; i++) { + layer.doSimulationStep(); + } + } + + let heightmap = [] + let bottomLayer = layers[0].data; + + let canvi = []; + for (let i = 0; i < layers.length; i++) { + canvi.push(document.createElement("canvas")); + canvi[i].width = canvi[i].height = size; + } + let canvidats = canvi.map(c => c.getContext("2d").getImageData(0, 0, size, size)); + + for (let p of iterateQGrid(size, size)) { + let i = p.y * size + p.x; + //(p.y * sim.height / MAX_W) * sim.width + (p.x * sim.width / MAX_W) + let height = layers.map((sim) => (1 - sim.data[i])).reduce((a, b) => a + b); + height = layers[0].data[i] ? 0 : height; + + // dat.data[i*4] = + // dat.data[i*4 + 1] = + // dat.data[i*4 + 2] = val; + // dat.data[i*4 + 3] = 255; + + + for (let j = 0; j < height; j++) { + let val = Math.floor(j / layers.length * 155 + 100); + let dat = canvidats[j]; + dat.data[i * 4] = + dat.data[i * 4 + 1] = + dat.data[i * 4 + 2] = val; + dat.data[i * 4 + 3] = 255; + } + } + + for (let i = 0; i < layers.length; i++) { + canvi[i].getContext("2d").putImageData(canvidats[i], 0, 0); + canvi[i].style.transform = `translateZ(${i * 10}px)` + // canvi[i].style.transform = `scale3d(${scale},${scale},${scale}) rotateX(45deg) rotateZ(45deg) translate3d(${dx}px, ${dy}px, ${i*depthScale}px) `; + } + + let container = document.querySelector("#canvasStack"); + for (let canvas of canvi) { + container.append(canvas); + } + } +} + +function* iterateQGrid(w, h) { + for (let x = 0; x < w; x++) + for (let y = 0; y < h; y++) { + yield { x: x, y: y, index: y*w+x}; + } +} + +window.addEventListener("load", () => { + new Generator(64); +}) \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 00000000..b3033c58 --- /dev/null +++ b/style.css @@ -0,0 +1,28 @@ +canvas { + width: 300px; + height: 300px; + image-rendering: crisp-edges; + image-rendering: pixelated; +} + +canvas.game { + width: auto; + border: 1px solid black; +} + +#gameWorld { + background-color: #111; + margin: auto; + overflow: hidden; + width: 640px; + height: 360px; + perspective: 400px; +} + +#canvasStack { + /* transform: translate3d(0, 0, 0) rotateZ(12deg); */ +} + +#canvasStack canvas { + position: absolute; +} From 775830ba5419e1cfbe76ae5884f66109caa9dea0 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Sat, 17 Aug 2019 15:12:11 +0200 Subject: [PATCH 02/29] Implement 3d rendering demo with car overlay --- images/corolla.gal | Bin 0 -> 1233 bytes images/corolla.png | Bin 0 -> 1332 bytes images/corolla0000.png | Bin 0 -> 500 bytes images/corolla0001.png | Bin 0 -> 429 bytes images/corolla0002.png | Bin 0 -> 503 bytes images/tile_0000.png | Bin 0 -> 202 bytes images/tile_0001.png | Bin 0 -> 221 bytes images/tile_0002.png | Bin 0 -> 210 bytes images/tile_0003.png | Bin 0 -> 212 bytes images/tile_0004.png | Bin 0 -> 211 bytes images/tile_0005.png | Bin 0 -> 213 bytes images/tile_0006.png | Bin 0 -> 209 bytes images/tiles.gal | Bin 0 -> 796 bytes index.html | 7 ++-- modules/generator.js | 73 +++++++++++++++++------------------------ modules/renderer.js | 72 ++++++++++++++++++++++++++++++++++++++++ style.css | 27 ++++++--------- 17 files changed, 116 insertions(+), 63 deletions(-) create mode 100644 images/corolla.gal create mode 100644 images/corolla.png create mode 100644 images/corolla0000.png create mode 100644 images/corolla0001.png create mode 100644 images/corolla0002.png create mode 100644 images/tile_0000.png create mode 100644 images/tile_0001.png create mode 100644 images/tile_0002.png create mode 100644 images/tile_0003.png create mode 100644 images/tile_0004.png create mode 100644 images/tile_0005.png create mode 100644 images/tile_0006.png create mode 100644 images/tiles.gal create mode 100644 modules/renderer.js diff --git a/images/corolla.gal b/images/corolla.gal new file mode 100644 index 0000000000000000000000000000000000000000..e77f5ba12093c2588b0449ef8fac1cd08cea5b5d GIT binary patch literal 1233 zcmV;?1TOnWVQghsGB7YF0RRAa0qs)FPJ}QJzMJ?Clim$1%SueZMA+^5(Oapzj74F{=t0&)ld)yQ=vv5^X2{s$ zoKbEJ24Y5qe(TU%GP)nT!!1|1XLQHATDS5;)ENUvp<7dpMtl{eco&$&RlY5>(!E1X zk|bKJ&{F#FI`g^-S01@h@AR;dO66O}ba{G0lzQc~gZzy7GbB_!@9d0yhI%a&AF&cT zS8HjFwoYs`K>(!WBb*-%VdEF;o|^DYn^cNt1Of0$BOCnp(#j_VN#E2!LL5gIi0n$4 zhC{;&uKoE2u{(?2i5G<368?QVm?_g>(mI(ByyKri6xxiB|7px&{_!-@>6KVKlTXe^ z6(KeO004Laoscn3LqQA$OS;q)93euv15Ju3HAhIr2`JM1nkq$ZkRpmW3Tb%hi8%_pm(0cpwlR8AmI(sY$&sfXV@Wl-Ik8zHV5 zCaa=h$VRT{s?z6J0upJlF;?Bm>~l|ESV4;)%Ww4LY>~a{;ZGIJ^M`ZTf7heS2aX}X zg2k~D000004gmlFcmbV~Ee^s!5QQZUjo=6k;SLxCL30EYCqQ7TQHb6EK?sLIAUF&k zdC6olvt3$}m(BdV_jY#LcGb20I37;I<=h2Wt0J$rUhk(oZ}%%8RiN0q1a+Tc*W`WX zs%wC;R}3`&`wIEjCs&tKE0~bs(Kd_5V>X=!_%1OLgvi4-LaN7VPHjV#U``6h7HIFH5)4C% zCmab)rN9pyT3!_lLkiQhn%|#l`HEsM35GcpnPmc-Iq%0>VoH_WLM8i*tpvlI@+X)l zF%U;_PCYrh6)VFfTxaB>)dw;Aua5u#002S(004Lam60KC!$1&4VOelNk6=~3LrFmt z+#?v}1O+9*MFqY=KosRL1pzrspY)`!k?nOHX*Bwtoq6+jeRv-Z?fBY%|NaaMLBqm$ z>xik-`3RPLz5S6};c1IATL*AuAY}HC&6`Yljm$%?M(FZ$mOu z+qd4RSn5D3CnNGbCDZx2>l2t)Qzz;aIxE5trzI)@xSzta6fwGj&yv?}!6ke=T?hzF z5zm{K8W!z|e~XOgj(V$$u&ObelPesZy22Hj@zgD3KFKU{3&MA(9?(-hMF!=BPcf?< z;S)jKe*F7@2#d)Xq~ag(`sncMACZg!gf{FCP)N-lIpR7^<`;|v>lkqnomoT?$dn_k vW=4RFu=(T&&v5FR|DNig)WpMX#0UD!FmRh7>tY=c#y5u2H2}enRpF2>-7|3SuYS5GiQY`6? zzK#qG8~eHcB(gFvFf#=Bgt*G^NzCk@n&B65<<5-^10NkdofYfWH00H6VD|uN9R;Hy zFsws>T{6=G=t0ILZ+DmfOW8ji1+qIlT^vIy=9Gp+Zab>L5uEbkU-%y;t)tQLc2zP? zN(*wQY?x=T>p+g7!H#$FkE8oaUL3oyPQLfzZnpFU6T4R&E1Be(rQerH^vJg_TK}tX zL!m^&mc!E>?Y}B6{I&nzbwwt>_IYe)Y)(&~9d|UZNGhZ@fRC9`)-lsSnlaHlqV}+6 z*vdV}9)5e~8Mg0vO8E6;8?$Lw*Sl~WK9Kv&#Bi!1vfPh^)_TOIcM=kD=S-~t5mYHEnXM;X!y)jXW|Ndm# zmwlRUA=L)qS~s|L)djhG+OO?Xu;tYgNtb1-5{=Tg47g_em*;y-<<#V&`XU#}?gQ7> o)L*WO*{Lh$`pJwD(1}Fp$iP(@PXp$Ah6SAVBf)E}7%_V2Z z*&KH7Ft@PDm)V`UbI<*mX_J|y2$%DP-CSLVfNjlRNc8T$v2L?Aj5TP705FzN&HG7s%845?}g_pah22c)gu(v;gYY33`3@{?btXcM*>G9MShwlkQp4I1T>h0e? z^4!zIf_Nn2)C{jIJEZ$p3Y qBf?q+k8Olm{(t{FMHsfFX8#59V~FWG;a8{t0000C&jW;TI)s6;8$WMPLBH4AmfT!8hR79=) zYC-@@$`99sgqTM^o+L~>d4z#QvC>eX%hAcP^`N3+%fSm5#d=glRUhV*^ysi(V!qU! zBWA&2wxN2#10H7ij_w5}W<6`zV(m}(um#*`*vlBQVDa+i#?&7w81EV4e5L~56N1cK`4Ujy8BBIU{sPY1e{D6vrE@g^Sgp?ZT3erc>6%;8D;t8oS ztfe_k_H5r7ERAgWmYMa;`4H!vr11JObIbXn6STIC4~UAre-xff=jwOs=Qg13B4HH; zL9Q@r6>X<)Y=8iw_B1)-%F#)9Fd8^yYP0Pk9Ls(B_*A17hP9U15}<~yoKBwqL4Sv} zK99Ya4dEPEGHw>4wlO|+ZLiHI1rV)n#~w=nVPjtb53g-ve058Dc+nVzyq_URW2f zgYCkaGvjN#)5IPBS-nQdwaRQr%i0P}1)4%`#002ovPDHLkV1f?)&7J@N literal 0 HcmV?d00001 diff --git a/images/tile_0000.png b/images/tile_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..252dbce29e24f329c8abed00a426cc159eea43a4 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{V>?NMQuI!IF`1v#>3U)NV1qz9m zxJHx&=ckpFCl;kLxcj&OjZr8|EmAPnGbwCc@(`$mqolyk9VlZAWHWd*XvzaAmUKs7 zM+SzC{oH>NS%G}EByV>YhW{YAVDIwDKoM0>7srr_Ims54pPtk^&2S73{%o)4lj_

1|%O$WD@{V>?NMQuI!IF`1v#>3U)NV1qz9m zxJHx&=ckpFCl;kLxcj&OjZr8|EmAPnGbwCc@(`$mqolyk9VlZAWHWd*XvzaAmUKs7 zM+SzC{oH>NS%G}EByV>YhW{YAVDIwDKoJX17srr_Ims54pPtk^&2S73{%mig<|Sx$ z)IwBIn?<|o&kw_}qY=J`md#jDY9YFCHd`2r630vyhAUTBzi1|%O$WD@{V>?NMQuI!IF`1v#>3U)NV1qz9m zxJHx&=ckpFCl;kLxcj&OjZr8|EmAPnGbwCc@(`$mqolyk9VlZAWHWd*XvzaAmUKs7 zM+SzC{oH>NS%G}EByV>YhW{YAVDIwDKoMO}7srr_Ims54pPtk^&2S73{%o)4lj_

BtMGk9+8KcRcs+aIWr!PC{xWt~$(697Q&J5&Gw literal 0 HcmV?d00001 diff --git a/images/tile_0003.png b/images/tile_0003.png new file mode 100644 index 0000000000000000000000000000000000000000..a27b0c36f1722153242ae0f0da1d02c01668380a GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{V>?NMQuI!IF`1v#>3U)NV1qz9m zxJHx&=ckpFCl;kLxcj&OjZr8|EmAPnGbwCc@(`$mqolyk9VlZAWHWd*XvzaAmUKs7 zM+SzC{oH>NS%G}EByV>YhW{YAVDIwDKoNaU7srr_Ims54pPtnF`ufh1P&wbA_v6RM vWFA8v#+(@yt~b^=h%Mh#%2Mm@W5LLfThn~~K+}vQpjHM?S3j3^P61|%O$WD@{V>?NMQuI!IF`1v#>3U)NV1qz9m zxJHx&=ckpFCl;kLxcj&OjZr8|EmAPnGbwCc@(`$mqolyk9VlZAWHWd*XvzaAmUKs7 zM+SzC{oH>NS%G}EByV>YhW{YAVDIwDKoLDp7srr_Ims54pPtk^&2S73{%o)3Gev2J v(<4#ax<3-@Bxd^V@)Xf*`rS99fQdoi#G08~Z!6aWbuxIm`njxgN@xNA$EZFk literal 0 HcmV?d00001 diff --git a/images/tile_0005.png b/images/tile_0005.png new file mode 100644 index 0000000000000000000000000000000000000000..7d266389a71dc95599139b052c9b102d27607df0 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{V>?NMQuI!IF`1v#>3U)NV1qz9m zxJHx&=ckpFCl;kLxcj&OjZr8|EmAPnGbwCc@(`$mqolyk9VlZAWHWd*XvzaAmUKs7 zM+SzC{oH>NS%G}EByV>YhW{YAVDIwDKoJ8^7srr_Ims1&ei$-y%(tohWY4j9v*V0L v4Oat3UteDyp2^C7f@eH(x-v3kj{?o|_;!K$&gHrcpk4+~S3j3^P61|%O$WD@{V>?NMQuI!IF`1v#>3U)NV1qz9m zxJHx&=ckpFCl;kLxcj&OjZr8|EmAPnGbwCc@(`$mqolyk9VlZAWHWd*XvzaAmUKs7 zM+SzC{oH>NS%G}EByV>YhW{YAVDIwDKoK2J7srr_ImrR%4gULc7(bY7^7gCJ8jiy% tGxQd%b-wN}tCU4Fv6z38M4%cY!}?Vdwk$l!w*sh-!PC{xWt~$(697GzJf;8u literal 0 HcmV?d00001 diff --git a/images/tiles.gal b/images/tiles.gal new file mode 100644 index 0000000000000000000000000000000000000000..4eab57aa5a988c74a9e9a40999da683a63fe15a3 GIT binary patch literal 796 zcmZ=y%t?(fGB7yG$iPs+_;!|K_8|k2<_w-%Gho2pr z-4*Zix!>m9bmqD9Ij35$%wtdAEcMxbe&eUg>6=sMyem<%KYH%@7Uva{b3)S|RSR_T zJC-m9^zpcV=MUSe^{a2(H_3`@0 z+zPkp+vl)9E=ZY`eB1f_mU-3^R`33%SiJqJsiY^sz$n1bxLW1G)?U`0iED(`ct)sv zd*VLntk-12m*u+LI|Ea+icaTSTh8$fDeLfbtKGEVt^LI2-cM_8zNy=yp(KCnzGF&VU3@o#p7?PG;zwxT|=kt#Y`8~dx@lQCJ6d0Jk3O7h7 z9{aVL%eskyqrst>)1gNox%ssC%4QJvFN=a($HsrF4)p<*DqKil5>P&J_+r9!zW75UCJkP$(dNjP2xt%?D34vdpqzFq_Qq6J!MdW<5J( literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 0781e499..bbac3f83 100644 --- a/index.html +++ b/index.html @@ -2,10 +2,9 @@ + - -

-
-
+ + \ No newline at end of file diff --git a/modules/generator.js b/modules/generator.js index 9e269b4f..a6690bc2 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -1,5 +1,4 @@ -// import PatternEditor from "./patternEditor.js"; -// import { perlin2, seed } from "./noise.js"; +import Renderer from "./renderer.js"; class NoiseLayer { constructor(w, h) { @@ -81,6 +80,8 @@ class NoiseLayer { export default class Generator { constructor(size) { + this.size = size; + let layers = [1, 1, 1, 1].map(f => new NoiseLayer(f * size, f * size)); // simulate noise and overlay for (let layer of layers) { @@ -89,48 +90,25 @@ export default class Generator { } } - let heightmap = [] - let bottomLayer = layers[0].data; - - let canvi = []; - for (let i = 0; i < layers.length; i++) { - canvi.push(document.createElement("canvas")); - canvi[i].width = canvi[i].height = size; - } - let canvidats = canvi.map(c => c.getContext("2d").getImageData(0, 0, size, size)); + this.floorMap = layers[0].data; + this.heightmap = [] for (let p of iterateQGrid(size, size)) { - let i = p.y * size + p.x; - //(p.y * sim.height / MAX_W) * sim.width + (p.x * sim.width / MAX_W) - let height = layers.map((sim) => (1 - sim.data[i])).reduce((a, b) => a + b); - height = layers[0].data[i] ? 0 : height; - - // dat.data[i*4] = - // dat.data[i*4 + 1] = - // dat.data[i*4 + 2] = val; - // dat.data[i*4 + 3] = 255; - - - for (let j = 0; j < height; j++) { - let val = Math.floor(j / layers.length * 155 + 100); - let dat = canvidats[j]; - dat.data[i * 4] = - dat.data[i * 4 + 1] = - dat.data[i * 4 + 2] = val; - dat.data[i * 4 + 3] = 255; - } - } - - for (let i = 0; i < layers.length; i++) { - canvi[i].getContext("2d").putImageData(canvidats[i], 0, 0); - canvi[i].style.transform = `translateZ(${i * 10}px)` - // canvi[i].style.transform = `scale3d(${scale},${scale},${scale}) rotateX(45deg) rotateZ(45deg) translate3d(${dx}px, ${dy}px, ${i*depthScale}px) `; + let height = layers.map((sim) => (1 - sim.data[p.index])).reduce((a, b) => a + b); + height = layers[0].data[p.index] ? 0 : height; + this.heightmap[p.index] = height; } + } - let container = document.querySelector("#canvasStack"); - for (let canvas of canvi) { - container.append(canvas); - } + sampleHeightmap(x, y) { + // return 0; + // return Math.random() < 0.05? .5 : 0; + return this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)]; + } + + sampleFloorMap(x, y) { + return this.floorMap[(mod(y, this.size) * this.size) + mod(x, this.size)] + + Math.round(x - Math.floor(x)); } } @@ -142,5 +120,16 @@ function* iterateQGrid(w, h) { } window.addEventListener("load", () => { - new Generator(64); -}) \ No newline at end of file + let gen = new Generator(64); + let rend = new Renderer(gen, document.querySelector("canvas.game")); + window.setInterval(() => { + rend.x+=0.05; + rend.angle += 0.01; + // rend.z += 0.01; + rend.render(); + }, 1000/30) +}) + +function mod(x, n) { + return((Math.floor(x)%n)+n)%n; +} \ No newline at end of file diff --git a/modules/renderer.js b/modules/renderer.js new file mode 100644 index 00000000..ce5eaae6 --- /dev/null +++ b/modules/renderer.js @@ -0,0 +1,72 @@ +const COLOR_TABLE = [ + 0x777777, + 0x121212, + 0x126612, +].map(val => [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff]); + +export default class Renderer { + constructor(generator, canvas) { + this.generator = generator; + this.canvas = canvas; + this.x = 0; + this.y = 0.5; + this.z = 0.25; + this.angle = 0; + + this.imgCorolla = document.querySelector("img#corolla"); + } + + render() { + let t0 = Date.now(); + let w = this.canvas.width; + let h = this.canvas.height; + let d = 128; + let farPlane = 16; // units + // let nearPlane = .1; // units + let fovRatio = 1; + + let ctx = this.canvas.getContext("2d"); + ctx.clearRect(0, 0, w, h); + let imgData = ctx.getImageData(0, 0, w, h); + + + // iterate from z to 1 + for(let screenZ = d; screenZ>=0; screenZ--) { + // iterate from left to right + for(let screenX = 0; screenX < w; screenX++) { + // TODO implement rotation + let val = (screenZ/d) * (screenZ/d) * farPlane + let rx = val; + let ry = val * (screenX/w * 2 - 1); + let x = this.x + Math.cos(this.angle) * rx - Math.sin(this.angle) * ry; + let y = this.y + Math.sin(this.angle) * rx + Math.cos(this.angle) * ry; + let sample = this.generator.sampleHeightmap(x, y); + let screenHeight = (sample - this.z + val) / (val*2) * h; + screenHeight = Math.min(Math.floor(screenHeight), h); + screenHeight += 32; + // screenHeight = 16 + val; + // render a vertical scanline + let col = sample > 0? + [1, 1, 1].map(v => (1-screenZ/d)*255) + : + COLOR_TABLE[this.generator.sampleFloorMap(x, y)] + ; + + for(let screenY = 0; screenY < screenHeight; screenY++) { + let i = (h-screenY) * w + screenX; + imgData.data[i*4] = col[0] + imgData.data[i*4+1] = col[1] + imgData.data[i*4+2] = col[2]; + imgData.data[i*4+3] = 255; + } + } + } + + ctx.putImageData(imgData, 0, 0); + + ctx.drawImage(this.imgCorolla, w/2 - this.imgCorolla.width/2, h*0.8 - this.imgCorolla.height/2); + + let t = (Date.now() - t0); + // console.log(`render time: ${t}ms, ${1000/t}fps`); + } +} \ No newline at end of file diff --git a/style.css b/style.css index b3033c58..1ccb8a7e 100644 --- a/style.css +++ b/style.css @@ -1,28 +1,21 @@ canvas { - width: 300px; - height: 300px; image-rendering: crisp-edges; image-rendering: pixelated; } canvas.game { - width: auto; - border: 1px solid black; + width: 100%; + height: 100vh; + padding:0; + margin: 0; + background-color: darkcyan; } -#gameWorld { - background-color: #111; - margin: auto; - overflow: hidden; - width: 640px; - height: 360px; - perspective: 400px; +body { + padding: 0; + margin: 0; } -#canvasStack { - /* transform: translate3d(0, 0, 0) rotateZ(12deg); */ -} - -#canvasStack canvas { - position: absolute; +img.sprite { + display: none; } From 40aafb8775a033045ec6d740f227a268e1c2bf3f Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Sun, 18 Aug 2019 11:59:12 +0200 Subject: [PATCH 03/29] Improve performance of renderer --- images/tiles.gal | Bin 796 -> 894 bytes images/{tile_0000.png => tiles0000.png} | Bin 202 -> 202 bytes images/{tile_0001.png => tiles0001.png} | Bin 221 -> 221 bytes images/{tile_0002.png => tiles0002.png} | Bin 210 -> 210 bytes images/{tile_0003.png => tiles0003.png} | Bin 212 -> 212 bytes images/{tile_0004.png => tiles0004.png} | Bin 211 -> 211 bytes images/{tile_0005.png => tiles0005.png} | Bin 213 -> 213 bytes images/{tile_0006.png => tiles0006.png} | Bin 209 -> 209 bytes images/tiles0007.png | Bin 0 -> 200 bytes images/tiles0008.png | Bin 0 -> 191 bytes index.html | 10 ++- modules/game.js | 72 +++++++++++++++ modules/generator.js | 19 ++-- modules/keyHandler.js | 27 ++++++ modules/renderer.js | 111 ++++++++++++++++-------- style.css | 4 +- 16 files changed, 190 insertions(+), 53 deletions(-) rename images/{tile_0000.png => tiles0000.png} (68%) rename images/{tile_0001.png => tiles0001.png} (71%) rename images/{tile_0002.png => tiles0002.png} (69%) rename images/{tile_0003.png => tiles0003.png} (69%) rename images/{tile_0004.png => tiles0004.png} (69%) rename images/{tile_0005.png => tiles0005.png} (69%) rename images/{tile_0006.png => tiles0006.png} (69%) create mode 100644 images/tiles0007.png create mode 100644 images/tiles0008.png create mode 100644 modules/game.js create mode 100644 modules/keyHandler.js diff --git a/images/tiles.gal b/images/tiles.gal index 4eab57aa5a988c74a9e9a40999da683a63fe15a3..50d4c969ad11ea2cb73d7e9c42352fc25e0ea784 100644 GIT binary patch delta 592 zcmbQk_K&UJJuxRW!pOkjAtM7r1>@VXnBFEo*f0Lc1CMPbsJcCKC>9TA`^qj)< zD^@4DR2O*PSbjhIV(#*}7HgjIvu7@SzxUs#N%Aru(p=|sOJxr1-9&WHg}Kg|`dp+u|2A?DdM{4<=2|~jfq_Zk zf(>JbU{cP$)dGSn4h$?hY7B->J!0GSz1V@e8lQ1G^avz>j&1D#sZB5hsok=pHqZ*> zP!oTKq!x+U@3mZ5KsI!P)Gj{yw)>JJ1B=6gN~ZrlJ#l|slRfAA)i8E-im)m%fZ{nh z!GXa|f`RMc<_C;6jBaue!IY#H2R1e~H8+j~rcDcd+}ym5PCUcTAi9Jh38;WU0;;No X38YA3fs%BZjq(K_2KhG(6G8F-Z4>sp delta 478 zcmV<40U`eW2Al?eM`3JbSTZm$#Q^{ScmeI2L2JS=6vyv_eTNY6IMh0=LMzxh-J!VQ z#GR|g8J0Gs)j{{^_mb+i(@s6C@^20%vB`V+@!;1&y;&Y7qF`&07gDD_cRC%;Ughq1 z@cD?dM7MU(g&Q7%x?G?o(o&ItzTJhKMZFokP zx10y#aAcZ)W7|W|w4OT{I_~{sIPLW*A}$aB0T2Lzt0dsHjRlO6tP-qASS0r3N0QA- zlQHRcEep9(WGZsYZ#y}hO;mP>Nk)CLpzS}AgN^d6+3bF_C?X%)>GnC6*RG^h$*^s0 zSoI;st1L7e3Isp^1e!tsVx-sD>V^5|@c?g(O(|bgG00Q+BfEXdh`l|{%f&d7B zK!XWDj1Xgk%NMDG00@9U{RALJh_U{v!j2#S0wB;}0uUp_*x>R->L36DAW%PneG0sx0d+&E|0~rU3vsF9xds diff --git a/images/tile_0001.png b/images/tiles0001.png similarity index 71% rename from images/tile_0001.png rename to images/tiles0001.png index e1263a8740465bfe506b990f8ccc8e15c0801644..1c77963b6ac98107d87eb897405c336c3b5bcf75 100644 GIT binary patch delta 18 Zcmcc1c$aa44u>G0sx0d+&E|0~RssM!$Ohy9 diff --git a/images/tile_0002.png b/images/tiles0002.png similarity index 69% rename from images/tile_0002.png rename to images/tiles0002.png index 46d6807181c14ceb47d332976fdb50f5de79b70c..ee96aa2b61993e015d18a69d4eec9d0e9ee26c31 100644 GIT binary patch delta 18 Zcmcb_c!_a>4u>G0sx0d+&E|4hKJ0~<^cdYM+UG0sx0d+&E|0~761S`jRwg8 diff --git a/images/tile_0004.png b/images/tiles0004.png similarity index 69% rename from images/tile_0004.png rename to images/tiles0004.png index 31905f2dac870931d74f375169b014c4cbf0e8eb..d86617b1133865fe490cc84937febd20875f8eba 100644 GIT binary patch delta 18 Zcmcc2c$sm64u>G0sx0d+&E|0~<^upaY6ivt diff --git a/images/tile_0005.png b/images/tiles0005.png similarity index 69% rename from images/tile_0005.png rename to images/tiles0005.png index 7d266389a71dc95599139b052c9b102d27607df0..81751595d9449cbdafb0585dd0bd2acbb80f647b 100644 GIT binary patch delta 18 Zcmcc0c$IO24u>G0sx0d+&E|0~76Je|um;Qk diff --git a/images/tile_0006.png b/images/tiles0006.png similarity index 69% rename from images/tile_0006.png rename to images/tiles0006.png index fb1de8e9c2c03c0602617197e209ac37d4dae222..7dcf4258326a12ac8883c5f7610efa08c9c01352 100644 GIT binary patch delta 18 Zcmcb}c#&~}4u>G0sx0d+&E|0~<^ljZBnH3$ diff --git a/images/tiles0007.png b/images/tiles0007.png new file mode 100644 index 0000000000000000000000000000000000000000..5a451822c7faa46386c228fa13df204c583ecdb6 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{V>?NMQuI!IF1o>2DS$AnR1BJv( zTq85rEMQ`I%G1@YvbS{uP&tFAtDnm{r-UW|+aWri literal 0 HcmV?d00001 diff --git a/images/tiles0008.png b/images/tiles0008.png new file mode 100644 index 0000000000000000000000000000000000000000..0f4a6610d38aecdb00e431ace42e4262c6c48322 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{V>?NMQuI!IF1o>2DS$AnR1BJv( zTq8 + - - - \ No newline at end of file + + + + + +
\ No newline at end of file diff --git a/modules/game.js b/modules/game.js new file mode 100644 index 00000000..72186c85 --- /dev/null +++ b/modules/game.js @@ -0,0 +1,72 @@ +import Generator from "./generator.js"; +import Renderer from "./renderer.js"; +import { keyIsDown } from "./keyHandler.js"; + +const ACC = .25 / 30; +const STEER = 2 / 30; +const TRACT = 0.1; +const FRIC = 0.1; + +class Car { + constructor(renderer, generator) { + this.x = 0; + this.y = 0; + this.hspd = 0; + this.vspd = 0; + this.angle = 0; + + this.renderer = renderer; + this.generator = generator; + } + + update() { + let acceleration = (keyIsDown("up") - keyIsDown("down")) * ACC; + let steering = (keyIsDown("right") - keyIsDown("left")) + this.renderer.carFrame = -steering+1; + steering *= STEER; + + this.angle += steering; + this.hspd += Math.cos(this.angle) * acceleration; + this.vspd += Math.sin(this.angle) * acceleration; + this.x += this.hspd; + this.y += this.vspd; + + // friction + + // sideways + let dx = -Math.sin(this.angle); + let dy = Math.cos(this.angle); + let scl = (dx * this.hspd + dy * this.vspd) * TRACT; + this.hspd -= scl * dx; + this.vspd -= scl * dy; + + // forw. backw. + dx = Math.cos(this.angle); + dy = Math.sin(this.angle); + scl = (dx * this.hspd + dy * this.vspd) * FRIC; + this.hspd -= scl * dx; + this.vspd -= scl * dy; + + + // render + this.renderer.x = this.x; + this.renderer.y = this.y; + this.renderer.z = 0.35 + this.generator.sampleHeightmap(this.x, this.y); + this.renderer.angle = this.angle; + this.renderer.render(); + } +} + +window.addEventListener("load", () => { + startGame(); +}); + +async function startGame() { + let gen = new Generator(64); + let rend = new Renderer(gen, document.querySelector("canvas.game")); + await rend.load(); + let car = new Car(rend, gen); + window.setInterval(() => { + car.update(); + }, 1000/30) +} \ No newline at end of file diff --git a/modules/generator.js b/modules/generator.js index a6690bc2..f01ceef8 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -76,8 +76,6 @@ class NoiseLayer { } } -// TODO separate logic for drawing and world gen! (and generally clean up this hacky code) - export default class Generator { constructor(size) { this.size = size; @@ -93,6 +91,7 @@ export default class Generator { this.floorMap = layers[0].data; this.heightmap = [] + // calculate height map for (let p of iterateQGrid(size, size)) { let height = layers.map((sim) => (1 - sim.data[p.index])).reduce((a, b) => a + b); height = layers[0].data[p.index] ? 0 : height; @@ -103,7 +102,10 @@ export default class Generator { sampleHeightmap(x, y) { // return 0; // return Math.random() < 0.05? .5 : 0; - return this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)]; + if(x >= this.size || y >= this.size || x < 0 || y < 0) return 0; + return this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)] + + (Math.sin(y*3.141569*0.25) + Math.sin(x*3.141569*0.25) - 2) * 0.2 + ; } sampleFloorMap(x, y) { @@ -119,17 +121,6 @@ function* iterateQGrid(w, h) { } } -window.addEventListener("load", () => { - let gen = new Generator(64); - let rend = new Renderer(gen, document.querySelector("canvas.game")); - window.setInterval(() => { - rend.x+=0.05; - rend.angle += 0.01; - // rend.z += 0.01; - rend.render(); - }, 1000/30) -}) - function mod(x, n) { return((Math.floor(x)%n)+n)%n; } \ No newline at end of file diff --git a/modules/keyHandler.js b/modules/keyHandler.js new file mode 100644 index 00000000..86d80cde --- /dev/null +++ b/modules/keyHandler.js @@ -0,0 +1,27 @@ +var keys = {}; + +registerKey("left", [65, 37]); +registerKey("right", [68, 39]); +registerKey("up", [87, 38]); +registerKey("down",[83, 40]); + + +export function registerKey(key, keyCodes) { + keys[key] = false; + document.addEventListener("keydown", (ev) => { + if(keyCodes.includes(ev.keyCode)) { + ev.preventDefault(); + keys[key] = true; + } + }); + document.addEventListener("keyup", (ev) => { + if(keyCodes.includes(ev.keyCode)) { + ev.preventDefault(); + keys[key] = false; + } + }); +} + +export function keyIsDown(key) { + return !!keys[key]; +} \ No newline at end of file diff --git a/modules/renderer.js b/modules/renderer.js index ce5eaae6..a10af3dc 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -10,63 +10,106 @@ export default class Renderer { this.canvas = canvas; this.x = 0; this.y = 0.5; - this.z = 0.25; + this.z = 0.35; this.angle = 0; + this.horizon = 0.2; - this.imgCorolla = document.querySelector("img#corolla"); + this.carFrame = 1; + } + + async load() { + // TODO maybe put this in a separate async function and change game logic to only start after load + this.imgCorolla = await this.loadImages("corolla", 3); + this.imgTiles = await this.loadImages("tiles", 9); + // load tile data + this.tileData = []; + let canvas = document.createElement('canvas'); + canvas.width = this.imgTiles[0].width; + canvas.height = this.imgTiles[0].height; + let ctx = canvas.getContext('2d'); + for (let tile of this.imgTiles) { + ctx.drawImage(tile, 0, 0); + this.tileData.push(ctx.getImageData(0, 0, tile.width, tile.height).data); + } + } + + async loadImages(name, numberOfFrames, format = "png") { + let images = []; + let promises = []; + for (let i = 0; i < numberOfFrames; i++) { + let img = document.createElement("img"); + img.src = `images/${name}${("000" + i).slice(-4)}.${format}`; + promises.push(new Promise(resolve => { img.onload = resolve })); + images.push(img); + } + + for (let promise of promises) { + await promise; + } + return images; } render() { let t0 = Date.now(); - let w = this.canvas.width; - let h = this.canvas.height; - let d = 128; - let farPlane = 16; // units + const w = this.canvas.width; + const h = this.canvas.height; + const d = 180; + const farPlane = 1; // not actually a far plane anymore bec. of tangent bias // let nearPlane = .1; // units - let fovRatio = 1; + // let fovRatio = 1; let ctx = this.canvas.getContext("2d"); ctx.clearRect(0, 0, w, h); let imgData = ctx.getImageData(0, 0, w, h); - + const cosAngle = Math.cos(this.angle); + const sinAngle = Math.sin(this.angle); + + let depthBuffer = new Array(w).fill(0); // iterate from z to 1 - for(let screenZ = d; screenZ>=0; screenZ--) { + // for (let screenZ = d; screenZ >= 0; screenZ--) { + for (let screenZ = 0; screenZ <= d; screenZ++) { // iterate from left to right - for(let screenX = 0; screenX < w; screenX++) { - // TODO implement rotation - let val = (screenZ/d) * (screenZ/d) * farPlane - let rx = val; - let ry = val * (screenX/w * 2 - 1); - let x = this.x + Math.cos(this.angle) * rx - Math.sin(this.angle) * ry; - let y = this.y + Math.sin(this.angle) * rx + Math.cos(this.angle) * ry; - let sample = this.generator.sampleHeightmap(x, y); - let screenHeight = (sample - this.z + val) / (val*2) * h; - screenHeight = Math.min(Math.floor(screenHeight), h); - screenHeight += 32; - // screenHeight = 16 + val; + const relDistance = Math.tan(screenZ / d * 1.57) * farPlane + const rx = relDistance; + const lum = (1 - screenZ / d) * 255; + for (let screenX = 0; screenX < w; screenX++) { + const ry = relDistance * (screenX / w * 2 - 1); + const x = this.x + cosAngle * rx - sinAngle * ry; + const y = this.y + sinAngle * rx + cosAngle * ry; + const sample = this.generator.sampleHeightmap(x, y); + // ~~ ist essentially Math.floor + const screenHeight = Math.min(~~((sample - this.z + relDistance) / (relDistance * 2) * h + this.horizon * h), h); // render a vertical scanline - let col = sample > 0? - [1, 1, 1].map(v => (1-screenZ/d)*255) - : - COLOR_TABLE[this.generator.sampleFloorMap(x, y)] + const col = sample > 0 ? + [lum, lum, lum] + : + COLOR_TABLE[this.generator.sampleFloorMap(x, y)]; ; - - for(let screenY = 0; screenY < screenHeight; screenY++) { - let i = (h-screenY) * w + screenX; - imgData.data[i*4] = col[0] - imgData.data[i*4+1] = col[1] - imgData.data[i*4+2] = col[2]; - imgData.data[i*4+3] = 255; + const buf = depthBuffer[screenX]; + for (let screenY = buf; screenY < screenHeight; screenY++) { + let i = ((h - screenY) * w + screenX) * 4; + imgData.data[i] = col[0] + imgData.data[i + 1] = col[1] + imgData.data[i + 2] = col[2]; + imgData.data[i + 3] = 255; } + if (screenHeight > buf) depthBuffer[screenX] = screenHeight; } } ctx.putImageData(imgData, 0, 0); - - ctx.drawImage(this.imgCorolla, w/2 - this.imgCorolla.width/2, h*0.8 - this.imgCorolla.height/2); + if (this.imgCorolla) { + let image = this.imgCorolla[this.carFrame]; + ctx.drawImage(image, ~~(w / 2 - image.width / 2), ~~(h * 0.8 - image.height / 2)); + } let t = (Date.now() - t0); // console.log(`render time: ${t}ms, ${1000/t}fps`); + document.querySelector("footer").innerHTML = `render time: ${t}ms, \t${~~(1000 / t)}fps`; + } + + sampleFloor() { + } } \ No newline at end of file diff --git a/style.css b/style.css index 1ccb8a7e..29c3955f 100644 --- a/style.css +++ b/style.css @@ -5,10 +5,10 @@ canvas { canvas.game { width: 100%; - height: 100vh; + height: auto; padding:0; margin: 0; - background-color: darkcyan; + background-color: black; } body { From cd2eb114c2b900c61747a2a2ad56975591f0d1c3 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Sun, 18 Aug 2019 14:40:19 +0200 Subject: [PATCH 04/29] Sample color from floor texture --- images/tiles.gal | Bin 894 -> 1204 bytes images/tiles0000.png | Bin 202 -> 243 bytes images/tiles0001.png | Bin 221 -> 270 bytes images/tiles0002.png | Bin 210 -> 275 bytes images/tiles0003.png | Bin 212 -> 304 bytes images/tiles0004.png | Bin 211 -> 259 bytes images/tiles0005.png | Bin 213 -> 265 bytes images/tiles0006.png | Bin 209 -> 225 bytes images/tiles0007.png | Bin 200 -> 304 bytes images/tiles0008.png | Bin 191 -> 218 bytes index.html | 7 +++--- modules/game.js | 2 +- modules/renderer.js | 56 ++++++++++++++++++++++++++++++++----------- style.css | 2 +- 14 files changed, 47 insertions(+), 20 deletions(-) diff --git a/images/tiles.gal b/images/tiles.gal index 50d4c969ad11ea2cb73d7e9c42352fc25e0ea784..45fb174188cebf2f1e885e623c0e55c046551bd9 100644 GIT binary patch literal 1204 zcmZ=y%t?(fGBDs|VqmCXd^_9U|B!)1`+K#z_GO*!%sKD)Pr4b+ILz$0*yTL)DxJR4 zn+I6z_rAaFd&Z{1=weI!eU@8!Z*SY({I-GFF=%OYXt#Ul)tP!unO}bg*`&U{t2Tw% zze{@R-kdVu+L&nzzbu{cqSi6iSyPVh=qtUmFE@yM&)j(J_M6zX&Ys0SU$(`_c$May zxiWi^@yiPpRu^}!%b4=Ia-M0y3hnEwubb)4KX~^1l`HdJ$4+|sv*OfDx990jENKsu z{8sZnJ!<@Z(T)7+t9DID3S8=XciE05Q;%x-^P6lsU){ZV*A>(CH?z_f?M!>w`0f8r zeW{Pe44WAoRd>wqb9WA2qj-Gl*~IE~`IX@lP|-WitOKm+OWBM*{;fPqns)94G7w%>eY+UB>a3cs0`AKJui8>#*msF6VntUEbDL4LzR<^_R0 zFB+T-)zsY7M0^%jzBrf6GOPU^uh{|#RR)HiZy1X~I&5G%_zV^^FEDc}ao8xq&3(Zm zO)O1;GpXrJGUM&Thgc>hCL}O+H>zH|5_nVh#l2KDy%`5qF+3Jw3>oK{?fpVaNs*M1(nRdej(otHi}5mj5f*XpQ0 z*b}F9Y{94IuZNl~b)Q{0c<-&1690yk$zQ*+E#AYZ1Tx+eZ2Z!`gM17M98Q^^{@<6G zweyb_J+uWy$0Mf9qpQTOQzY(IF`uH z;9tgE05UHQh$|RNCopn>!gKPq|NASCWy&7r3M%&4a*5#<)5e|#p|pQ>m!e%f-<_Gx zQZz|$!;#9v?=?j8_;pUKPTGA=uyv3Biqx>_`%a$UQsjOncjncj`M=(mi0A&?f1g>h zpK%e$7G1C{$tf-%SL?iB&}ic|045rx62)bTT5V_dzc_e^DWpZLs;9@nkRk6elOjkH E0G`eR1^@s6 literal 894 zcmZ=y%t?(fGB9|^$iPs+_;$8qev^U7@%P@}WM`?#iHk1JU{Y(kEZY%1r!f7B)k!YZ z1>QH7-_O37yL_(2nrHm%nM>dA{r72-yv&C**EwC2*186&X?@;#`DMYkS#SE-=l5Gw zi=4duFhj4|=KIeHn*-)|q<+w5{GVV!Yre!j$KpPM<3TYvA_ zdcFFD-%B?U-E(2CbEZBQDG#|Ba$?7#sj_=FX9hi6b;Z=WRHaMwsOZ~&oAjeTUjOD> z<<`D^4*TPRCH}|f@Y~jYyL0eD;d}MNnd|)}3mX_XfG9?las67|1-&U#5_}FRZB)0P zloQs~?XV8hrUn3S_` zwSXXt0|SeW8iS!zkJxs7FLt1&#%G)kJp##}V_Q2wY7-1WYPamD4YUHe)5M=4sYPP; zdo33hkOkc!wTq9w?Y`v5z~Zo=lIg!sPuySEWY772HH=-IBCJXbph!+maA0tgVBk79 zqvu7Anwy(}lX!ckgD`v1Lk0%EI}FZ1MGU$Kl?n|X9dac254Cw18Y!wY=os)XVPD0z zhT{mEXT#YhuZ9~8+>8v?AWdorO>pBk@CdhA_%MgqMo6B>SYgq@$-wu4!3(5X9jdy8 zflYy7;|vFuDZEKZ8xC?cocgemF{^ndvzbT|4>N<13PTV`xe8P{(Cr)y+zvN;6!^Gi zcTN=SFgUufYr>W0vyN&EJQo=(L29)jYLk)@7!(!QgbmD?nUy&hTpbw>rX?|LKKN?l z(TO(|JZD&*$YBsjfw)Ekq9v`3iH(g-qq|k|U|QRTNQEGSLILq(Y$q3NK6t8;WtIhl z*<^;FAS>h`>QjSJGN~5><&(KhGVUBUQDlH RUJEpY!PC{xWt~$(69C$vBNG4s diff --git a/images/tiles0001.png b/images/tiles0001.png index 1c77963b6ac98107d87eb897405c336c3b5bcf75..1fe791a1737bacdab8d32b03880b34b900203b75 100644 GIT binary patch delta 187 zcmcc1*vDkj8Q|y6%O%Cdz`(%k>ERLtqy<5kgAGVp=eqv_QtTz3zOL+#IfQsr)s{Bw zR-b4RQkY1lOyb lH-40BWjZQ-X|edi_*itd)55tre}E2Q@O1TaS?83{1OT3vM5zD( delta 138 zcmeBUy31(N8Q|y6%O%Cdz`(%k>ERLtq&Y#DgAGVNn8+poq}WS5eO=ifa|rUO%Chd# zY@TQm<7?sR;uumfC)uL%)029q8IHlhpY4s*yadgTT8JuYvuIcS`C%A#G{X1LvKb3X lEkqa2W(#9c;+V<8aOLXi7mmRlr-0@%c)I$ztaD0e0sw9}DIov= diff --git a/images/tiles0002.png b/images/tiles0002.png index ee96aa2b61993e015d18a69d4eec9d0e9ee26c31..3e9a5a57bef8f751f893467761ffcf855234a640 100644 GIT binary patch delta 193 zcmcb_IGM?$Gr-TCmrII^fq{Y7)59eQNDG262OE&I&UOC%J#Ae+(&7nKK&T1kvz qTNDHrI8I_iIBa{FD diff --git a/images/tiles0003.png b/images/tiles0003.png index 6fcc29b14d6d804aba6661f8b97147e017cfbb0e..993901c2bb624637efb66cf42894801ac5530ee0 100644 GIT binary patch delta 222 zcmcb@xPi%}Gr-TCmrII^fq{Y7)59eQNDG262OE&I&UOC}H2g^7SggUoIZ0ZL;q&I} V{y&o*?FPD)!PC{xWt~$(695_lQ}+M> delta 129 zcmdnMbcNBRGr-TCmrII^fq{Y7)59eQNOOWP2OE%lFp*6FNU@iA`ns||<`Cplm1W(f z**wuC#zo)L#WAE}PO?SirziEkzP@uLRL(c({rK@Qna7ZaF=s}F>y0%IV#_y`vedf! bSTHi=)-+!~&@>|nXdHv5tDnm{r-UW|KRG6N diff --git a/images/tiles0004.png b/images/tiles0004.png index d86617b1133865fe490cc84937febd20875f8eba..e52827bd464a7863ffce238106c562e9cad364ef 100644 GIT binary patch delta 176 zcmcc2*vw?o8Q|y6%O%Cdz`(%k>ERLtqy<5kgAGVp=eqv_QtTz3zOL+#IfQsr)s{Bw zR-b4RQyk~%;uumfCwYct@w0j_L*`JP!1D(G{Wa4rT)FbcUsG;f+l+5jjfRG^E?hM_ zz>;{;wMWrlt3mUM=aUYKZZI)s5qr>bsO#l?TX|+xhBfmi__jO==wb@UQ&E+1c$HYi Z7&`B6#>KNQdx5qyc)I$ztaD0e0syFIK1~1s delta 128 zcmZo>y3AERLtq&Y#DgAGVNn8+poq}WS5eO=ifa|rUO%Chd# zY@TQmERLtqy<5kgAGVp=eqv_QtTz3zOL+#IfQsr)s{Bw zR-b4RQ<>uF;uumfCwYct@w0j_L*`JP!1D(G{k4u6xC94(wqJQ;%k}OeX9tO!uFb~< z)Yvp0cm}d0r+hd+k6(vv(MP5)4Nm+Aj`zzqXD-^q9MrK+Vu^qX+oE>21AKE7B{ZjL eRiERLtq&Y#DgAGVNn8+poq}WS5eO=ifa|rUO%Chd# zY@TQm<7(jP;uumfC%NL!4?|{-`8JiG>^T;1cAU|u;cCF>>+8$IGg;YB@Qg=JS4M{H cQAY*_k8c;4?_93S0Gh|(>FVdQ&MBb@0P(vdi2wiq diff --git a/images/tiles0006.png b/images/tiles0006.png index 7dcf4258326a12ac8883c5f7610efa08c9c01352..e8da36aeb004c015043d77aab95b37d096109725 100644 GIT binary patch delta 142 zcmcb}_>j@0Gr-TCmrII^fq{Y7)59eQNDG262OE&I&UOC{lpB{2X1 diff --git a/images/tiles0007.png b/images/tiles0007.png index 5a451822c7faa46386c228fa13df204c583ecdb6..cc835f2e7c8b61de32040fad77b3cc638a084b25 100644 GIT binary patch delta 222 zcmX@XxPi%}Gr-TCmrII^fq{Y7)59eQNDG262OE&I&UOC9ynrNe7)a$s-PZX(c@T!bP!6e8Ler3x3ZOU|ZRK^p*mb z@TwgxT8U0mZ%CRX9y-FnqG|Jw$40Y@=fs2gw(@ay9=3`e@)7~6;>z5UqUSL%Olzopr0HPR7djJ3c delta 117 zcmdnMbb`^OGr-TCmrII^fq{Y7)59eQNOOWP2OE%lFp*6FNU@iA`ns||<`Cplm1W(f z**wuC#zNWC#WAE}PV$8-SN{C}@iCdL#)P@2k-PcHN3Dj33v-1Q7BDe9<>~5H+1t7S PXas|&tDnm{r-UW|+0i5s diff --git a/images/tiles0008.png b/images/tiles0008.png index 0f4a6610d38aecdb00e431ace42e4262c6c48322..af9e4c562b3c063a3e69f506693b0d2231a7e205 100644 GIT binary patch delta 135 zcmdnbc#F}bGr-TCmrII^fq{Y7)59eQNDG262OE&I&UOC>>x#WAE}PV$8-SN`~GrWw5b`U^HWmQa3-xO<>KBB1)!k}p00i_>zopr07+aeYybcN delta 108 zcmcb`xS!FaGr-TCmrII^fq{Y7)59eQNOOWP2OE%lFp*6FNU@iA`ns||<`Cplm1W(f z**wuCMqkR)#WAE}PV$8-SN`~O7~9nSnV}%}tgC}louTi+A)Ui}TZ@1?89ZJ6T-G@y GGywo-`5xi` diff --git a/index.html b/index.html index 264338c5..10827921 100644 --- a/index.html +++ b/index.html @@ -6,9 +6,8 @@ - - - -
\ No newline at end of file +
+

+
\ No newline at end of file diff --git a/modules/game.js b/modules/game.js index 72186c85..4281c250 100644 --- a/modules/game.js +++ b/modules/game.js @@ -51,7 +51,7 @@ class Car { // render this.renderer.x = this.x; this.renderer.y = this.y; - this.renderer.z = 0.35 + this.generator.sampleHeightmap(this.x, this.y); + this.renderer.z = 0.25 + this.generator.sampleHeightmap(this.x, this.y); this.renderer.angle = this.angle; this.renderer.render(); } diff --git a/modules/renderer.js b/modules/renderer.js index a10af3dc..c31cc436 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -15,10 +15,10 @@ export default class Renderer { this.horizon = 0.2; this.carFrame = 1; + this.floorCanvas = null; } async load() { - // TODO maybe put this in a separate async function and change game logic to only start after load this.imgCorolla = await this.loadImages("corolla", 3); this.imgTiles = await this.loadImages("tiles", 9); // load tile data @@ -31,6 +31,8 @@ export default class Renderer { ctx.drawImage(tile, 0, 0); this.tileData.push(ctx.getImageData(0, 0, tile.width, tile.height).data); } + + this.renderFloorTexture(); } async loadImages(name, numberOfFrames, format = "png") { @@ -49,12 +51,39 @@ export default class Renderer { return images; } + renderFloorTexture() { + let canvas = document.createElement("canvas"); + let tileW = this.imgTiles[0].width; + let tileH = this.imgTiles[0].height; + canvas.width = this.generator.size * tileW; + canvas.height = this.generator.size * tileH; + document.querySelector("footer").append(canvas); + let ctx = canvas.getContext("2d"); + for(let x = 0; x < this.generator.size; x++) + for(let y = 0; y < this.generator.size; y++) { + ctx.drawImage(this.imgTiles[this.generator.sampleFloorMap(x, y)], x * tileW, y * tileH) + } + + this.floorCanvas = canvas; + this.floorCanvasData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + } + + sampleFloor(x, y, lum = 1) { + let i = (~~(y*this.imgTiles[0].width) * this.floorCanvas.width + ~~(x*this.imgTiles[0].height)) * 4; + return [ + (1-lum) * 0xcc + lum * this.floorCanvasData[i], + (1-lum) * 0xcc + lum * this.floorCanvasData[i+1], + (1-lum) * 0xcc + lum * this.floorCanvasData[i+2], + (1-lum) * 0xcc + lum * this.floorCanvasData[i+3] + ]; + } + render() { let t0 = Date.now(); const w = this.canvas.width; const h = this.canvas.height; const d = 180; - const farPlane = 1; // not actually a far plane anymore bec. of tangent bias + const farPlane = 12; // let nearPlane = .1; // units // let fovRatio = 1; @@ -70,9 +99,10 @@ export default class Renderer { // for (let screenZ = d; screenZ >= 0; screenZ--) { for (let screenZ = 0; screenZ <= d; screenZ++) { // iterate from left to right - const relDistance = Math.tan(screenZ / d * 1.57) * farPlane + // const relDistance = Math.tan(screenZ / d * 1.57) * 0.125 * farPlane; // tangent bias + const relDistance = (screenZ / d)**2 * farPlane; // square bias const rx = relDistance; - const lum = (1 - screenZ / d) * 255; + const lum = (1 - screenZ / d); for (let screenX = 0; screenX < w; screenX++) { const ry = relDistance * (screenX / w * 2 - 1); const x = this.x + cosAngle * rx - sinAngle * ry; @@ -81,11 +111,13 @@ export default class Renderer { // ~~ ist essentially Math.floor const screenHeight = Math.min(~~((sample - this.z + relDistance) / (relDistance * 2) * h + this.horizon * h), h); // render a vertical scanline - const col = sample > 0 ? - [lum, lum, lum] - : - COLOR_TABLE[this.generator.sampleFloorMap(x, y)]; - ; + // const col = sample > 0 ? + // [lum, lum, lum] + // : + // // COLOR_TABLE[this.generator.sampleFloorMap(x, y)]; + // this.sampleFloor(x, y); + // ; + const col = this.sampleFloor(x, y, lum); const buf = depthBuffer[screenX]; for (let screenY = buf; screenY < screenHeight; screenY++) { let i = ((h - screenY) * w + screenX) * 4; @@ -106,10 +138,6 @@ export default class Renderer { let t = (Date.now() - t0); // console.log(`render time: ${t}ms, ${1000/t}fps`); - document.querySelector("footer").innerHTML = `render time: ${t}ms, \t${~~(1000 / t)}fps`; - } - - sampleFloor() { - + document.querySelector("footer>p").innerHTML = `render time: ${t}ms, \t${~~(1000 / t)}fps`; } } \ No newline at end of file diff --git a/style.css b/style.css index 29c3955f..baf1312f 100644 --- a/style.css +++ b/style.css @@ -8,7 +8,7 @@ canvas.game { height: auto; padding:0; margin: 0; - background-color: black; + background-color: #ccc; } body { From ba5d61e5d39beb4d6e61bc77d3b023c36f24a597 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Mon, 19 Aug 2019 17:49:18 +0200 Subject: [PATCH 05/29] Add textures, wrap city --- images/tilemap.png | Bin 0 -> 261 bytes images/tiles.gal | Bin 1204 -> 0 bytes images/tiles0000.png | Bin 243 -> 0 bytes images/tiles0001.png | Bin 270 -> 0 bytes images/tiles0002.png | Bin 275 -> 0 bytes images/tiles0003.png | Bin 304 -> 0 bytes images/tiles0004.png | Bin 259 -> 0 bytes images/tiles0005.png | Bin 265 -> 0 bytes images/tiles0006.png | Bin 225 -> 0 bytes images/tiles0007.png | Bin 304 -> 0 bytes images/tiles0008.png | Bin 218 -> 0 bytes modules/game.js | 18 ++++-- modules/generator.js | 14 ++-- modules/renderer.js | 150 ++++++++++++++++++++++++++++++++----------- style.css | 2 +- 15 files changed, 133 insertions(+), 51 deletions(-) create mode 100644 images/tilemap.png delete mode 100644 images/tiles.gal delete mode 100644 images/tiles0000.png delete mode 100644 images/tiles0001.png delete mode 100644 images/tiles0002.png delete mode 100644 images/tiles0003.png delete mode 100644 images/tiles0004.png delete mode 100644 images/tiles0005.png delete mode 100644 images/tiles0006.png delete mode 100644 images/tiles0007.png delete mode 100644 images/tiles0008.png diff --git a/images/tilemap.png b/images/tilemap.png new file mode 100644 index 0000000000000000000000000000000000000000..db22763ef7dbcaf2842d4652adba593c28e74f7f GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|>?NMQuI!IFg!nAks%3@l0ENU$ zTq8<&lR<&;!A6F)wg+yo vC1^2ywqn}(CH?z_f?M!>w`0f8r zeW{Pe44WAoRd>wqb9WA2qj-Gl*~IE~`IX@lP|-WitOKm+OWBM*{;fPqns)94G7w%>eY+UB>a3cs0`AKJui8>#*msF6VntUEbDL4LzR<^_R0 zFB+T-)zsY7M0^%jzBrf6GOPU^uh{|#RR)HiZy1X~I&5G%_zV^^FEDc}ao8xq&3(Zm zO)O1;GpXrJGUM&Thgc>hCL}O+H>zH|5_nVh#l2KDy%`5qF+3Jw3>oK{?fpVaNs*M1(nRdej(otHi}5mj5f*XpQ0 z*b}F9Y{94IuZNl~b)Q{0c<-&1690yk$zQ*+E#AYZ1Tx+eZ2Z!`gM17M98Q^^{@<6G zweyb_J+uWy$0Mf9qpQTOQzY(IF`uH z;9tgE05UHQh$|RNCopn>!gKPq|NASCWy&7r3M%&4a*5#<)5e|#p|pQ>m!e%f-<_Gx zQZz|$!;#9v?=?j8_;pUKPTGA=uyv3Biqx>_`%a$UQsjOncjncj`M=(mi0A&?f1g>h zpK%e$7G1C{$tf-%SL?iB&}ic|045rx62)bTT5V_dzc_e^DWpZLs;9@nkRk6elOjkH E0G`eR1^@s6 diff --git a/images/tiles0000.png b/images/tiles0000.png deleted file mode 100644 index 112add8988a8f9e214d5eacbe99fd30227aa1ffa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8e diff --git a/images/tiles0001.png b/images/tiles0001.png deleted file mode 100644 index 1fe791a1737bacdab8d32b03880b34b900203b75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8Wk*j$Y$da^j&6s4iSUlxIRY+M>Z2xTZE?}MAa>H}!Rt+J zJN8A``%jvw);ec_$);wn6I`!$-uO|jmFcMTrN!b4<73g;P7CMi`~h0Z;OXk;vd$@? F2>=lWTK)h4 diff --git a/images/tiles0002.png b/images/tiles0002.png deleted file mode 100644 index 3e9a5a57bef8f751f893467761ffcf855234a640..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8z~H71jl_2LIHyOml%XEDsUG}l>F2n zAlZM4L+RdfK{llmE-DWkwUQXcwkQZLaGb!X!V;O#wt(q_xZErC-zB?tf!xC2>FVdQ I&MBb@0Feq+XaE2J diff --git a/images/tiles0003.png b/images/tiles0003.png deleted file mode 100644 index 993901c2bb624637efb66cf42894801ac5530ee0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8Jt~wx9GmD+4Wa!&IJdPqn*7sS~{+(8XTCs=tMw30K>A6jLYi3)f};u olvfaG_>si1Sc8FclC&Db=grssemdKI;Vst03riw5dZ)H diff --git a/images/tiles0004.png b/images/tiles0004.png deleted file mode 100644 index e52827bd464a7863ffce238106c562e9cad364ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8;>Ay;OXk;vd$@?2>>{ERKfrN diff --git a/images/tiles0005.png b/images/tiles0005.png deleted file mode 100644 index 1621fc7a341279178858b5a584c82a240292bfda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8V364)D!Ul+c`}RhfQ4gq5MHMXQI|W9l5BeGHzielF{r5}E*9t5P=r diff --git a/images/tiles0006.png b/images/tiles0006.png deleted file mode 100644 index e8da36aeb004c015043d77aab95b37d096109725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq82LJsv(eX92!n2|I+ z;Hbc@cFtY(?BQ<&itZ}#-Dos#xsh>n8Q)QXB%VXrCmPy087A&$I-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8L7W|$mz_zme=q&{<;Z-|Yv=W`B-jFm&JamMCMbqXVkBw#*&xr@~ZRO+a oJZwGWB?45%mANNH&tqVi*!nqR#%nDfptBe}UHx3vIVCg!0J*+mp#T5? diff --git a/images/tiles0008.png b/images/tiles0008.png deleted file mode 100644 index af9e4c562b3c063a3e69f506693b0d2231a7e205..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^f*{Pn1|+R>-G2co_7YEDSN6voLOiN!OB;5p1BJv( zTq8 0? FRIC : BACKFRIC; this.hspd -= scl * dx; this.vspd -= scl * dy; diff --git a/modules/generator.js b/modules/generator.js index f01ceef8..268b779e 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -102,15 +102,13 @@ export default class Generator { sampleHeightmap(x, y) { // return 0; // return Math.random() < 0.05? .5 : 0; - if(x >= this.size || y >= this.size || x < 0 || y < 0) return 0; - return this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)] - + (Math.sin(y*3.141569*0.25) + Math.sin(x*3.141569*0.25) - 2) * 0.2 - ; + // if(x >= this.size || y >= this.size || x < 0 || y < 0) return 0; + const hm = this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)]; + return hm || (Math.sin(y*3.141569*0.25) + Math.sin(x*3.141569*0.25) + 0) * 0.2; } sampleFloorMap(x, y) { - return this.floorMap[(mod(y, this.size) * this.size) + mod(x, this.size)] - + Math.round(x - Math.floor(x)); + return this.floorMap[(mod(y, this.size) * this.size) + mod(x, this.size)]; } } @@ -121,6 +119,6 @@ function* iterateQGrid(w, h) { } } -function mod(x, n) { - return((Math.floor(x)%n)+n)%n; +export function mod(x, n) { + return ~~(((x%n)+n)%n); } \ No newline at end of file diff --git a/modules/renderer.js b/modules/renderer.js index c31cc436..43f1e52a 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -1,9 +1,49 @@ +import { mod } from "./generator.js"; + const COLOR_TABLE = [ 0x777777, 0x121212, 0x126612, ].map(val => [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff]); +export class Texture { + constructor(w, h) { + this.data = new Array(w*h); + this.width = w; + this.height = h; + } + + static fromImage(img) { + let canvas = document.createElement("canvas"); + let w = canvas.width = img.width; + let h = canvas.height = img.height; + let ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + let data = ctx.getImageData(0, 0, w, h).data; + let texture = new Texture(w, h); + for(let i = 0; i < w * h; i++) { + texture.data[i] = [ + data[i*4], + data[i*4+1], + data[i*4+2] + ]; + } + return texture; + } + + fill(val) { + this.data = this.data.fill(val); + } + + sampleAt(x, y) { + return this.data[y*this.width + x]; + } + + setAt(x, y, value) { + this.data[y*this.width + x] = value; + } +} + export default class Renderer { constructor(generator, canvas) { this.generator = generator; @@ -20,21 +60,19 @@ export default class Renderer { async load() { this.imgCorolla = await this.loadImages("corolla", 3); - this.imgTiles = await this.loadImages("tiles", 9); // load tile data - this.tileData = []; - let canvas = document.createElement('canvas'); - canvas.width = this.imgTiles[0].width; - canvas.height = this.imgTiles[0].height; - let ctx = canvas.getContext('2d'); - for (let tile of this.imgTiles) { - ctx.drawImage(tile, 0, 0); - this.tileData.push(ctx.getImageData(0, 0, tile.width, tile.height).data); - } + this.tileTexture = Texture.fromImage(await this.loadImage("tilemap")); this.renderFloorTexture(); } + async loadImage(name, format = "png") { + let img = document.createElement("img"); + img.src = `images/${name}.${format}`; + await new Promise(resolve => { img.onload = resolve }); + return img; + } + async loadImages(name, numberOfFrames, format = "png") { let images = []; let promises = []; @@ -52,29 +90,71 @@ export default class Renderer { } renderFloorTexture() { - let canvas = document.createElement("canvas"); - let tileW = this.imgTiles[0].width; - let tileH = this.imgTiles[0].height; - canvas.width = this.generator.size * tileW; - canvas.height = this.generator.size * tileH; - document.querySelector("footer").append(canvas); - let ctx = canvas.getContext("2d"); + const tileW = this.tileTexture.width; + const halfW = ~~(tileW/2); + const tileH = this.tileTexture.height; + + let tex = new Texture(this.generator.size * tileW, this.generator.size * tileH); + + const COS = [1, 0, -1, 0]; + const SIN = [0, 1, 0, -1]; + let transform = (x, y, r) => { + x -= (tileW-1)/2; + y -= (tileH-1)/2; + let bufX = COS[r] * x - SIN[r] * y; + y = SIN[r] * x + COS[r] * y; + x = bufX; + x += (tileW-1)/2; + y += (tileH-1)/2; + return {x: x, y: y}; + } + for(let x = 0; x < this.generator.size; x++) for(let y = 0; y < this.generator.size; y++) { - ctx.drawImage(this.imgTiles[this.generator.sampleFloorMap(x, y)], x * tileW, y * tileH) + let baseID = this.generator.sampleFloorMap(x, y); + for(let dir = 0; dir < 4; dir++) { + const adjDir = (dir+2)%4; + const id = baseID * 2 + this.generator.sampleFloorMap(x + COS[adjDir], y + SIN[adjDir]); + // id = 0; + + for(let locX = 0; locX < halfW; locX++) { + for(let locY = locX; locY < tileH-locX-1; locY++) { + const tp = transform(locX, locY, dir); + const sp = transform(locX, locY, id); + tex.setAt(x*tileW + tp.x, y*tileH + tp.y, this.tileTexture.sampleAt(sp.x, sp.y)); + } + } + } } + + let canvas = document.createElement("canvas"); + canvas.width = tex.width; + canvas.height = tex.height; + document.querySelector("footer").append(canvas); + let ctx = canvas.getContext("2d"); this.floorCanvas = canvas; - this.floorCanvasData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); + for(let i = 0; i < tex.width * tex.height; i++) { + if(!tex.data[i]) continue; + for(let j = 0; j < 3; j++) { + imgData.data[i*4+j] = tex.data[i][j]; + } + imgData.data[i*4+3] = 255; + } + ctx.putImageData(imgData, 0, 0); + this.floorCanvasData = imgData.data; } sampleFloor(x, y, lum = 1) { - let i = (~~(y*this.imgTiles[0].width) * this.floorCanvas.width + ~~(x*this.imgTiles[0].height)) * 4; + x = mod(x * this.tileTexture.width, this.floorCanvas.width); + y = mod(y * this.tileTexture.height, this.floorCanvas.height); + let i = (y * this.floorCanvas.width + x) * 4; return [ - (1-lum) * 0xcc + lum * this.floorCanvasData[i], - (1-lum) * 0xcc + lum * this.floorCanvasData[i+1], - (1-lum) * 0xcc + lum * this.floorCanvasData[i+2], - (1-lum) * 0xcc + lum * this.floorCanvasData[i+3] + (1-lum) * 0x22 + lum * this.floorCanvasData[i], + (1-lum) * 0x22 + lum * this.floorCanvasData[i+1], + (1-lum) * 0x22 + lum * this.floorCanvasData[i+2], + (1-lum) * 0x22 + lum * this.floorCanvasData[i+3] ]; } @@ -108,20 +188,18 @@ export default class Renderer { const x = this.x + cosAngle * rx - sinAngle * ry; const y = this.y + sinAngle * rx + cosAngle * ry; const sample = this.generator.sampleHeightmap(x, y); - // ~~ ist essentially Math.floor - const screenHeight = Math.min(~~((sample - this.z + relDistance) / (relDistance * 2) * h + this.horizon * h), h); - // render a vertical scanline - // const col = sample > 0 ? - // [lum, lum, lum] - // : - // // COLOR_TABLE[this.generator.sampleFloorMap(x, y)]; - // this.sampleFloor(x, y); - // ; + + const relH = (sample - this.z + relDistance) / (relDistance * 2) + this.horizon; + const screenHeight = Math.min(~~(relH * h), h); + const col = this.sampleFloor(x, y, lum); const buf = depthBuffer[screenX]; + let stripe = 18; for (let screenY = buf; screenY < screenHeight; screenY++) { - let i = ((h - screenY) * w + screenX) * 4; - imgData.data[i] = col[0] + // stripe += Math.random()*0.01; + const worldY = (relH * h - 1 - screenY) * relDistance; + const i = ((h - screenY) * w + screenX) * 4; + imgData.data[i] = col[0] * ~~(mod(worldY, stripe) * 2 / stripe); imgData.data[i + 1] = col[1] imgData.data[i + 2] = col[2]; imgData.data[i + 3] = 255; @@ -138,6 +216,6 @@ export default class Renderer { let t = (Date.now() - t0); // console.log(`render time: ${t}ms, ${1000/t}fps`); - document.querySelector("footer>p").innerHTML = `render time: ${t}ms, \t${~~(1000 / t)}fps`; + document.querySelector("footer>p").innerHTML = `render time: ${t}ms, \t${~~(1000 / t)}fps x: ${this.x} y: ${this.y}`; } } \ No newline at end of file diff --git a/style.css b/style.css index baf1312f..e7c84edb 100644 --- a/style.css +++ b/style.css @@ -8,7 +8,7 @@ canvas.game { height: auto; padding:0; margin: 0; - background-color: #ccc; + background-color: #222; } body { From c35b43b1cb6795ea7501c30d082ff5dedfc419b3 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Sun, 25 Aug 2019 19:42:05 +0200 Subject: [PATCH 06/29] whatever --- build/.vscode/settings.json | 3 + build/images/corolla0000.png | Bin 0 -> 500 bytes build/images/corolla0001.png | Bin 0 -> 429 bytes build/images/corolla0002.png | Bin 0 -> 503 bytes build/images/tilemap.png | Bin 0 -> 289 bytes build/index.html | 53 ++++++++++++++ build/style.css | 21 ++++++ images/corolla.gal | Bin 1233 -> 1160 bytes images/corolla0000.png | Bin 500 -> 438 bytes images/corolla0001.png | Bin 429 -> 370 bytes images/corolla0002.png | Bin 503 -> 438 bytes images/tilemap.png | Bin 261 -> 274 bytes index.html | 1 + modules/audio.js | 129 +++++++++++++++++++++++++++++++++++ modules/game.js | 75 ++++++++++++++++++-- modules/generator.js | 19 +++++- modules/renderer.js | 38 +++++++++-- 17 files changed, 326 insertions(+), 13 deletions(-) create mode 100644 build/.vscode/settings.json create mode 100644 build/images/corolla0000.png create mode 100644 build/images/corolla0001.png create mode 100644 build/images/corolla0002.png create mode 100644 build/images/tilemap.png create mode 100644 build/index.html create mode 100644 build/style.css create mode 100644 modules/audio.js diff --git a/build/.vscode/settings.json b/build/.vscode/settings.json new file mode 100644 index 00000000..6f3a2913 --- /dev/null +++ b/build/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/build/images/corolla0000.png b/build/images/corolla0000.png new file mode 100644 index 0000000000000000000000000000000000000000..2d65ee9d4e477065998b8d75398271334a01142e GIT binary patch literal 500 zcmVJwD(1}Fp$iP(@PXp$Ah6SAVBf)E}7%_V2Z z*&KH7Ft@PDm)V`UbI<*mX_J|y2$%DP-CSLVfNjlRNc8T$v2L?Aj5TP705FzN&HG7s%845?}g_pah22c)gu(v;gYY33`3@{?btXcM*>G9MShwlkQp4I1T>h0e? z^4!zIf_Nn2)C{jIJEZ$p3Y qBf?q+k8Olm{(t{FMHsfFX8#59V~FWG;a8{t0000C&jW;TI)s6;8$WMPLBH4AmfT!8hR79=) zYC-@@$`99sgqTM^o+L~>d4z#QvC>eX%hAcP^`N3+%fSm5#d=glRUhV*^ysi(V!qU! zBWA&2wxN2#10H7ij_w5}W<6`zV(m}(um#*`*vlBQVDa+i#?&7w81EV4e5L~56N1cK`4Ujy8BBIU{sPY1e{D6vrE@g^Sgp?ZT3erc>6%;8D;t8oS ztfe_k_H5r7ERAgWmYMa;`4H!vr11JObIbXn6STIC4~UAre-xff=jwOs=Qg13B4HH; zL9Q@r6>X<)Y=8iw_B1)-%F#)9Fd8^yYP0Pk9Ls(B_*A17hP9U15}<~yoKBwqL4Sv} zK99Ya4dEPEGHw>4wlO|+ZLiHI1rV)n#~w=nVPjtb53g-ve058Dc+nVzyq_URW2f zgYCkaGvjN#)5IPBS-nQdwaRQr%i0P}1)4%`#002ovPDHLkV1f?)&7J@N literal 0 HcmV?d00001 diff --git a/build/images/tilemap.png b/build/images/tilemap.png new file mode 100644 index 0000000000000000000000000000000000000000..842ba9664dd70d44a9a1345966e12c7a08bccb56 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|>?NMQuI!IFMEMPLXO%HU1BJv( zTq8Wy#Dz8 z`|~>)c~+_0rz)^i{g!T25uWq@ZMJ`aDTl)x78i%tJ-xjTV^UbAXt6RYFfU|WA+=hr z!$OQ#;A%!y&%uTU-U8cW1`apaOdJldC7C`5>OIeP$z(zrbBpsDHj8!*-V-uOCJYR> Xb}9xkRfQS>-NE4L>gTe~DWM4fNpDxe literal 0 HcmV?d00001 diff --git a/build/index.html b/build/index.html new file mode 100644 index 00000000..ebbe1911 --- /dev/null +++ b/build/index.html @@ -0,0 +1,53 @@ + + + + + + + + + +
+

+
\ No newline at end of file diff --git a/build/style.css b/build/style.css new file mode 100644 index 00000000..e7c84edb --- /dev/null +++ b/build/style.css @@ -0,0 +1,21 @@ +canvas { + image-rendering: crisp-edges; + image-rendering: pixelated; +} + +canvas.game { + width: 100%; + height: auto; + padding:0; + margin: 0; + background-color: #222; +} + +body { + padding: 0; + margin: 0; +} + +img.sprite { + display: none; +} diff --git a/images/corolla.gal b/images/corolla.gal index e77f5ba12093c2588b0449ef8fac1cd08cea5b5d..fb59e89131984188e0ea9b476ddd7b2b4c728da4 100644 GIT binary patch literal 1160 zcmV;31b6#KVQghsGB7YE0RRAa0qs)FPJ}QJzMJ?Clim$1xN-p#VYBOjtVTEM(bT0% zW1$U26MTBVwyXy|_yEd<&UEJcOY`->J7H-r1}k-6uq2KdJ;-Kk3zjB~u7!MQ$AT>` z7?s9g;G9vV-+J^?M)y;HxaA7>jP7{f=uUo!reFZ6bZ2&x5nn|u-UTLcRc@?rt8q?pjc9y~ptx?7(mh z32kP9jHx0I!_Zn}%z`oEv;^s>1MBM-l2`)~Sf1zFlKbeGVI^yZ{Jnzt7#{Dq#}Kfuk618bPd4Hty{zWcE~JF` zfgW3+y%&lLODLK20EoFn{J@8jcO{`t^0Dv(6nhYPwCmt5ET?K_nZRa_`>~Qb<;oMC z8gqIT7j{Z5^R&Yix|-$dveeCAr0AVw00000D**ricmb7>!41Md3#*re<PcV#sKOlw8AXe a`;*y{AL-}&u;s}7_skPp4Wj!10002_MlxXl literal 1233 zcmV;?1TOnWVQghsGB7YF0RRAa0qs)FPJ}QJzMJ?Clim$1%SueZMA+^5(Oapzj74F{=t0&)ld)yQ=vv5^X2{s$ zoKbEJ24Y5qe(TU%GP)nT!!1|1XLQHATDS5;)ENUvp<7dpMtl{eco&$&RlY5>(!E1X zk|bKJ&{F#FI`g^-S01@h@AR;dO66O}ba{G0lzQc~gZzy7GbB_!@9d0yhI%a&AF&cT zS8HjFwoYs`K>(!WBb*-%VdEF;o|^DYn^cNt1Of0$BOCnp(#j_VN#E2!LL5gIi0n$4 zhC{;&uKoE2u{(?2i5G<368?QVm?_g>(mI(ByyKri6xxiB|7px&{_!-@>6KVKlTXe^ z6(KeO004Laoscn3LqQA$OS;q)93euv15Ju3HAhIr2`JM1nkq$ZkRpmW3Tb%hi8%_pm(0cpwlR8AmI(sY$&sfXV@Wl-Ik8zHV5 zCaa=h$VRT{s?z6J0upJlF;?Bm>~l|ESV4;)%Ww4LY>~a{;ZGIJ^M`ZTf7heS2aX}X zg2k~D000004gmlFcmbV~Ee^s!5QQZUjo=6k;SLxCL30EYCqQ7TQHb6EK?sLIAUF&k zdC6olvt3$}m(BdV_jY#LcGb20I37;I<=h2Wt0J$rUhk(oZ}%%8RiN0q1a+Tc*W`WX zs%wC;R}3`&`wIEjCs&tKE0~bs(Kd_5V>X=!_%1OLgvi4-LaN7VPHjV#U``6h7HIFH5)4C% zCmab)rN9pyT3!_lLkiQhn%|#l`HEsM35GcpnPmc-Iq%0>VoH_WLM8i*tpvlI@+X)l zF%U;_PCYrh6)VFfTxaB>)dw;Aua5u#002S(004Lam60KC!$1&4VOelNk6=~3LrFmt z+#?v}1O+9*MFqY=KosRL1pzrspY)`!k?nOHX*Bwtoq6+jeRv-Z?fBY%|NaaMLBqm$ z>xik-`3RPLz5S6};c1IATL*AuAY}HC&6`Yljm$%?M(FZ$mOu z+qd4RSn5D3CnNGbCDZx2>l2t)Qzz;aIxE5trzI)@xSzta6fwGj&yv?}!6ke=T?hzF z5zm{K8W!z|e~XOgj(V$$u&ObelPesZy22Hj@zgD3KFKU{3&MA(9?(-hMF!=BPcf?< z;S)jKe*F7@2#d)Xq~ag(`sncMACZg!gf{FCP)N-lIpR7^<`;|v>lkqnomoT?$dn_k vW=4RFu=(T&&v5Fy4db}FNT+gxLrnW|71}sHeg%q|m5i2Uv2|Tjl5Njl3c78Z?fY(8d%{iuXl(`2 z%~FoC&WxYPts(`2aC&kVTkHS0Kzb^^BsM4IRMkR0=jd@ImZuz%R- z+*h`dW1l_c^QyraeZa5B(Piy(PlJQt*Q4x`0U8qUn7S2 z0PCSu-d2z6srRMCR{sOJd}a2)cPPZYPl0>71E&0cf1U0IR=*x2y#Z)%tfX)B2kQU; N002ovPDHLkV1nUNn+gB` delta 377 zcmV-<0fzpz1M~xsEC>)0HUdVQ=FE{aTz^kVL_t(|oTZhmP6I&{g(nys8iB{Oz+)f? znn$SW6IAd9CN&HG7s%845?}g_pah22c)g zu(v;gYY33`3@{?btXcM*>G9MShwlkQp4I1T>h0e?^4!zIifG^U+US{}z_3m9m4&@U?_iZq zy#}G#F>Ad>dS&xRsl49dqEih{OG#U1R{gE632#Gx?<2xm29IroS^j_jJ4G0_rDp#H X@?(hUI^kES00000NkvXXu0mjf0}r?r diff --git a/images/corolla0001.png b/images/corolla0001.png index bdcdf1b37905486f21edb7c09a4ab20b151e094a..3043d13a5960747a316404deae6072ebace211bc 100644 GIT binary patch delta 247 zcmVK_kNOd&Xnb42D6w+ScWbL#8+UwZIZ!pk~cvcAU!k7qdBBuze@N{iFdGk x8*pV#_gG%FaotvlPcCoA`F;4j-iTv_nS4EO*5002ovPDHLkV1jB3Yk2?w delta 307 zcmV-30nGmL0<8m(EC>)0HUdVQ=FE{aTYmuvNkl)M*nqtORG_1$gMnrEB(P#^YC&dz%mSIo01A9;83HDb z%V3Zka*9AU10>FXoD$F#z%aVYu&F^ehXLI%LRpxYEDkmpxom(X8B)U-IjO;t8v`zl zlwz=pL9vM}2##GKhUFp{AD0-qULce#jZKmmjBYSY8~_gmB*ew{M!5h0002ovPDHLk FV1frMe!&0$ diff --git a/images/corolla0002.png b/images/corolla0002.png index 46c751601abe188727669c76392720e8d359c823..f0b2be8236637e06db4d9a5a5f6ff2da215c98bd 100644 GIT binary patch delta 315 zcmV-B0mT0I1GWQ@EC?A7GacPj-i(nnTz?TsL_t(|oSl{35riNNgnhdP?7~KF?7&7W z#4>!kZY+nJW#(gI$>9D0&zpa z5`~~wm{_4z{o@4$i27=J#KrMScv+S}PknFS2$%Z4u4_UP##&G835c;)ZrgT))_*bA z_tM6E2=~B~*<=y*iP@@?y}my+K=k{3BSa=1VV>s)_(psZ7O=-xkgJsJS!+=fwn&?b z020uscl6Hg1FZCk>bWFrt=BxA-CWkpZO`m6v;DNNTsaqoDD1a7f)cVW)0HUdVQ=FE{aTz^tYL_t(|oTXJUZUQk7T%}1zoq|3MkUWDT zqRtbj@&bzdfQo`HWr|aTlp5&@(nrx16e$qm38^uxr8!OZY~L9yjcoarnf1*15a*nv z@cJ@y%lV=cw6={8h>E>`6rN1y>UZnsHlXezVHE{It}tp9ZKrQ+fB>TQG&$nR(SJ#J zFd8^yYP0Pk9Ls(B_*A17hP9U15}<~yoKBwqL4Sv}K99Ya4dEPEGHw>4wlO|+ZLiHI z1rV)n#~w=nVPjtb53g-ve058EIlac4D?(*Irl`u!HTwnls~T#(6TB%QlC#gkE(B zst~&n5=(tP(QN2Y(Ju=-!4B|MfpsC`7(B<~IR<+j65NqATym{(?1h6Xso{I(FMS>fJ{Dcup`9K~30000 + diff --git a/modules/audio.js b/modules/audio.js new file mode 100644 index 00000000..ad022fac --- /dev/null +++ b/modules/audio.js @@ -0,0 +1,129 @@ +const keys = { + aMaj: [0,2,3,5,7,8,10], + eMaj: [-5,-3,-2,0,2,3,5], + hmm: [0,2,5,7,10] +}; + +class Channel { + constructor(audioContext, octave, key, atc, dec, type = "sine", vol) { + this.ctx = audioContext; + this.octave = octave; + this.key = key; + this.atc = atc / 1000; + this.dec = dec / 1000; + this.lastNote = null; + this.vol = vol; + + this.osc = audioContext.createOscillator(); + this.osc.frequency.value = 440 * (2**this.octave) + this.osc.type = type; + + this.gainNode = audioContext.createGain(); + this.gainNode.gain.value = 0; + this.gainNode.connect(this.ctx.destination); + this.osc.connect(this.gainNode); + + this.osc.start(audioContext.currentTime); + } + + triggerNote(note, time) { + this.lastNote = note; + if(note == 0) return; + + this.osc.detune.value = this.key[(note-1)%this.key.length] * 100; + + let t = time || this.ctx.currentTime; + + this.gainNode.gain.setValueAtTime(0, t); + this.gainNode.gain.linearRampToValueAtTime(1/3 * this.vol, t + this.atc); + this.gainNode.gain.linearRampToValueAtTime(0, t + this.atc + this.dec); + + } +} + +class Track { + constructor(notes) { + this.notes = notes; + } + + static notes(...notes) { + return new Track(notes); + } + + cmul(...notes) { + return new Track(Track.convolution(this.notes, notes, (a,b) => a*b)); + } + + cadd(...notes) { + return new Track(Track.convolution(this.notes, notes, (a,b) => (b!=0? a+b : 0))); + } + + cadd2(...notes) { + return new Track(Track.convolution(this.notes, notes, (a,b) => (a!=0? a+b : 0))); + } + + space(length) { + return new Track(Track.convolution(this.notes, new Array(length), (a,b,i,j) => a * (j==0))); + } + + static convolution(notes1, notes2, fnc) { + let newNotes = new Array(notes1.length * notes2.length); + for(let i = 0; i < notes1.length; i++) { + for(let j = 0; j < notes2.length; j++) { + newNotes[i * notes2.length + j] = fnc(notes1[i], notes2[j], i, j); + } + } + return newNotes; + } + + at(beat) { + return this.notes[beat % this.notes.length]; + } +} + +export default class Audio { + constructor() { + } + + start() { + // Create an audio context + this.ctx = new AudioContext(); + this.bass = new Channel(this.ctx, -3, keys.aMaj, 1, 124, "square", 0.5); + this.kick = new Channel(this.ctx, -1, keys.aMaj, 1, 500, "sawtooth", 0.25); + this.lead = new Channel(this.ctx, -1, keys.aMaj, 1, 400, "sawtooth", .4); + + this.bassTrack = + + this.beat = 0; + this.bpm = 450; + this.t = 0; + this.lastCtxT = null; + + // window.setInterval(() => this.nextBeat(), 60000/this.bpm); + } + + update() { + if(!this.ctx) return; + + const ct = this.ctx.currentTime; + const delta = ct - (this.lastCtxT || ct); + this.lastCtxT = ct; + + this.t += delta; + this.bpm += delta * 1; + this.bass.dec = ((1+Math.sin(this.t / 4)) / 2 * 80 + 30) / 1000; + + const tNext = this.beat * 60/this.bpm; + if(tNext - this.t < 1) { + this.nextBeat(tNext); + } + } + + nextBeat(time) { + if(this.beat > 32) this.bass.triggerNote(Track.notes(1,5,1,3).cmul(1,1,1).cmul(1,1,2,2).cadd(1,1,1,1).cmul(1,1).at(this.beat), time); + if(this.beat > 64) this.kick.triggerNote(Track.notes(1,0,7,0).at(this.beat), time); + // this.lead.triggerNote(Track.notes(0,7).cadd(1,3,1,5).space(8).cadd2(1,3,5,1).cmul(1,0).at(this.beat), time) + if(this.beat > 0) this.lead.triggerNote(Track.notes(1,5,1,3).cmul(1,0,1,0,0,0,1,0,0,0,0,0).cadd(1,2,3,4).cmul(1,0).at(this.beat), time); + this.beat++; + } +} \ No newline at end of file diff --git a/modules/game.js b/modules/game.js index e267ba2b..1449487f 100644 --- a/modules/game.js +++ b/modules/game.js @@ -1,6 +1,7 @@ import Generator from "./generator.js"; import Renderer from "./renderer.js"; import { keyIsDown } from "./keyHandler.js"; +import Audio from "./audio.js"; const ACC = .25 / 30; const STEER = 2 / 30; @@ -12,6 +13,7 @@ class Car { constructor(renderer, generator) { this.x = 0; this.y = 0; + this.r = .03; this.hspd = 0; this.vspd = 0; this.angle = 0; @@ -22,8 +24,8 @@ class Car { update() { let dx, dy, scl; - let acceleration = (keyIsDown("up") - keyIsDown("down")) * ACC; - let steering = (keyIsDown("right") - keyIsDown("left")) + const acceleration = (keyIsDown("up") - keyIsDown("down")) * ACC; + let steering = (keyIsDown("right") - keyIsDown("left")); this.renderer.carFrame = -steering+1; steering *= STEER; @@ -53,14 +55,68 @@ class Car { this.hspd -= scl * dx; this.vspd -= scl * dy; + //collision + this.handleCircleCollision(this); // render - this.renderer.x = this.x; - this.renderer.y = this.y; - this.renderer.z = 0.25 + this.generator.sampleHeightmap(this.x, this.y); + this.renderer.x = this.x - dx * .25; + this.renderer.y = this.y - dy * .25; + this.renderer.z = 0.25 + this.generator.sampleGroundHeightmap(this.x, this.y); this.renderer.angle = this.angle; + this.renderer.render(); } + + handleCircleCollision(obj) { + if(this.collisionAtPoint(obj.x, obj.y)) { + obj.vspd *= -1; + obj.hspd *= -1; + } + + const x = Math.round(obj.x); + const y = Math.round(obj.y); + const rx = obj.x - x; + const ry = obj.y - y; + const posX = rx > 0? 1 : 0; + const negX = 1 - posX; + const posY = ry > 0? 1 : 0; + const negY = 1 - posY; + + const l = Math.sqrt(rx*rx + ry*ry); + + const colX = Math.abs(rx) < obj.r && this.collisionAtPoint(x - posX, y - negY); + const colY = Math.abs(ry) < obj.r && this.collisionAtPoint(x - negX, y - posY); + const colXY = !colX && !colY && l < obj.r && this.collisionAtPoint(x - posX, y - posY); + + document.querySelector("footer>p").innerHTML = ` + rx: ${String(rx).slice(0, 4)}
+ ry: ${String(ry).slice(0, 4)}
+ colX: ${colX}
+ colY: ${colY}
+ colXY: ${colXY}
+ x: ${String(this.x).slice(0, 4)}
+ y: ${String(this.y).slice(0, 4)}`; + + if(colXY && l > 0.01) { + // corner collision + obj.x = x + rx*obj.r/l; + obj.y = y + ry*obj.r/l; + } else { + if(colX) { + obj.x = x + Math.sign(rx) * obj.r; + obj.hspd *= -1; + } + if(colY) { + obj.y = y + Math.sign(ry) * obj.r; + obj.vspd *= -1; + } + } + + } + + collisionAtPoint(x, y) { + return this.generator.sampleFloorMap(x, y) != 1; + } } window.addEventListener("load", () => { @@ -68,11 +124,18 @@ window.addEventListener("load", () => { }); async function startGame() { - let gen = new Generator(64); + let audio = new Audio(); + let body = document.querySelector("body"); + body.onkeydown = ev => { + audio.start(); + body.onkeydown = null; + }; + let gen = new Generator(32); let rend = new Renderer(gen, document.querySelector("canvas.game")); await rend.load(); let car = new Car(rend, gen); window.setInterval(() => { car.update(); + audio.update(); }, 1000/30) } \ No newline at end of file diff --git a/modules/generator.js b/modules/generator.js index 268b779e..2830f99c 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -104,12 +104,29 @@ export default class Generator { // return Math.random() < 0.05? .5 : 0; // if(x >= this.size || y >= this.size || x < 0 || y < 0) return 0; const hm = this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)]; - return hm || (Math.sin(y*3.141569*0.25) + Math.sin(x*3.141569*0.25) + 0) * 0.2; + return hm || this.sampleGroundHeightmap(x, y); + } + + sampleGroundHeightmap(x, y) { + return 0 + + (Math.sin(y*3.141569*0.25) + Math.sin(x*3.141569*0.25) + 0) * .2; + // Math.abs(Math.sin(y*3.141569*1) + Math.sin(x*3.141569*1) + 0) * .1 + + // (Math.tan(x) + Math.tan(y)) *0.1 + + // Math.atan(x) + Math.atan(y) + ; } sampleFloorMap(x, y) { return this.floorMap[(mod(y, this.size) * this.size) + mod(x, this.size)]; } + + setFloorMap(x, y, val) { + this.floorMap[(mod(y, this.size) * this.size) + mod(x, this.size)] = val; + } + + setHeightmap(x, y, val) { + this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)] = val; + } } function* iterateQGrid(w, h) { diff --git a/modules/renderer.js b/modules/renderer.js index 43f1e52a..15fcb3ad 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -64,6 +64,7 @@ export default class Renderer { this.tileTexture = Texture.fromImage(await this.loadImage("tilemap")); this.renderFloorTexture(); + this.renderMinimap(); } async loadImage(name, format = "png") { @@ -116,12 +117,13 @@ export default class Renderer { const adjDir = (dir+2)%4; const id = baseID * 2 + this.generator.sampleFloorMap(x + COS[adjDir], y + SIN[adjDir]); // id = 0; + let fac = baseID == 0? (2+COS[dir])/5 : 1; for(let locX = 0; locX < halfW; locX++) { for(let locY = locX; locY < tileH-locX-1; locY++) { const tp = transform(locX, locY, dir); const sp = transform(locX, locY, id); - tex.setAt(x*tileW + tp.x, y*tileH + tp.y, this.tileTexture.sampleAt(sp.x, sp.y)); + tex.setAt(x*tileW + tp.x, y*tileH + tp.y, this.tileTexture.sampleAt(sp.x, sp.y).map(v => v*fac)); } } } @@ -146,6 +148,24 @@ export default class Renderer { this.floorCanvasData = imgData.data; } + renderMinimap() { + let canvas = document.createElement("canvas"); + let ctx = canvas.getContext("2d"); + const w = this.generator.size; + canvas.width = canvas.height = w; + let imgDat = ctx.getImageData(0, 0, w, w); + for(let x = 0; x < w; x++) + for(let y = 0; y < w; y++) { + const i = (y * w + x) * 4; + imgDat.data[i] = + imgDat.data[i+1] = + imgDat.data[i+2] = this.generator.sampleFloorMap(x, y)? 0 : 128; + imgDat.data[i+3] = 255; + } + ctx.putImageData(imgDat, 0, 0); + this.minimap = canvas; + } + sampleFloor(x, y, lum = 1) { x = mod(x * this.tileTexture.width, this.floorCanvas.width); y = mod(y * this.tileTexture.height, this.floorCanvas.height); @@ -182,7 +202,10 @@ export default class Renderer { // const relDistance = Math.tan(screenZ / d * 1.57) * 0.125 * farPlane; // tangent bias const relDistance = (screenZ / d)**2 * farPlane; // square bias const rx = relDistance; - const lum = (1 - screenZ / d); + // const lum = (1 - screenZ / d); + // const lum = screenZ % 2; + // const lum = Math.random(); + const lum = 1-relDistance/farPlane; for (let screenX = 0; screenX < w; screenX++) { const ry = relDistance * (screenX / w * 2 - 1); const x = this.x + cosAngle * rx - sinAngle * ry; @@ -194,14 +217,15 @@ export default class Renderer { const col = this.sampleFloor(x, y, lum); const buf = depthBuffer[screenX]; - let stripe = 18; + const stripe = 18; for (let screenY = buf; screenY < screenHeight; screenY++) { // stripe += Math.random()*0.01; const worldY = (relH * h - 1 - screenY) * relDistance; const i = ((h - screenY) * w + screenX) * 4; - imgData.data[i] = col[0] * ~~(mod(worldY, stripe) * 2 / stripe); - imgData.data[i + 1] = col[1] - imgData.data[i + 2] = col[2]; + const fac = ~~(1 + mod(worldY, stripe) * 2 / stripe); + imgData.data[i] = col[0] * fac; // - Math.random()*20; + imgData.data[i + 1] = col[1] * fac; // - Math.random()*20; + imgData.data[i + 2] = col[2] * fac; // - Math.random()*20; imgData.data[i + 3] = 255; } if (screenHeight > buf) depthBuffer[screenX] = screenHeight; @@ -214,6 +238,8 @@ export default class Renderer { ctx.drawImage(image, ~~(w / 2 - image.width / 2), ~~(h * 0.8 - image.height / 2)); } + ctx.drawImage(this.minimap, 0, 0); + let t = (Date.now() - t0); // console.log(`render time: ${t}ms, ${1000/t}fps`); document.querySelector("footer>p").innerHTML = `render time: ${t}ms, \t${~~(1000 / t)}fps x: ${this.x} y: ${this.y}`; From c20a13fbcbd6c333148586de041f583f1e8cdbdd Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Thu, 5 Sep 2019 18:48:14 +0200 Subject: [PATCH 07/29] Optimize renderer --- modules/renderer.js | 65 +++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/modules/renderer.js b/modules/renderer.js index 15fcb3ad..084384e8 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -1,11 +1,5 @@ import { mod } from "./generator.js"; -const COLOR_TABLE = [ - 0x777777, - 0x121212, - 0x126612, -].map(val => [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff]); - export class Texture { constructor(w, h) { this.data = new Array(w*h); @@ -13,6 +7,7 @@ export class Texture { this.height = h; } + // TODO do this compile time / paste manually static fromImage(img) { let canvas = document.createElement("canvas"); let w = canvas.width = img.width; @@ -74,6 +69,7 @@ export default class Renderer { return img; } + // TODO remove and just use loadImage async loadImages(name, numberOfFrames, format = "png") { let images = []; let promises = []; @@ -129,23 +125,7 @@ export default class Renderer { } } - - let canvas = document.createElement("canvas"); - canvas.width = tex.width; - canvas.height = tex.height; - document.querySelector("footer").append(canvas); - let ctx = canvas.getContext("2d"); - this.floorCanvas = canvas; - let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); - for(let i = 0; i < tex.width * tex.height; i++) { - if(!tex.data[i]) continue; - for(let j = 0; j < 3; j++) { - imgData.data[i*4+j] = tex.data[i][j]; - } - imgData.data[i*4+3] = 255; - } - ctx.putImageData(imgData, 0, 0); - this.floorCanvasData = imgData.data; + this.floorCanvasData = tex; } renderMinimap() { @@ -166,16 +146,12 @@ export default class Renderer { this.minimap = canvas; } - sampleFloor(x, y, lum = 1) { - x = mod(x * this.tileTexture.width, this.floorCanvas.width); - y = mod(y * this.tileTexture.height, this.floorCanvas.height); - let i = (y * this.floorCanvas.width + x) * 4; - return [ - (1-lum) * 0x22 + lum * this.floorCanvasData[i], - (1-lum) * 0x22 + lum * this.floorCanvasData[i+1], - (1-lum) * 0x22 + lum * this.floorCanvasData[i+2], - (1-lum) * 0x22 + lum * this.floorCanvasData[i+3] - ]; + sampleFloor(x, y) { + x = mod(x * this.tileTexture.width, this.floorCanvasData.width); + y = mod(y * this.tileTexture.height, this.floorCanvasData.height); + let sample = this.floorCanvasData.sampleAt(x,y); + // return sample.map(v => (1-lum) * 0x22 + lum * v); + return sample; } render() { @@ -184,8 +160,6 @@ export default class Renderer { const h = this.canvas.height; const d = 180; const farPlane = 12; - // let nearPlane = .1; // units - // let fovRatio = 1; let ctx = this.canvas.getContext("2d"); ctx.clearRect(0, 0, w, h); @@ -195,16 +169,12 @@ export default class Renderer { let depthBuffer = new Array(w).fill(0); - // iterate from z to 1 - // for (let screenZ = d; screenZ >= 0; screenZ--) { + // iterate from front to back for (let screenZ = 0; screenZ <= d; screenZ++) { // iterate from left to right // const relDistance = Math.tan(screenZ / d * 1.57) * 0.125 * farPlane; // tangent bias const relDistance = (screenZ / d)**2 * farPlane; // square bias const rx = relDistance; - // const lum = (1 - screenZ / d); - // const lum = screenZ % 2; - // const lum = Math.random(); const lum = 1-relDistance/farPlane; for (let screenX = 0; screenX < w; screenX++) { const ry = relDistance * (screenX / w * 2 - 1); @@ -215,17 +185,14 @@ export default class Renderer { const relH = (sample - this.z + relDistance) / (relDistance * 2) + this.horizon; const screenHeight = Math.min(~~(relH * h), h); - const col = this.sampleFloor(x, y, lum); + const col = this.sampleFloor(x, y); const buf = depthBuffer[screenX]; - const stripe = 18; for (let screenY = buf; screenY < screenHeight; screenY++) { - // stripe += Math.random()*0.01; const worldY = (relH * h - 1 - screenY) * relDistance; const i = ((h - screenY) * w + screenX) * 4; - const fac = ~~(1 + mod(worldY, stripe) * 2 / stripe); - imgData.data[i] = col[0] * fac; // - Math.random()*20; - imgData.data[i + 1] = col[1] * fac; // - Math.random()*20; - imgData.data[i + 2] = col[2] * fac; // - Math.random()*20; + const fac = ~~(1 + mod(worldY, 18) * 2 / 18); + for(let j = 0; j < 3; j++) + imgData.data[i+j] = (1-lum) * 0x22 + lum * (col[j] * fac); imgData.data[i + 3] = 255; } if (screenHeight > buf) depthBuffer[screenX] = screenHeight; @@ -239,6 +206,10 @@ export default class Renderer { } ctx.drawImage(this.minimap, 0, 0); + ctx.fillStyle = "red"; + ctx.beginPath(); + ctx.rect(mod(this.x, this.generator.size), mod(this.y, this.generator.size), 1, 1); + ctx.fill(); let t = (Date.now() - t0); // console.log(`render time: ${t}ms, ${1000/t}fps`); From 6e30c86ce9ef38402c99b5a9f10019fc4c329d72 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Fri, 6 Sep 2019 14:15:07 +0200 Subject: [PATCH 08/29] Remove import, json -> object in generator --- modules/generator.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/generator.js b/modules/generator.js index 2830f99c..db70193a 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -1,10 +1,8 @@ -import Renderer from "./renderer.js"; - class NoiseLayer { constructor(w, h) { this.width = w; this.height = h; - this.patternTree = JSON.parse('{"0":{"0":{"0":{"0":{"0":{"0":{"0":{"0":{"0":{"score":1}}},"1":{"1":{"1":{"score":1}}}}},"1":{"0":{"0":{"0":{"0":{"score":-1}}}}}},"1":{"1":{"1":{"1":{"1":{"1":{"score":-1}}}}}}},"1":{"0":{"0":{"1":{"0":{"0":{"1":{"score":1}}},"1":{"1":{"1":{"score":1}}}}}}}},"1":{"1":{"0":{"1":{"1":{"0":{"1":{"1":{"score":-1}}}}}}}}},"1":{"0":{"0":{"1":{"0":{"0":{"1":{"0":{"0":{"score":1}},"1":{"1":{"score":1}}}}}}}},"1":{"0":{"1":{"1":{"0":{"1":{"1":{"0":{"score":-1}}}}}}},"1":{"0":{"0":{"0":{"0":{"0":{"0":{"score":1}}}},"1":{"0":{"0":{"1":{"score":1}}}}}},"1":{"0":{"0":{"1":{"0":{"0":{"score":1}}}},"1":{"1":{"1":{"1":{"score":-1}}}}},"1":{"1":{"0":{"0":{"0":{"score":-1}}},"1":{"1":{"1":{"score":-1}}}}}}}}}}'); + this.patternTree = {0:{0:{0:{0:{0:{0:{0:{0:{0:{score:1}}},1:{1:{1:{score:1}}}}},1:{0:{0:{0:{0:{score:-1}}}}}},1:{1:{1:{1:{1:{1:{score:-1}}}}}}},1:{0:{0:{1:{0:{0:{1:{score:1}}},1:{1:{1:{score:1}}}}}}}},1:{1:{0:{1:{1:{0:{1:{1:{score:-1}}}}}}}}},1:{0:{0:{1:{0:{0:{1:{0:{0:{score:1}},1:{1:{score:1}}}}}}}},1:{0:{1:{1:{0:{1:{1:{0:{score:-1}}}}}}},1:{0:{0:{0:{0:{0:{0:{score:1}}}},1:{0:{0:{1:{score:1}}}}}},1:{0:{0:{1:{0:{0:{score:1}}}},1:{1:{1:{1:{score:-1}}}}},1:{1:{0:{0:{0:{score:-1}}},1:{1:{1:{score:-1}}}}}}}}}}; this.noiseOffset = 0; From c9aa3af30e2e403986a47f10917758ed34dfd20b Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Fri, 6 Sep 2019 14:15:56 +0200 Subject: [PATCH 09/29] Remove unnecessary modules in index.html --- index.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/index.html b/index.html index 2612ceac..46ddcc01 100644 --- a/index.html +++ b/index.html @@ -1,9 +1,6 @@ - - - From fe63a14c89587e0ac6a1edadfc373e0d1074c2bd Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Fri, 6 Sep 2019 14:17:13 +0200 Subject: [PATCH 10/29] Remove old build folder --- build/.vscode/settings.json | 3 -- build/images/corolla0000.png | Bin 500 -> 0 bytes build/images/corolla0001.png | Bin 429 -> 0 bytes build/images/corolla0002.png | Bin 503 -> 0 bytes build/images/tilemap.png | Bin 289 -> 0 bytes build/index.html | 53 ----------------------------------- build/style.css | 21 -------------- 7 files changed, 77 deletions(-) delete mode 100644 build/.vscode/settings.json delete mode 100644 build/images/corolla0000.png delete mode 100644 build/images/corolla0001.png delete mode 100644 build/images/corolla0002.png delete mode 100644 build/images/tilemap.png delete mode 100644 build/index.html delete mode 100644 build/style.css diff --git a/build/.vscode/settings.json b/build/.vscode/settings.json deleted file mode 100644 index 6f3a2913..00000000 --- a/build/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "liveServer.settings.port": 5501 -} \ No newline at end of file diff --git a/build/images/corolla0000.png b/build/images/corolla0000.png deleted file mode 100644 index 2d65ee9d4e477065998b8d75398271334a01142e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500 zcmVJwD(1}Fp$iP(@PXp$Ah6SAVBf)E}7%_V2Z z*&KH7Ft@PDm)V`UbI<*mX_J|y2$%DP-CSLVfNjlRNc8T$v2L?Aj5TP705FzN&HG7s%845?}g_pah22c)gu(v;gYY33`3@{?btXcM*>G9MShwlkQp4I1T>h0e? z^4!zIf_Nn2)C{jIJEZ$p3Y qBf?q+k8Olm{(t{FMHsfFX8#59V~FWG;a8{t0000C&jW;TI)s6;8$WMPLBH4AmfT!8hR79=) zYC-@@$`99sgqTM^o+L~>d4z#QvC>eX%hAcP^`N3+%fSm5#d=glRUhV*^ysi(V!qU! zBWA&2wxN2#10H7ij_w5}W<6`zV(m}(um#*`*vlBQVDa+i#?&7w81EV4e5L~56N1cK`4Ujy8BBIU{sPY1e{D6vrE@g^Sgp?ZT3erc>6%;8D;t8oS ztfe_k_H5r7ERAgWmYMa;`4H!vr11JObIbXn6STIC4~UAre-xff=jwOs=Qg13B4HH; zL9Q@r6>X<)Y=8iw_B1)-%F#)9Fd8^yYP0Pk9Ls(B_*A17hP9U15}<~yoKBwqL4Sv} zK99Ya4dEPEGHw>4wlO|+ZLiHI1rV)n#~w=nVPjtb53g-ve058Dc+nVzyq_URW2f zgYCkaGvjN#)5IPBS-nQdwaRQr%i0P}1)4%`#002ovPDHLkV1f?)&7J@N diff --git a/build/images/tilemap.png b/build/images/tilemap.png deleted file mode 100644 index 842ba9664dd70d44a9a1345966e12c7a08bccb56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|>?NMQuI!IFMEMPLXO%HU1BJv( zTq8Wy#Dz8 z`|~>)c~+_0rz)^i{g!T25uWq@ZMJ`aDTl)x78i%tJ-xjTV^UbAXt6RYFfU|WA+=hr z!$OQ#;A%!y&%uTU-U8cW1`apaOdJldC7C`5>OIeP$z(zrbBpsDHj8!*-V-uOCJYR> Xb}9xkRfQS>-NE4L>gTe~DWM4fNpDxe diff --git a/build/index.html b/build/index.html deleted file mode 100644 index ebbe1911..00000000 --- a/build/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - -
-

-
\ No newline at end of file diff --git a/build/style.css b/build/style.css deleted file mode 100644 index e7c84edb..00000000 --- a/build/style.css +++ /dev/null @@ -1,21 +0,0 @@ -canvas { - image-rendering: crisp-edges; - image-rendering: pixelated; -} - -canvas.game { - width: 100%; - height: auto; - padding:0; - margin: 0; - background-color: #222; -} - -body { - padding: 0; - margin: 0; -} - -img.sprite { - display: none; -} From ed6dff305d3909aaaa48ea7d8671364522042f04 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Fri, 6 Sep 2019 14:17:30 +0200 Subject: [PATCH 11/29] Add gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a75d3b37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +build*/ \ No newline at end of file From fa76940ded85d34bcf1e3603aa6be8a0210f0b3a Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Fri, 6 Sep 2019 14:20:12 +0200 Subject: [PATCH 12/29] Remove unused export from key handler --- modules/keyHandler.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/keyHandler.js b/modules/keyHandler.js index 86d80cde..1a604ec6 100644 --- a/modules/keyHandler.js +++ b/modules/keyHandler.js @@ -5,8 +5,7 @@ registerKey("right", [68, 39]); registerKey("up", [87, 38]); registerKey("down",[83, 40]); - -export function registerKey(key, keyCodes) { +function registerKey(key, keyCodes) { keys[key] = false; document.addEventListener("keydown", (ev) => { if(keyCodes.includes(ev.keyCode)) { From 363a2563746f2d22277dba65fbcacb48a68a1313 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Fri, 6 Sep 2019 17:31:30 +0200 Subject: [PATCH 13/29] Add webpack/gulp config --- gulpfile.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 23 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 gulpfile.js create mode 100644 package.json diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..158f013d --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,51 @@ +const { src, dest } = require('gulp'); +const webpack = require('webpack-stream'); +const TerserPlugin = require('terser-webpack-plugin'); + +function defaultTask(cb) { + // place code for your default task here + + src(['modules/*.js']) + .pipe(webpack({ + // Any configuration options... + output: { + filename: 'bundle.js' + }, + optimization: { + minimizer: [ + new TerserPlugin({ + cache: true, + parallel: true, + sourceMap: true, // Must be set to true if using source-maps in production + terserOptions: { + // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions + ecma: 6, + warnings: true, + parse: {}, + compress: {}, + mangle: { + properties: { + reserved: ["keyIsDown", "mod"], // for some reason webpack cant handle these exports + // debug: "", + undeclared: true, + } + }, // Note `mangle.properties` is `false` by default. + module: false, + output: null, + toplevel: false, + nameCache: null, + ie8: false, + keep_classnames: undefined, + keep_fnames: false, + safari10: false, + } + }), + ], + } + })) + .pipe(dest('build')) + + cb(); +} + +exports.default = defaultTask \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..8354d352 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "js13k", + "version": "1.0.0", + "description": "my js13k 2019 entry", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nilsgawlik/js13k2919.git" + }, + "author": "Nils Gawlik", + "license": "", + "bugs": { + "url": "https://github.com/nilsgawlik/js13k2919/issues" + }, + "homepage": "https://github.com/nilsgawlik/js13k2919#readme", + "devDependencies": { + "gulp": "^4.0.2", + "webpack-stream": "^5.2.1" + } +} From 200e14303590af702908f552577d59d0ee7dc94e Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Tue, 10 Sep 2019 18:11:53 +0200 Subject: [PATCH 14/29] Create new audio track --- modules/audio.js | 151 ++++++++++++----------------------------------- modules/game.js | 16 +++-- 2 files changed, 49 insertions(+), 118 deletions(-) diff --git a/modules/audio.js b/modules/audio.js index ad022fac..89c9b049 100644 --- a/modules/audio.js +++ b/modules/audio.js @@ -1,129 +1,52 @@ -const keys = { - aMaj: [0,2,3,5,7,8,10], - eMaj: [-5,-3,-2,0,2,3,5], - hmm: [0,2,5,7,10] -}; - -class Channel { - constructor(audioContext, octave, key, atc, dec, type = "sine", vol) { - this.ctx = audioContext; - this.octave = octave; - this.key = key; - this.atc = atc / 1000; - this.dec = dec / 1000; - this.lastNote = null; - this.vol = vol; - - this.osc = audioContext.createOscillator(); - this.osc.frequency.value = 440 * (2**this.octave) - this.osc.type = type; - - this.gainNode = audioContext.createGain(); - this.gainNode.gain.value = 0; - this.gainNode.connect(this.ctx.destination); - this.osc.connect(this.gainNode); - - this.osc.start(audioContext.currentTime); - } - - triggerNote(note, time) { - this.lastNote = note; - if(note == 0) return; - - this.osc.detune.value = this.key[(note-1)%this.key.length] * 100; - - let t = time || this.ctx.currentTime; - - this.gainNode.gain.setValueAtTime(0, t); - this.gainNode.gain.linearRampToValueAtTime(1/3 * this.vol, t + this.atc); - this.gainNode.gain.linearRampToValueAtTime(0, t + this.atc + this.dec); - - } -} - -class Track { - constructor(notes) { - this.notes = notes; - } - - static notes(...notes) { - return new Track(notes); - } - - cmul(...notes) { - return new Track(Track.convolution(this.notes, notes, (a,b) => a*b)); - } - - cadd(...notes) { - return new Track(Track.convolution(this.notes, notes, (a,b) => (b!=0? a+b : 0))); - } - - cadd2(...notes) { - return new Track(Track.convolution(this.notes, notes, (a,b) => (a!=0? a+b : 0))); - } - - space(length) { - return new Track(Track.convolution(this.notes, new Array(length), (a,b,i,j) => a * (j==0))); - } - - static convolution(notes1, notes2, fnc) { - let newNotes = new Array(notes1.length * notes2.length); - for(let i = 0; i < notes1.length; i++) { - for(let j = 0; j < notes2.length; j++) { - newNotes[i * notes2.length + j] = fnc(notes1[i], notes2[j], i, j); - } - } - return newNotes; - } - - at(beat) { - return this.notes[beat % this.notes.length]; - } -} +let random = Math.random; +let sign = Math.sign; +let sin = Math.sin; +let min = Math.min; +let max = Math.max; +const PI = Math.PI; export default class Audio { constructor() { + this.ctx = new AudioContext(); } start() { // Create an audio context - this.ctx = new AudioContext(); - this.bass = new Channel(this.ctx, -3, keys.aMaj, 1, 124, "square", 0.5); - this.kick = new Channel(this.ctx, -1, keys.aMaj, 1, 500, "sawtooth", 0.25); - this.lead = new Channel(this.ctx, -1, keys.aMaj, 1, 400, "sawtooth", .4); - this.bassTrack = + const srate = 12000; + const length = 1 << 20; + let musicBuffer = this.ctx.createBuffer(1, length, srate); + let data = musicBuffer.getChannelData(0); + + let flavor; + + for(let t = 0; t < length; t++) { + const env = 1-(t % (1<<12) / (1<<12)); + const env2 = 1-(t % (1<<15) / (1<<15)); + const env3 = 1-(t % (1<<10) / (1<<10)); + let hihat1 = (random()*2-1)*env**32 * 0.5; + let hihat2 = (random()*2-1)*env3**32 * 1.9; + let kick = sin(env**6*0x6f)*env; + let snare = (random()*2-1)*env**4; + if(t % (1<<15) == 0) flavor = ~~(random() * 2048); + data[t] = + [[kick,hihat1,snare,hihat1][(t>>12)%4] * 0.4, [kick,snare,hihat1,hihat2][(t>>12)%4] * 0.4][max((t>>14)%4-2, 0)] + + sign(sin((t&((1<<(5+(t>>12)%6))-1))*(1/64)*PI*(1+env2*.25))) * 0.04 + + sign(sin((t&(flavor << 4))*(1/64)*PI)) * 0.04 + // + sign(sin((t&([32,2,3,111][(t>>15)%4] << 4))*(1/64)*PI)) * 0.1 + + data[max(0, t - 2100)] * 0.4 + + data[max(0, t - 2000)] * 0.4 + ; + } - this.beat = 0; - this.bpm = 450; - this.t = 0; - this.lastCtxT = null; + let musicSource = this.ctx.createBufferSource(); + musicSource.buffer = musicBuffer; + musicSource.loop = true; - // window.setInterval(() => this.nextBeat(), 60000/this.bpm); + musicSource.connect(this.ctx.destination); + musicSource.start(); } update() { - if(!this.ctx) return; - - const ct = this.ctx.currentTime; - const delta = ct - (this.lastCtxT || ct); - this.lastCtxT = ct; - - this.t += delta; - this.bpm += delta * 1; - this.bass.dec = ((1+Math.sin(this.t / 4)) / 2 * 80 + 30) / 1000; - - const tNext = this.beat * 60/this.bpm; - if(tNext - this.t < 1) { - this.nextBeat(tNext); - } - } - - nextBeat(time) { - if(this.beat > 32) this.bass.triggerNote(Track.notes(1,5,1,3).cmul(1,1,1).cmul(1,1,2,2).cadd(1,1,1,1).cmul(1,1).at(this.beat), time); - if(this.beat > 64) this.kick.triggerNote(Track.notes(1,0,7,0).at(this.beat), time); - // this.lead.triggerNote(Track.notes(0,7).cadd(1,3,1,5).space(8).cadd2(1,3,5,1).cmul(1,0).at(this.beat), time) - if(this.beat > 0) this.lead.triggerNote(Track.notes(1,5,1,3).cmul(1,0,1,0,0,0,1,0,0,0,0,0).cadd(1,2,3,4).cmul(1,0).at(this.beat), time); - this.beat++; } } \ No newline at end of file diff --git a/modules/game.js b/modules/game.js index 1449487f..ceee8fb7 100644 --- a/modules/game.js +++ b/modules/game.js @@ -9,6 +9,8 @@ const TRACT = 0.1; const FRIC = 0.1; const BACKFRIC = 0.3; +const DEBUG = true; + class Car { constructor(renderer, generator) { this.x = 0; @@ -125,11 +127,17 @@ window.addEventListener("load", () => { async function startGame() { let audio = new Audio(); - let body = document.querySelector("body"); - body.onkeydown = ev => { + // TODO uncomment in final build + if(DEBUG) + { audio.start(); - body.onkeydown = null; - }; + } else { + let body = document.querySelector("body"); + body.onkeydown = ev => { + audio.start(); + body.onkeydown = null; + }; + } let gen = new Generator(32); let rend = new Renderer(gen, document.querySelector("canvas.game")); await rend.load(); From 8425e695e4533ed4d85894ccca8bd4fb5ce1ced9 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Wed, 11 Sep 2019 09:37:01 +0200 Subject: [PATCH 15/29] Make car not spawn in walls --- modules/game.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/game.js b/modules/game.js index ceee8fb7..26fcf19e 100644 --- a/modules/game.js +++ b/modules/game.js @@ -142,6 +142,11 @@ async function startGame() { let rend = new Renderer(gen, document.querySelector("canvas.game")); await rend.load(); let car = new Car(rend, gen); + do { + car.x = ~~(Math.random() * gen.size) + 0.5; + car.y = ~~(Math.random() * gen.size) + 0.5; + } while(gen.sampleFloorMap(car.x, car.y) != 1 || gen.sampleFloorMap(car.x+1, car.y) != 1) + window.setInterval(() => { car.update(); audio.update(); From 58282be021350acf593a8728d11244bbac5ed18c Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Wed, 11 Sep 2019 11:23:26 +0200 Subject: [PATCH 16/29] Implement basic pizza rendering --- images/spritesheet.png | Bin 0 -> 1358 bytes modules/renderer.js | 74 +++++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 images/spritesheet.png diff --git a/images/spritesheet.png b/images/spritesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..5a3571f3a6282bdc58aa042dbbb8b81dee421e64 GIT binary patch literal 1358 zcmV-U1+n^xP)TRQBj`J#4M5hHy0j>wys9T68gh0FsIz zi2%0Ow?Z9!{70Sj>wX0CfY6~6LqY3JE71TKCocy@7XjFV#g|U?YY%UDx|4z!%wQnj zcIHc70=)nr@BgDR^Z%fB~*xc>WZQQ7k+&vw;=GI(=#eUI?NcSSmcy#Nn3;QGBcUndP<hP4fc!x<_*cB$|g0>`M{=l0*rn;DV4uplip&0*>4B0+2|O5?G$-hJ0RR~)w=X4uNG=r#b@5nNHPgB2l07=RrLbU{>AF5} zT>g&VYXa!Mx3bW;ldScbDt_Jg9Alx`j4)T5ivXXY9PP~DP;mU*u+xovTzb{^ohNmw zB=6_uVgxA}wCw}z*O!LV{em?b4dZhwy2X0|V@FPD;bH@WM}w_*p8^npAl2x|q z0&pdto(-@S02mj6>t>KdKJU*b`6d#RlAnuLpnC*-i&!ex{pOMsN+Pfk17Gi#&`I)b z>bSW5j*9?hE|Mpm12BOu4ZC;&Fd#}N1Xo~xzMho}DOtpP?;-%gaL+&9`~P=T46hPs z9p2tm1hAff#JLKBx5NH?JgX#RWRdWB6#?$<;XVO}&3*wrKmvV1pQdqt8EDTtmfNspPh`<*)9e)ep$HHB60Dfk`X3TCQom-YGdbzF+nhu8~h3h@| zxOU2tuKwjlIycDPDS7-Tmvp>~mhrY}?xpBt+gRZVmn(^b07*qoM6N<$g5zmsHvj+t literal 0 HcmV?d00001 diff --git a/modules/renderer.js b/modules/renderer.js index 084384e8..5fdf8a54 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -8,13 +8,14 @@ export class Texture { } // TODO do this compile time / paste manually - static fromImage(img) { - let canvas = document.createElement("canvas"); - let w = canvas.width = img.width; - let h = canvas.height = img.height; - let ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0); - let data = ctx.getImageData(0, 0, w, h).data; + static fromCanvas(canvas) { + // let canvas = document.createElement("canvas"); + // let w = canvas.width = img.width; + // let h = canvas.height = img.height; + // ctx.drawImage(img, 0, 0); + let w = canvas.width; + let h = canvas.height; + let data = canvas.getContext("2d").getImageData(0, 0, w, h).data; let texture = new Texture(w, h); for(let i = 0; i < w * h; i++) { texture.data[i] = [ @@ -49,14 +50,26 @@ export default class Renderer { this.angle = 0; this.horizon = 0.2; + this.pizzaX = 3.5; + this.pizzaY = 3.5; + this.carFrame = 1; this.floorCanvas = null; + this.spritesheet = null; } async load() { - this.imgCorolla = await this.loadImages("corolla", 3); + this.spritesheet = await this.loadImage("spritesheet"); + this.imgCorolla = [ + this.getImageFromSpritesheet(0, 0, 32, 16), + this.getImageFromSpritesheet(0, 16, 32, 16), + this.getImageFromSpritesheet(0, 0, 32, 16, true) + ]; + + this.imgPizza = this.getImageFromSpritesheet(32, 0, 32, 32); + // load tile data - this.tileTexture = Texture.fromImage(await this.loadImage("tilemap")); + this.tileTexture = Texture.fromCanvas(await this.getImageFromSpritesheet(64, 0, 16, 16)); this.renderFloorTexture(); this.renderMinimap(); @@ -69,21 +82,15 @@ export default class Renderer { return img; } - // TODO remove and just use loadImage - async loadImages(name, numberOfFrames, format = "png") { - let images = []; - let promises = []; - for (let i = 0; i < numberOfFrames; i++) { - let img = document.createElement("img"); - img.src = `images/${name}${("000" + i).slice(-4)}.${format}`; - promises.push(new Promise(resolve => { img.onload = resolve })); - images.push(img); - } - - for (let promise of promises) { - await promise; - } - return images; + getImageFromSpritesheet(x, y, w, h, flipX = false) { + let canvas = document.createElement("canvas"); + // x = w/2; y = h/2; + canvas.width = w; + canvas.height = h; + let ctx = canvas.getContext("2d"); + if(flipX) ctx.scale(-1,1); + ctx.drawImage(this.spritesheet, -x - (flipX? w : 0), -y); + return canvas; } renderFloorTexture() { @@ -169,6 +176,17 @@ export default class Renderer { let depthBuffer = new Array(w).fill(0); + // Crazy pizza math + const pizzaRY = (this.pizzaY - (this.y + sinAngle * (this.pizzaX - this.x) / cosAngle)) / (sinAngle * sinAngle / cosAngle + cosAngle); + const pizzaRX = (this.pizzaX - this.x + sinAngle * pizzaRY) / cosAngle; + const pizzaSX = ((pizzaRY / pizzaRX) + 1) / 2 * w; + let pizzaSZ = Math.sqrt(pizzaRX / farPlane) * d; + + const samplePizza = this.generator.sampleHeightmap(this.pizzaX, this.pizzaY); + const relPizzaH = (samplePizza - this.z + pizzaRX) / (pizzaRX * 2) + this.horizon; + const pizzaScale = 1 / pizzaRX; + const pizzaSY = ~~(relPizzaH * h); + // iterate from front to back for (let screenZ = 0; screenZ <= d; screenZ++) { // iterate from left to right @@ -202,7 +220,13 @@ export default class Renderer { ctx.putImageData(imgData, 0, 0); if (this.imgCorolla) { let image = this.imgCorolla[this.carFrame]; - ctx.drawImage(image, ~~(w / 2 - image.width / 2), ~~(h * 0.8 - image.height / 2)); + ctx.drawImage(image, ~~(w / 2 - image.width / 2), ~~(h * 0.8 - image.height / 2), image.width, image.height); + } + + if(pizzaSZ > 0) { + let pizzaW = this.imgPizza.width * pizzaScale; + let pizzaH = this.imgPizza.height * pizzaScale; + ctx.drawImage(this.imgPizza, pizzaSX - pizzaW/2, h-pizzaSY - pizzaH/2, pizzaW, pizzaH); } ctx.drawImage(this.minimap, 0, 0); From 0e6eebea4b98a96d8c424f0bd3dd3ca78bf919b6 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Wed, 11 Sep 2019 12:50:28 +0200 Subject: [PATCH 17/29] upgrade the music with glitchy sounds --- modules/audio.js | 54 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/modules/audio.js b/modules/audio.js index 89c9b049..09ece80d 100644 --- a/modules/audio.js +++ b/modules/audio.js @@ -19,6 +19,40 @@ export default class Audio { let data = musicBuffer.getChannelData(0); let flavor; + + let b = 0; + for(let i = 0; i < 5; i++) { + b = b | ~~(random() * 4); + b = b << 2; + } + // look at those magic numbers! + let bs = [ + 773752, // very fast upbeat + 773752, // very fast upbeat + 773752|0x7f, // very fast upbeat + 957758312, // fast ish + + 773752, // very fast upbeat + 773752, // very fast upbeat + 773752|0x7f, // very fast upbeat + 957758312, // fast ish + + 792323628, // noisy? slow + 792323628, // noisy? slow + 957758312, // fast ish + 957758312, // fast ish + + 1008437436, // fast ish synth + 1008437436, // fast ish synth + 773752|0x7f, // very fast upbeat + 957758312, // fast ish + + // -409420504, // interesting medium + // 792323628,// noisy? slow + // 345849296, // continuus subbase + // 1962361744, // fast tension + ]; + // let bs = [345849296,1962361744,957758312,792323628,773752,1008437436,773752,792323628,773752,792323628]; for(let t = 0; t < length; t++) { const env = 1-(t % (1<<12) / (1<<12)); @@ -29,15 +63,29 @@ export default class Audio { let kick = sin(env**6*0x6f)*env; let snare = (random()*2-1)*env**4; if(t % (1<<15) == 0) flavor = ~~(random() * 2048); - data[t] = + if(t % (1<<16) == 0) { + // this is how magic numbers are originally made! + // for(let i = 0; i < 5; i++) { + // b = b | ~~(random() * 4); + // b = b << 2; + // } + // bs.push(b); + b = bs.shift(); + }; + let tt = t; + t = t & b; + data[tt] = [[kick,hihat1,snare,hihat1][(t>>12)%4] * 0.4, [kick,snare,hihat1,hihat2][(t>>12)%4] * 0.4][max((t>>14)%4-2, 0)] - + sign(sin((t&((1<<(5+(t>>12)%6))-1))*(1/64)*PI*(1+env2*.25))) * 0.04 - + sign(sin((t&(flavor << 4))*(1/64)*PI)) * 0.04 + + sign(sin((t&((1<<(5-(t>>14)%3+(t>>12)%6))-1))*(1/64)*PI*(1+env2*.25))) * 0.05 + + sign(sin((t&(flavor << 4))*(1/64)*PI)) * 0.05 // + sign(sin((t&([32,2,3,111][(t>>15)%4] << 4))*(1/64)*PI)) * 0.1 + data[max(0, t - 2100)] * 0.4 + data[max(0, t - 2000)] * 0.4 ; + t = tt; } + + console.log(bs.toString()); let musicSource = this.ctx.createBufferSource(); musicSource.buffer = musicBuffer; From 0733d56aafa4e0e4eb4d38cb2cdda2fdbfa56124 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Wed, 11 Sep 2019 12:59:19 +0200 Subject: [PATCH 18/29] Remove unused images --- images/corolla.gal | Bin 1160 -> 0 bytes images/corolla.png | Bin 1332 -> 0 bytes images/corolla0000.png | Bin 438 -> 0 bytes images/corolla0001.png | Bin 370 -> 0 bytes images/corolla0002.png | Bin 438 -> 0 bytes images/spritesheet.png | Bin 1358 -> 1338 bytes images/tilemap.png | Bin 274 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 images/corolla.gal delete mode 100644 images/corolla.png delete mode 100644 images/corolla0000.png delete mode 100644 images/corolla0001.png delete mode 100644 images/corolla0002.png delete mode 100644 images/tilemap.png diff --git a/images/corolla.gal b/images/corolla.gal deleted file mode 100644 index fb59e89131984188e0ea9b476ddd7b2b4c728da4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1160 zcmV;31b6#KVQghsGB7YE0RRAa0qs)FPJ}QJzMJ?Clim$1xN-p#VYBOjtVTEM(bT0% zW1$U26MTBVwyXy|_yEd<&UEJcOY`->J7H-r1}k-6uq2KdJ;-Kk3zjB~u7!MQ$AT>` z7?s9g;G9vV-+J^?M)y;HxaA7>jP7{f=uUo!reFZ6bZ2&x5nn|u-UTLcRc@?rt8q?pjc9y~ptx?7(mh z32kP9jHx0I!_Zn}%z`oEv;^s>1MBM-l2`)~Sf1zFlKbeGVI^yZ{Jnzt7#{Dq#}Kfuk618bPd4Hty{zWcE~JF` zfgW3+y%&lLODLK20EoFn{J@8jcO{`t^0Dv(6nhYPwCmt5ET?K_nZRa_`>~Qb<;oMC z8gqIT7j{Z5^R&Yix|-$dveeCAr0AVw00000D**ricmb7>!41Md3#*re<PcV#sKOlw8AXe a`;*y{AL-}&u;s}7_skPp4Wj!10002_MlxXl diff --git a/images/corolla.png b/images/corolla.png deleted file mode 100644 index 6899aecded5a5af7f6ae41ebc8704b1bf6508848..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1332 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5s!3-qtrF5MJQtTz3zOL+#IRpfaRNMbfTMQHu zFL8}13C>R|DNig)WpMX#0UD!FmRh7>tY=c#y5u2H2}enRpF2>-7|3SuYS5GiQY`6? zzK#qG8~eHcB(gFvFf#=Bgt*G^NzCk@n&B65<<5-^10NkdofYfWH00H6VD|uN9R;Hy zFsws>T{6=G=t0ILZ+DmfOW8ji1+qIlT^vIy=9Gp+Zab>L5uEbkU-%y;t)tQLc2zP? zN(*wQY?x=T>p+g7!H#$FkE8oaUL3oyPQLfzZnpFU6T4R&E1Be(rQerH^vJg_TK}tX zL!m^&mc!E>?Y}B6{I&nzbwwt>_IYe)Y)(&~9d|UZNGhZ@fRC9`)-lsSnlaHlqV}+6 z*vdV}9)5e~8Mg0vO8E6;8?$Lw*Sl~WK9Kv&#Bi!1vfPh^)_TOIcM=kD=S-~t5mYHEnXM;X!y)jXW|Ndm# zmwlRUA=L)qS~s|L)djhG+OO?Xu;tYgNtb1-5{=Tg47g_em*;y-<<#V&`XU#}?gQ7> o)L*WO*{Lh$`pj4Uh zm$*ih1m~xflqVLYGPwJ=0F6;7OD$3`)-x$=UGfm9grlUu&mAaZ3}iESHE7BMDVB6c zUq=Rpjs4tz5?O(Kwj^(N7l!{JxM1({$v_(gJzX3_D&{PmdYiY|fXDUob=DhAu3BaZ zf^3OA`*!g@-m%MLc~P^o&+kIfTNZY8tX(sw_DoCNSQ;BVDffIl*RI^zZ-3`SRUh(W z%*bWEcs8hS(*=$-_XYQGUOgDUkYmRc7XQmBE3TgB+40WwwMAbU({?tOS#F!Wwn|Lc|CmmUfG&%CoH_1l5+BYXWDt9LOk`%_;(_Z?f< cemUj6j2ZcBmgIk7e+>#oPgg&ebxsLQ0E}?ABLDyZ diff --git a/images/corolla0001.png b/images/corolla0001.png deleted file mode 100644 index 3043d13a5960747a316404deae6072ebace211bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 370 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw!3HFS-u9~jDfSXiUsv|W9FqLTvUfx7^#Fy$ zOI#yLg7ec#$`gxH8QgtbfW|14r4}g|>zNd`E_n!4!ckJ-=MI!H2C^Bv8Z_mB6id3J zuOkD)#(wTUiL5|ATavfC3&Vd9T(EcfWT41dPZ!6KiaA@SoaSX#W-f757L>if z(Hg+Eyu{hk#ZBsoX`4>eQ-fPad*tJt*P34P(lph0p5s-eb4+B#*1jh}UgF0;^l$s} z@xNFNlWR3=JK`;kYSm`&D;%@P6utlTp~3MS!71Lmwa*=Nm{HnZ7x%u~iQ{g^ zdexT~f1Xj1o9Ld@{Jm~c@wq8aQ`t7p@Qi5F<2VwYus3(o!NN)Aj?xOY#-|rsE=k<) z^~0~Td_#?NL8{;Ph`6nVcf&gUO||^l)?Uw2*Z!XWK>xNwOyq}CJU@UQWAJqKb6Mw< G&;$VbIftj4Uh zm$*ih1m~xflqVLYGPwJ=0F6;7OD$3`)-x$=UGfm9grlUu&mAaZ3}iESHE7BMDVB6c zUq=Rpjs4tz5?O(Kwj^(N7l!{JxM1({$v_(gJzX3_D(1|cdRMSTfv2T*H`|*-Zh3DG zxak}*s@avN(>^=(S#r`7_lg^grf<}w4`{X+T;1@#+M@5VsL1U{%&r?h+bGXG_IlT& z8%%}HhlE-egwKdr82a}yD@(_(tg0hNKYCR}PIK_D&A-nv{nx&A*-k>oqWs@*c5DpG zyIt0N&G7p7OUG(Bz8{!;Gez)g=gn1<_wKVdb@=kTM%hJ8E^*#-_8)FFLSh?^hfi2N zjj#tZ@wanjEB*d;hey^-Byw!5L?k2gAdLf3>ZXNCa{QgPF cD&{@pKX_x!l6hM<{{#i2r>mdKI;Vst0NY!*SpWb4 diff --git a/images/spritesheet.png b/images/spritesheet.png index 5a3571f3a6282bdc58aa042dbbb8b81dee421e64..b77dccf6aababbe4c5d2d5a35845fba1d73dc96f 100644 GIT binary patch delta 1152 zcmV-`1b_R^3c3oAE(R$%l7|YAku+QZm64Gse}IG_R@!M5qLAW15Q{@1Nf8j?2(j}g z$O$O|i6Rj}#DEe|bj_^JGB=s`yZdhMb{+WGySMLW@4oNN%)a-c$kJ|(PsR7|PFn%$ z+V)$tUBA5?zkR*!h0)Q`uKAsr?9tTUvgK;yHUi+e0A6>zNJol*3jjdNIDquh2|$&| z0mjZ>N&p1oX9R`_3Y}=Mj&DdF5P)!hw!- zSD1vL699^UelME@bjV=G)h|>D+za?eQ=b!DW2W9Z)09U|jV&*-M6X17RMgir}Zu-h;F;s97m^=VS**riblbZr0e_MG0 zNF+%Kpz1Awih<5{^a3>A(^pOU3_RZJh@>-H1ydcG6I5ORK!(cgOGzM-OGQFmJl0js zbZ)w2kIrr>ESP4xt`8iSzvEX;0KNBC7J7D)wH{N&uN$9JEHoMkW^1zv;4_q?o#`Kl zj-MNJdXSGxuUfwIq)wIO{oGtke;_4;wtay8`tro-dCOwyUcktaQ(CxKU;oi)>)oda z#AVwCGxgfc-#3cGlUG~r<9EpXxv7X&^})qfRq}H+9xiO#=|VsruAcY9LjZT_RSN;u z#*;o%Vrbj1wCLkPyh$`Zi$i&siD*}ZdQK~&Yy7I6KfV_LpKbbvjNN6`f6pZ`GzkF4 z4*9=Tj(oZjoM4~VGg?I(JN5Hp;rE=Xpm!wb>=)1jB+wW1X&M(vQe8Q*Dwz7o z(|n1@>e7P>0NuBABn-EEea;N%#x#)@>YCNE5K&ZHqw&3d$=&mre{OSTKs8>sl8@Hz zclO}pN&Ee}rC-}K1G+KGB7#ulwEZoB9}9QQ0r;5#n=!kMbZ&XB=oPv;XgVC?3)g1| zac!6RuKpE9IycDPDS7-Tmvp>~mhrY(o~6^twy};UTryu0xd1{wrE@s02e1O2SpbF*n`EFPW5XKZ+N#eUI?NcSSmcy#Nn3;QGBcUndP<Nu0wjOi z^8%1ak`h4GTLKjWo$craXuPMdn)Ducyw?#)XSNEaIy5Kfc>w?!Dz`5sfk-YD33c&U zS2feQ>5@Gyz+Jjc2uSmsiWUN_jVFDk#L%{1Y0<}pc#~+7EDmL1CZb*8>pp+2jIQyk zcK-NY0DQLT8!~p6RX>-+&?EpD+vjuZIr8bcKvp84uXNJFkFJi+_4_6tt>?(kLIfxT zNf0^@Ac;eA`<)=4l9!hJ+=PGva3!Cf4X_me7#D%-W{^Za@6RXsCK8j9pNm(Zdjx!o zSSr{3=8_ajBCrtyU+bSW5j*9?hE|Mpm12BOu4ZC;&Fd#}N1Xo~xzMho} zDOtpP?;-%gaL+&9`~P=T46hPs9p2tm1hAff#JLKBx5NH?JgX#RWRdWB6#?$<;XVO} z&3*wrKmvV1pQdqmM1`kmeTc+!5qZt2(d%z$ppvWUPJIURos;K#yUa{zv3 zz-G*DBb{58D|)%E4w?>!B!%le__%h;lCJ*cMmjgh-YI$fD3^4+iiCh3a@A1E-vp*EghBtd3TCpVn0000?NMQuI!IFB>B13d5-Ts3KSAA zag8Vm&QB{TPb^AhaQATm8lzB_TBKmCXHwX@!lvI6;RN#5=*4F5rJ!QSPQfg-t{E{-7;bCOqN*cW~MQ?4W!V8|>Wy#Dz8 z`|~>)dBomZoOa;Qi3np?nfc()4iV|5RgMggR1}QZm@UL58DoRE*c{k)vH93K2r@}c z`0B8u#nj=;ftxOw4s6bD%oCiO*f`oFcnkOfr6yP&$zWjknIIc9OW_v}&|(HpS3j3^ HP6 Date: Wed, 11 Sep 2019 15:43:25 +0200 Subject: [PATCH 19/29] Implement super basic gameplay loop --- gulpfile.js | 2 +- modules/game.js | 85 ++++++++++++++++++++++++++++++++++++++++---- modules/generator.js | 2 +- modules/renderer.js | 84 +++++++++++++++++++++++++++++++++---------- style.css | 12 +++---- 5 files changed, 152 insertions(+), 33 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 158f013d..a323b9d7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,7 +25,7 @@ function defaultTask(cb) { compress: {}, mangle: { properties: { - reserved: ["keyIsDown", "mod"], // for some reason webpack cant handle these exports + reserved: ["keyIsDown", "mod", "iterateQGrid"], // for some reason webpack cant handle these exports // debug: "", undeclared: true, } diff --git a/modules/game.js b/modules/game.js index 26fcf19e..4dba1f20 100644 --- a/modules/game.js +++ b/modules/game.js @@ -1,4 +1,5 @@ import Generator from "./generator.js"; +import {iterateQGrid} from "./generator.js"; import Renderer from "./renderer.js"; import { keyIsDown } from "./keyHandler.js"; import Audio from "./audio.js"; @@ -9,7 +10,9 @@ const TRACT = 0.1; const FRIC = 0.1; const BACKFRIC = 0.3; -const DEBUG = true; +const DEBUG = true; // TODO flip + +const MAP_SIZE = 32; class Car { constructor(renderer, generator) { @@ -121,6 +124,77 @@ class Car { } } +class GameManager { + constructor(renderer, generator, car) { + this.level = 2; + this.pizzasCollected = 0; + this.pizzasToCollect = 0; + this.renderer = renderer; + this.generator = generator; + this.car = car; + this.pizzas = []; + this.startObj = null; + } + + startLevel() { + // start of level + this.pizzasToCollect = this.level; + this.pizzasCollected = 0; + this.placeObjects(); + this.renderer.setText("Collect the pizzas!", "red"); + } + + placeObjects() { + this.pizzas = []; + let positions = []; + for(let p of iterateQGrid(MAP_SIZE/4, MAP_SIZE/4)) { // TODO go back to normal size + p.x += 0.5; + p.y += 0.5; + positions.push(p); + } + // only use streets + positions = positions.filter(p => this.generator.sampleFloorMap(p.x, p.y) == 1); + + // select a few random positions for pizzas + for(let i = 0; i < this.pizzasToCollect; i++) { + let pizza = positions.splice(~~(Math.random() * positions.length), 1)[0]; + pizza.spriteIndex = 0; + this.pizzas.push(pizza); + } + + if(!this.startObj) { + // select the start position + this.startObj = positions.splice(~~(Math.random() * positions.length), 1)[0]; + this.startObj.spriteIndex = 1; + this.car.x = this.startObj.x; + this.car.y = this.startObj.y; + } + + this.renderer.pizzaPositions = this.pizzas; + } + + update() { + if(this.pizzas.length > 0) { + for(let pizza of this.pizzas) { + if((pizza.x - this.car.x)**2 + (pizza.y - this.car.y)**2 < 0.25**2) { + pizza.collected = true; + this.pizzasCollected++; + this.renderer.setSuperText(`${this.pizzasCollected} / ${this.pizzasToCollect} pizzas\ncollected!!!`) + } + } + this.pizzas = this.pizzas.filter(p => !p.collected); + this.renderer.pizzaPositions = this.pizzas; + } else { + this.renderer.setText("Return to start!", "yellow") + this.renderer.pizzaPositions = [this.startObj]; + if((this.startObj.x - this.car.x)**2 + (this.startObj.y - this.car.y)**2 < 0.4**2) { + this.level++; + this.startLevel(); + } + } + } +} + window.addEventListener("load", () => { startGame(); }); @@ -138,16 +212,15 @@ async function startGame() { body.onkeydown = null; }; } - let gen = new Generator(32); + let gen = new Generator(MAP_SIZE); let rend = new Renderer(gen, document.querySelector("canvas.game")); await rend.load(); let car = new Car(rend, gen); - do { - car.x = ~~(Math.random() * gen.size) + 0.5; - car.y = ~~(Math.random() * gen.size) + 0.5; - } while(gen.sampleFloorMap(car.x, car.y) != 1 || gen.sampleFloorMap(car.x+1, car.y) != 1) + let gm = new GameManager(rend, gen, car); + gm.startLevel(); window.setInterval(() => { + gm.update(); car.update(); audio.update(); }, 1000/30) diff --git a/modules/generator.js b/modules/generator.js index db70193a..d0ea336b 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -127,7 +127,7 @@ export default class Generator { } } -function* iterateQGrid(w, h) { +export function* iterateQGrid(w, h) { for (let x = 0; x < w; x++) for (let y = 0; y < h; y++) { yield { x: x, y: y, index: y*w+x}; diff --git a/modules/renderer.js b/modules/renderer.js index 5fdf8a54..f171db31 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -40,6 +40,8 @@ export class Texture { } } +const SUPER_TEXT_TIME = 120; + export default class Renderer { constructor(generator, canvas) { this.generator = generator; @@ -50,14 +52,26 @@ export default class Renderer { this.angle = 0; this.horizon = 0.2; - this.pizzaX = 3.5; - this.pizzaY = 3.5; + this.infoText = ""; + this.infoTextStyle = "red"; + this.pizzaPositions = []; + this.startPos = null; this.carFrame = 1; this.floorCanvas = null; this.spritesheet = null; } + setText(str, style) { + this.infoText = str.split("").join(" "); + this.infoTextStyle = style; + } + + setSuperText(str) { + this.superText = str;// str.split("").join(" "); + this.superTextTimer = SUPER_TEXT_TIME; + } + async load() { this.spritesheet = await this.loadImage("spritesheet"); this.imgCorolla = [ @@ -66,7 +80,9 @@ export default class Renderer { this.getImageFromSpritesheet(0, 0, 32, 16, true) ]; - this.imgPizza = this.getImageFromSpritesheet(32, 0, 32, 32); + this.indexedSprites = []; + this.indexedSprites[0] = this.getImageFromSpritesheet(32, 0, 32, 32); + this.indexedSprites[1] = this.getImageFromSpritesheet(80, 0, 32, 32); // load tile data this.tileTexture = Texture.fromCanvas(await this.getImageFromSpritesheet(64, 0, 16, 16)); @@ -169,6 +185,7 @@ export default class Renderer { const farPlane = 12; let ctx = this.canvas.getContext("2d"); + ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, w, h); let imgData = ctx.getImageData(0, 0, w, h); const cosAngle = Math.cos(this.angle); @@ -176,17 +193,6 @@ export default class Renderer { let depthBuffer = new Array(w).fill(0); - // Crazy pizza math - const pizzaRY = (this.pizzaY - (this.y + sinAngle * (this.pizzaX - this.x) / cosAngle)) / (sinAngle * sinAngle / cosAngle + cosAngle); - const pizzaRX = (this.pizzaX - this.x + sinAngle * pizzaRY) / cosAngle; - const pizzaSX = ((pizzaRY / pizzaRX) + 1) / 2 * w; - let pizzaSZ = Math.sqrt(pizzaRX / farPlane) * d; - - const samplePizza = this.generator.sampleHeightmap(this.pizzaX, this.pizzaY); - const relPizzaH = (samplePizza - this.z + pizzaRX) / (pizzaRX * 2) + this.horizon; - const pizzaScale = 1 / pizzaRX; - const pizzaSY = ~~(relPizzaH * h); - // iterate from front to back for (let screenZ = 0; screenZ <= d; screenZ++) { // iterate from left to right @@ -222,11 +228,27 @@ export default class Renderer { let image = this.imgCorolla[this.carFrame]; ctx.drawImage(image, ~~(w / 2 - image.width / 2), ~~(h * 0.8 - image.height / 2), image.width, image.height); } + + // draw pizzas - if(pizzaSZ > 0) { - let pizzaW = this.imgPizza.width * pizzaScale; - let pizzaH = this.imgPizza.height * pizzaScale; - ctx.drawImage(this.imgPizza, pizzaSX - pizzaW/2, h-pizzaSY - pizzaH/2, pizzaW, pizzaH); + for(let pizzaPos of this.pizzaPositions) { + // Crazy pizza math + const pizzaRY = (pizzaPos.y - (this.y + sinAngle * (pizzaPos.x - this.x) / cosAngle)) / (sinAngle * sinAngle / cosAngle + cosAngle); + const pizzaRX = (pizzaPos.x - this.x + sinAngle * pizzaRY) / cosAngle; + const pizzaSX = ((pizzaRY / pizzaRX) + 1) / 2 * w; + const pizzaSZ = Math.sqrt(pizzaRX / farPlane) * d; + + const samplePizza = this.generator.sampleHeightmap(pizzaPos.x, pizzaPos.y); + const relPizzaH = (samplePizza - this.z + pizzaRX) / (pizzaRX * 2) + this.horizon; + const pizzaScale = 1 / pizzaRX; + const pizzaSY = ~~(relPizzaH * h); + + if(pizzaSZ > 0) { + let img = this.indexedSprites[pizzaPos.spriteIndex]; + let pizzaW = img.width * pizzaScale; + let pizzaH = img.height * pizzaScale; + ctx.drawImage(img, pizzaSX - pizzaW/2, h-pizzaSY - pizzaH/2, pizzaW, pizzaH); + } } ctx.drawImage(this.minimap, 0, 0); @@ -235,6 +257,32 @@ export default class Renderer { ctx.rect(mod(this.x, this.generator.size), mod(this.y, this.generator.size), 1, 1); ctx.fill(); + // small normal text + ctx.font = "8px Arial"; + ctx.textAlign = "center"; + let a = (t0%100)/100*Math.PI; + let tx = w/2 + Math.cos(a); + let ty = h-5 + Math.sin(a); + ctx.strokeStyle = "black"; + ctx.fillStyle = this.infoTextStyle; + ctx.strokeText(this.infoText, tx, ty); + ctx.fillText(this.infoText, tx, ty); + + // super text + this.superTextTimer--; + if(this.superTextTimer > 0) { + let sc = Math.sin((this.superTextTimer/SUPER_TEXT_TIME)**8 * Math.PI); + let rot = (1 - this.superTextTimer/SUPER_TEXT_TIME) * .5; + ctx.translate(w/2, h/2); + ctx.rotate(rot); + ctx.scale(sc,sc); + ctx.font = "32px Arial"; + ctx.strokeStyle = "2px solid red"; + ctx.fillStyle = "white"; + ctx.strokeText(this.superText, 0, 0, w*0.8); + ctx.fillText(this.superText, 0, 0, w*0.8); + } + let t = (Date.now() - t0); // console.log(`render time: ${t}ms, ${1000/t}fps`); document.querySelector("footer>p").innerHTML = `render time: ${t}ms, \t${~~(1000 / t)}fps x: ${this.x} y: ${this.y}`; diff --git a/style.css b/style.css index e7c84edb..836410c1 100644 --- a/style.css +++ b/style.css @@ -4,18 +4,16 @@ canvas { } canvas.game { + display: block; + margin: 0 auto; width: 100%; + max-width: 768px; height: auto; padding:0; - margin: 0; background-color: #222; } body { padding: 0; - margin: 0; -} - -img.sprite { - display: none; -} + background-color: #222; +} From 7ebbbef2dfbb6ec6feb99b9e34b5843a52154b86 Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Wed, 11 Sep 2019 16:08:08 +0200 Subject: [PATCH 20/29] Change pcg to contained map --- modules/generator.js | 19 +++++++++++++------ modules/renderer.js | 10 +++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/modules/generator.js b/modules/generator.js index d0ea336b..22a963d7 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -91,6 +91,12 @@ export default class Generator { // calculate height map for (let p of iterateQGrid(size, size)) { + let distanceToEdge = Math.min(p.x, p.y, size-1-p.x, size-1-p.y); + if(distanceToEdge == 0) + layers[0].data[p.index] = 0; + else if(distanceToEdge == 1) + layers[0].data[p.index] = 1; + let height = layers.map((sim) => (1 - sim.data[p.index])).reduce((a, b) => a + b); height = layers[0].data[p.index] ? 0 : height; this.heightmap[p.index] = height; @@ -101,7 +107,7 @@ export default class Generator { // return 0; // return Math.random() < 0.05? .5 : 0; // if(x >= this.size || y >= this.size || x < 0 || y < 0) return 0; - const hm = this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)]; + const hm = this.heightmap[(clamp(y, this.size) * this.size) + clamp(x, this.size)]; return hm || this.sampleGroundHeightmap(x, y); } @@ -115,15 +121,15 @@ export default class Generator { } sampleFloorMap(x, y) { - return this.floorMap[(mod(y, this.size) * this.size) + mod(x, this.size)]; + return this.floorMap[(clamp(y, this.size) * this.size) + clamp(x, this.size)]; } setFloorMap(x, y, val) { - this.floorMap[(mod(y, this.size) * this.size) + mod(x, this.size)] = val; + this.floorMap[(clamp(y, this.size) * this.size) + clamp(x, this.size)] = val; } setHeightmap(x, y, val) { - this.heightmap[(mod(y, this.size) * this.size) + mod(x, this.size)] = val; + this.heightmap[(clamp(y, this.size) * this.size) + clamp(x, this.size)] = val; } } @@ -134,6 +140,7 @@ export function* iterateQGrid(w, h) { } } -export function mod(x, n) { - return ~~(((x%n)+n)%n); +export function clamp(x, n) { + return Math.min(Math.max(~~x, 0), n-1); + // return ~~(((x%n)+n)%n); } \ No newline at end of file diff --git a/modules/renderer.js b/modules/renderer.js index f171db31..517459de 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -1,4 +1,4 @@ -import { mod } from "./generator.js"; +import { clamp } from "./generator.js"; export class Texture { constructor(w, h) { @@ -170,8 +170,8 @@ export default class Renderer { } sampleFloor(x, y) { - x = mod(x * this.tileTexture.width, this.floorCanvasData.width); - y = mod(y * this.tileTexture.height, this.floorCanvasData.height); + x = clamp(x * this.tileTexture.width, this.floorCanvasData.width); + y = clamp(y * this.tileTexture.height, this.floorCanvasData.height); let sample = this.floorCanvasData.sampleAt(x,y); // return sample.map(v => (1-lum) * 0x22 + lum * v); return sample; @@ -214,7 +214,7 @@ export default class Renderer { for (let screenY = buf; screenY < screenHeight; screenY++) { const worldY = (relH * h - 1 - screenY) * relDistance; const i = ((h - screenY) * w + screenX) * 4; - const fac = ~~(1 + mod(worldY, 18) * 2 / 18); + const fac = ~~(1 + ~~(worldY) % 18 * 2 / 18); for(let j = 0; j < 3; j++) imgData.data[i+j] = (1-lum) * 0x22 + lum * (col[j] * fac); imgData.data[i + 3] = 255; @@ -254,7 +254,7 @@ export default class Renderer { ctx.drawImage(this.minimap, 0, 0); ctx.fillStyle = "red"; ctx.beginPath(); - ctx.rect(mod(this.x, this.generator.size), mod(this.y, this.generator.size), 1, 1); + ctx.rect(clamp(this.x, this.generator.size), clamp(this.y, this.generator.size), 1, 1); ctx.fill(); // small normal text From d973f6f04cfcca3214b69f8019d04cd8da4addcb Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Thu, 12 Sep 2019 14:37:15 +0200 Subject: [PATCH 21/29] Add polish and improve map gen --- modules/game.js | 4 +- modules/generator.js | 89 +++++++++++++++++++++++++++++++------------- modules/renderer.js | 15 +++++++- 3 files changed, 80 insertions(+), 28 deletions(-) diff --git a/modules/game.js b/modules/game.js index 4dba1f20..1a8a5f93 100644 --- a/modules/game.js +++ b/modules/game.js @@ -147,7 +147,8 @@ class GameManager { placeObjects() { this.pizzas = []; let positions = []; - for(let p of iterateQGrid(MAP_SIZE/4, MAP_SIZE/4)) { // TODO go back to normal size + // for(let p of iterateQGrid(MAP_SIZE/4, MAP_SIZE/4)) { + for(let p of iterateQGrid(MAP_SIZE, MAP_SIZE)) { p.x += 0.5; p.y += 0.5; positions.push(p); @@ -189,6 +190,7 @@ class GameManager { this.renderer.pizzaPositions = [this.startObj]; if((this.startObj.x - this.car.x)**2 + (this.startObj.y - this.car.y)**2 < 0.4**2) { this.level++; + this.renderer.setSuperText(`LEVEL ${this.level}`); this.startLevel(); } } diff --git a/modules/generator.js b/modules/generator.js index 22a963d7..4d65d74f 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -1,11 +1,13 @@ + +const COS = [1, 0, -1, 0]; +const SIN = [0, 1, 0, -1]; + class NoiseLayer { constructor(w, h) { this.width = w; this.height = h; this.patternTree = {0:{0:{0:{0:{0:{0:{0:{0:{0:{score:1}}},1:{1:{1:{score:1}}}}},1:{0:{0:{0:{0:{score:-1}}}}}},1:{1:{1:{1:{1:{1:{score:-1}}}}}}},1:{0:{0:{1:{0:{0:{1:{score:1}}},1:{1:{1:{score:1}}}}}}}},1:{1:{0:{1:{1:{0:{1:{1:{score:-1}}}}}}}}},1:{0:{0:{1:{0:{0:{1:{0:{0:{score:1}},1:{1:{score:1}}}}}}}},1:{0:{1:{1:{0:{1:{1:{0:{score:-1}}}}}}},1:{0:{0:{0:{0:{0:{0:{score:1}}}},1:{0:{0:{1:{score:1}}}}}},1:{0:{0:{1:{0:{0:{score:1}}}},1:{1:{1:{1:{score:-1}}}}},1:{1:{0:{0:{0:{score:-1}}},1:{1:{1:{score:-1}}}}}}}}}}; - this.noiseOffset = 0; - this.data = new Array(this.width * this.height); this.accumulator = new Array(this.data.length); @@ -13,39 +15,77 @@ class NoiseLayer { } doSimulationStep() { - for (let i = 0; i < this.accumulator.length; i++) { - this.accumulator[i] = 0; - } + this.accumulator.fill(0); // unfortunately we hard-code size right now (It's just a prototype...) const pw = 3; - for (let x = 0; x < this.width - (pw - 1); x++) - for (let y = 0; y < this.height - (pw - 1); y++) { - // find bad matches, find good matches + const w = this.width; + const h = this.height; + for (let x = 0; x < w; x++) + for (let y = 0; y < h; y++) { + // find bad matches, find good matches // get neighborhood let data = []; for (let dy = 0; dy < pw; dy++) for (let dx = 0; dx < pw; dx++) { - let index = this.width * (y + dy) + x + dx; + let index = w * clamp(y + dy, h) + clamp(x + dx, w); data.push(this.data[index]); } let score = this.getMatchScore(data); if (score != 0) { for (let dy = 0; dy < pw; dy++) for (let dx = 0; dx < pw; dx++) { - let index = this.width * (y + dy) + x + dx; + let index = w * clamp(y + dy, h) + clamp(x + dx, w); this.accumulator[index] += score; } } } - for (let y = 0; y < this.height; y++) - for (let x = 0; x < this.width; x++) { - let index = y * this.width + x; + for (let y = 0; y < h; y++) + for (let x = 0; x < w; x++) { + let index = y * w + x; let mutate = this.accumulator[index] <= 0; - this.data[index] = mutate ? this.sampleNoise(x, y) : this.data[index]; + this.data[index] = mutate ? this.sampleNoise() : this.data[index]; + } + } + + doPostprocessingStep() { + for (let x = 0; x < this.width; x++) + for (let y = 0; y < this.height; y++) { + let sum = 0; + for(let d = 0; d < 4; d++) + sum += this.getAt(x+COS[d], y+SIN[d]); + if(sum < 2) + this.data[y*this.width+x] = 0; + } + } + + floodFill(x, y, value) { + let queue = [x,y]; + let initial = this.getAt(x, y); + for(let i = 0; i < 49; i++){ + if(queue.length == 0) break; + y = queue.pop(); + x = queue.pop(); + if(this.getAt(x, y) == value) return; + this.setAt(x, y, value); + + for(let d = 0; d < 4; d++) { + let adj = this.getAt(x+COS[d], y+SIN[d]); + if(adj == initial) { + queue.push(x+COS[d], y+SIN[d]); + } } + } + } + + getAt(x, y) { + return this.data[(clamp(y, this.height) * this.width) + clamp(x, this.width)]; + } + + setAt(x, y, value) { + this.data[(clamp(y, this.height) * this.width) + clamp(x, this.width)] = value; } getMatchScore(data) { @@ -65,12 +105,12 @@ class NoiseLayer { for (let x = 0; x < this.width; x++) { // seed(Date.now()); // TODO implement seed? let index = y * this.width + x; - this.data[index] = this.sampleNoise(x, y); + this.data[index] = this.sampleNoise(); } } - sampleNoise(x, y) { - return Math.random() < 0.5 + this.noiseOffset ? 1 : 0; + sampleNoise() { + return Math.random() < 0.5 ? 1 : 0; } } @@ -84,6 +124,13 @@ export default class Generator { for (let i = 0; i < 100; i++) { layer.doSimulationStep(); } + for (let i = 0; i < 16; i++) { + layer.doPostprocessingStep(); + } + } + + for(let i = 0; i < 1; i++) { + layers[0].floodFill(~~(Math.random() * size), ~~(Math.random() * size), 1); } this.floorMap = layers[0].data; @@ -123,14 +170,6 @@ export default class Generator { sampleFloorMap(x, y) { return this.floorMap[(clamp(y, this.size) * this.size) + clamp(x, this.size)]; } - - setFloorMap(x, y, val) { - this.floorMap[(clamp(y, this.size) * this.size) + clamp(x, this.size)] = val; - } - - setHeightmap(x, y, val) { - this.heightmap[(clamp(y, this.size) * this.size) + clamp(x, this.size)] = val; - } } export function* iterateQGrid(w, h) { diff --git a/modules/renderer.js b/modules/renderer.js index 517459de..821dddbe 100644 --- a/modules/renderer.js +++ b/modules/renderer.js @@ -50,7 +50,7 @@ export default class Renderer { this.y = 0.5; this.z = 0.35; this.angle = 0; - this.horizon = 0.2; + this.horizon = 0.1; this.infoText = ""; this.infoTextStyle = "red"; @@ -142,7 +142,9 @@ export default class Renderer { for(let locY = locX; locY < tileH-locX-1; locY++) { const tp = transform(locX, locY, dir); const sp = transform(locX, locY, id); - tex.setAt(x*tileW + tp.x, y*tileH + tp.y, this.tileTexture.sampleAt(sp.x, sp.y).map(v => v*fac)); + let sample = this.tileTexture.sampleAt(sp.x, sp.y); + if(sample) + tex.setAt(x*tileW + tp.x, y*tileH + tp.y, sample.map(v => v*fac)); } } } @@ -251,11 +253,20 @@ export default class Renderer { } } + // mini map ctx.drawImage(this.minimap, 0, 0); ctx.fillStyle = "red"; ctx.beginPath(); ctx.rect(clamp(this.x, this.generator.size), clamp(this.y, this.generator.size), 1, 1); ctx.fill(); + if(t0 % 250 < 125) { + ctx.beginPath(); + ctx.fillStyle = "#f5cc7a"; + for(let pizzaPos of this.pizzaPositions) { + ctx.rect(clamp(pizzaPos.x, this.generator.size), clamp(pizzaPos.y, this.generator.size), 1, 1); + } + ctx.fill(); + } // small normal text ctx.font = "8px Arial"; From 73e4ca723b6ac6ac5ea45989e693a7677b7e301d Mon Sep 17 00:00:00 2001 From: Nils Gawlik Date: Thu, 12 Sep 2019 18:45:55 +0200 Subject: [PATCH 22/29] Implement real game flow and UIs --- index.html | 21 ++++++- modules/game.js | 145 ++++++++++++++++++++++++++++++------------- modules/generator.js | 25 +++++--- modules/renderer.js | 11 ++++ style.css | 53 ++++++++++++++-- 5 files changed, 198 insertions(+), 57 deletions(-) diff --git a/index.html b/index.html index 46ddcc01..f403fe54 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,28 @@ + + - +
+ +