summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Kosarek <matt.kosarek@canonical.com>2026-02-19 16:58:58 -0500
committerMatt Kosarek <matt.kosarek@canonical.com>2026-02-19 16:58:58 -0500
commitda0eedbf1733e40613215ecd117e1a4e049089ad (patch)
treed83d5dc63b50efbd45084d692ae037cbe0f02b25
parent4d1beea73810af4641d074f974ad9c196a7e8d6e (diff)
Removed photo gallery + added cute little grass rendering for the rabbit and a nice gradient backgroundHEADmaster
-rw-r--r--_posts/sitemap.org2
-rw-r--r--public/images/dog_and_me-min.jpgbin391836 -> 0 bytes
-rw-r--r--public/images/dog_and_me.jpgbin1095273 -> 0 bytes
-rw-r--r--public/images/ebi.jpgbin727109 -> 0 bytes
-rw-r--r--public/images/friends-min.jpgbin935190 -> 0 bytes
-rw-r--r--public/images/friends.jpgbin2094114 -> 0 bytes
-rw-r--r--public/images/gardens-min.jpgbin41165 -> 0 bytes
-rw-r--r--public/images/gardens.jpgbin44843 -> 0 bytes
-rw-r--r--public/images/portrait-min.jpgbin329679 -> 0 bytes
-rw-r--r--public/images/portrait.jpgbin2324531 -> 0 bytes
-rw-r--r--public/images/resume.jpgbin44843 -> 0 bytes
-rw-r--r--public/index.html34
-rw-r--r--public/posts/dec_29_2025.html18
-rw-r--r--public/posts/feed.xml2
-rw-r--r--public/posts/hello.html74
-rw-r--r--public/posts/jul_28_2025.html6
-rw-r--r--public/posts/june_08_2025.html6
-rw-r--r--public/posts/may_06_2025.html6
-rw-r--r--public/posts/sitemap.html20
-rwxr-xr-xthemes/dist/output.wasmbin101674 -> 106007 bytes
-rw-r--r--themes/meson.build6
-rw-r--r--themes/src/_shaders/grass.frag11
-rw-r--r--themes/src/_shaders/grass.vert25
-rw-r--r--themes/src/main.cpp175
-rw-r--r--themes/src/main_loop.cpp48
-rw-r--r--themes/src/renderer_2d.h90
-rw-r--r--themes/src/shaders/grass_frag.cpp14
-rw-r--r--themes/src/shaders/grass_frag.h4
-rw-r--r--themes/src/shaders/grass_vert.cpp28
-rw-r--r--themes/src/shaders/grass_vert.h4
-rw-r--r--themes/src/spring/grass_renderer.cpp145
-rw-r--r--themes/src/spring/grass_renderer.hpp58
-rw-r--r--themes/src/spring/spring_theme.cpp41
-rw-r--r--themes/src/spring/spring_theme.hpp64
34 files changed, 570 insertions, 311 deletions
diff --git a/_posts/sitemap.org b/_posts/sitemap.org
index 3ea35fd..f85e6a3 100644
--- a/_posts/sitemap.org
+++ b/_posts/sitemap.org
@@ -1,6 +1,6 @@
#+TITLE:
-#+DATE: 2026-02-18 at 17:14
+#+DATE: 2026-02-19 at 15:52
#+HTML_LINK_HOME: /
diff --git a/public/images/dog_and_me-min.jpg b/public/images/dog_and_me-min.jpg
deleted file mode 100644
index 6e4e040..0000000
--- a/public/images/dog_and_me-min.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/dog_and_me.jpg b/public/images/dog_and_me.jpg
deleted file mode 100644
index 1dd3e13..0000000
--- a/public/images/dog_and_me.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/ebi.jpg b/public/images/ebi.jpg
deleted file mode 100644
index 8f4b77e..0000000
--- a/public/images/ebi.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/friends-min.jpg b/public/images/friends-min.jpg
deleted file mode 100644
index 0fbb4c1..0000000
--- a/public/images/friends-min.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/friends.jpg b/public/images/friends.jpg
deleted file mode 100644
index b2c0e5e..0000000
--- a/public/images/friends.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/gardens-min.jpg b/public/images/gardens-min.jpg
deleted file mode 100644
index 3503bfc..0000000
--- a/public/images/gardens-min.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/gardens.jpg b/public/images/gardens.jpg
deleted file mode 100644
index 450dbd2..0000000
--- a/public/images/gardens.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/portrait-min.jpg b/public/images/portrait-min.jpg
deleted file mode 100644
index 49e8cde..0000000
--- a/public/images/portrait-min.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/portrait.jpg b/public/images/portrait.jpg
deleted file mode 100644
index 26e2d81..0000000
--- a/public/images/portrait.jpg
+++ /dev/null
Binary files differ
diff --git a/public/images/resume.jpg b/public/images/resume.jpg
deleted file mode 100644
index 450dbd2..0000000
--- a/public/images/resume.jpg
+++ /dev/null
Binary files differ
diff --git a/public/index.html b/public/index.html
index d632f3b..55e4ba2 100644
--- a/public/index.html
+++ b/public/index.html
@@ -27,40 +27,6 @@
</ul>
</nav>
</header>
- <section id='image_container'>
- <figure class='image_item'>
- <img src='images/ebi.jpg' />
- <figcaption>
- Hanging with my son, circa July 2023.
- </figcaption>
- </figure>
- <figure class='image_item'>
- <img src='images/gardens-min.jpg' />
- <figcaption>
- Me at Longwood Gardens, circa December 2021.
- </figcaption>
- </figure>
- <figure class='image_item'>
- <img src='images/portrait-min.jpg' />
- <figcaption>
- Me in front of my desktop, circa August 2021.
- </figcaption>
- </figure>
- <figure class='image_item'>
- <img src='images/dog_and_me-min.jpg' />
- <figcaption>
- Hanging with my dog named Rizzy, circa May 2020.
- </figcaption>
- </figure>
- <figure class='image_item'>
- <img src='images/friends-min.jpg' />
- <figcaption>
- Hanging with my friends, circa July 2019.
- </figcaption>
- </figure>
-
-
- </section>
<section>
<h2>About Me</h2>
<p>
diff --git a/public/posts/dec_29_2025.html b/public/posts/dec_29_2025.html
index 48981c7..cbdd3c2 100644
--- a/public/posts/dec_29_2025.html
+++ b/public/posts/dec_29_2025.html
@@ -29,17 +29,17 @@
</div>
</div>
<div id="content" class="content">
-<div id="outline-container-org762ecbe" class="outline-2">
-<h2 id="org762ecbe">What have I been up to?</h2>
-<div class="outline-text-2" id="text-org762ecbe">
+<div id="outline-container-org5ff3714" class="outline-2">
+<h2 id="org5ff3714">What have I been up to?</h2>
+<div class="outline-text-2" id="text-org5ff3714">
<p>
2025 has been one busy year for me! I feel as though I've been working on a dozen things at once and have spent much of my working (and personal) days productively. Miracle finally feels like it's getting somewhere fast, the Flutter multi-window work is landing at a solid pace, and Mir is feeling like a truly solid option for Wayland compositor development. I will refrain from speaking too much on the Flutter and Mir work in this post, as those are best left in the hands of Canonical.
</p>
</div>
</div>
-<div id="outline-container-org0499ab5" class="outline-2">
-<h2 id="org0499ab5">Miracle Update</h2>
-<div class="outline-text-2" id="text-org0499ab5">
+<div id="outline-container-org6ce815f" class="outline-2">
+<h2 id="org6ce815f">Miracle Update</h2>
+<div class="outline-text-2" id="text-org6ce815f">
<p>
Miracle has come a long way this past year. I now feel entirely confident using it as my daily driver, minus a few hiccups that I encounter in-between releases. A lot of great things are cooking for 2026 too,
</p>
@@ -108,9 +108,9 @@ v0.9.0 of Miracle will probably be a long time in the making. My estimate is tha
</p>
</div>
</div>
-<div id="outline-container-org719e1b4" class="outline-2">
-<h2 id="org719e1b4">Conclusion</h2>
-<div class="outline-text-2" id="text-org719e1b4">
+<div id="outline-container-orgba126b9" class="outline-2">
+<h2 id="orgba126b9">Conclusion</h2>
+<div class="outline-text-2" id="text-orgba126b9">
<p>
2025 has been a whirlwind of a year, and I'm sure that 2026 won't slow down at all for me. A lot of the long term projects that I've been working on are finally coming together, and I feel as though I am on the cusp of making software that I'm truly proud of. On top of that, I am engaged! What a time to be alive :) I hope you all have a lovely New Year with your friends, family, cats, dogs, and everything else.
</p>
diff --git a/public/posts/feed.xml b/public/posts/feed.xml
index d1388a0..3493910 100644
--- a/public/posts/feed.xml
+++ b/public/posts/feed.xml
@@ -5,7 +5,7 @@
<link>https://matthewkosarek.xyz/</link>
<description>The RSS feed for Matthew Kosarek's Blog</description>
<language>en-us</language>
- <lastBuildDate>Wed, 18 February 2026 17:14:00 -0400</lastBuildDate>
+ <lastBuildDate>Thu, 19 February 2026 15:52:00 -0400</lastBuildDate>
<item>
<title>Update December 29, 2025</title>
diff --git a/public/posts/hello.html b/public/posts/hello.html
index 629ed69..4966f18 100644
--- a/public/posts/hello.html
+++ b/public/posts/hello.html
@@ -29,9 +29,9 @@
</div>
</div>
<div id="content" class="content">
-<div id="outline-container-org6bad422" class="outline-2">
-<h2 id="org6bad422">TLDR</h2>
-<div class="outline-text-2" id="text-org6bad422">
+<div id="outline-container-org9e9109d" class="outline-2">
+<h2 id="org9e9109d">TLDR</h2>
+<div class="outline-text-2" id="text-org9e9109d">
<ul class="org-ul">
<li>Create a new folder</li>
<li>Put <a href="https://raw.githubusercontent.com/mattkae/matthewkosarek-xyz/master/index.css">index.css</a>, <a href="https://raw.githubusercontent.com/mattkae/matthewkosarek-xyz/master/publish.el">publish.el</a>, and <a href="https://github.com/mattkae/matthewkosarek-xyz/blob/master/publish.sh">publish.sh</a> in the folder</li>
@@ -45,9 +45,9 @@
</ul>
</div>
</div>
-<div id="outline-container-orgc7f45b6" class="outline-2">
-<h2 id="orgc7f45b6">Introduction</h2>
-<div class="outline-text-2" id="text-orgc7f45b6">
+<div id="outline-container-org33cf264" class="outline-2">
+<h2 id="org33cf264">Introduction</h2>
+<div class="outline-text-2" id="text-org33cf264">
<p>
I've recently fallen in love with <code>org-mode</code>, specifically when I use it with <a href="https://www.orgroam.com/">org-roam</a>. I find the whole workflow of creating, tagging, and - later on - searching for information on my computer to be very elegant. On top of that, now that I have the time, I want to begin writing blog posts to better work out my thoughts. With both of these things in mind, I am again turning to the universal tool for human prospering: <code>org-mode</code>. This time, I want to see how it can help me turn a simple org file into a blog post on my website. My requirements are:
</p>
@@ -70,9 +70,9 @@ And that's pretty much it for now. Without further ado, let's jump into getting
</p>
</div>
</div>
-<div id="outline-container-orga3a87b9" class="outline-2">
-<h2 id="orga3a87b9">Basic HTML File</h2>
-<div class="outline-text-2" id="text-orga3a87b9">
+<div id="outline-container-org041489b" class="outline-2">
+<h2 id="org041489b">Basic HTML File</h2>
+<div class="outline-text-2" id="text-org041489b">
<p>
As a pilot, we are going to use this org file that I am currently writing (<code>hello.org</code>) as our guinea pig. The goal is to have this org file be our very first blog post.
</p>
@@ -112,9 +112,9 @@ We then do a <code>chmod +x publish.sh</code> to make it an executable and run i
</p>
</div>
</div>
-<div id="outline-container-orgf20d464" class="outline-2">
-<h2 id="orgf20d464">Disabling features that we don't want</h2>
-<div class="outline-text-2" id="text-orgf20d464">
+<div id="outline-container-org0353981" class="outline-2">
+<h2 id="org0353981">Disabling features that we don't want</h2>
+<div class="outline-text-2" id="text-org0353981">
<p>
The next thing will be to remove some of the generated items that I didn't ask for, namely the table of contents, author, section numbers, creation time stamp, and the validation link.
</p>
@@ -143,9 +143,9 @@ The next thing will be to remove some of the generated items that I didn't ask f
</div>
</div>
</div>
-<div id="outline-container-orgb6e0609" class="outline-2">
-<h2 id="orgb6e0609">Styling &amp; Code Highlighting</h2>
-<div class="outline-text-2" id="text-orgb6e0609">
+<div id="outline-container-org8a1e73c" class="outline-2">
+<h2 id="org8a1e73c">Styling &amp; Code Highlighting</h2>
+<div class="outline-text-2" id="text-org8a1e73c">
<p>
Next thing on our list is custom styling. This can be achieved by first installing the <code>htmlize</code> package from <code>melpa</code> / <code>elpa</code>. The EmacsWiki describes this as "a package for exporting the contents of an Emacs buffer to HTML while respecting display properties such as colors, fonts, underlining, invisibility, etc" (<a href="https://www.emacswiki.org/emacs/Htmlize">reference</a>). If used "out-of-the-box", the buffer will be exported to HTML with all of the styles inlined (e.g. if you underline something in your org file, you will generate a <code>&lt;span style="text-decoration: underline"&gt;...&lt;/span&gt;</code>). However, we are more interested in styling everything by ourselves: we don't want <code>htmlize</code> making assumptions about what underlining means to us! Luckily, <code>htmlize</code> gives us the option to export with class names instead of inline styles so that we can specify each style for ourselves.
</p>
@@ -315,9 +315,9 @@ If we run the publish again, we can see that we have full styling on our code sn
</p>
</div>
</div>
-<div id="outline-container-org456f7f8" class="outline-2">
-<h2 id="org456f7f8">Images</h2>
-<div class="outline-text-2" id="text-org456f7f8">
+<div id="outline-container-org624adfd" class="outline-2">
+<h2 id="org624adfd">Images</h2>
+<div class="outline-text-2" id="text-org624adfd">
<p>
Our first two criteria have been met! Next on the list is solving images. As an example, let's use this <a href="file:///_posts/assets/squirrel.jpg">squirrel image</a> that I found online with an open source license. The ideal situation would be:
</p>
@@ -362,16 +362,16 @@ So what's the fix here? Well, we have two options, but I am going to go with the
That's all there is to it! There are simpler ways as well, but that should do it:
</p>
-<div id="org04b7deb" class="figure">
+<div id="org9dc849e" class="figure">
<p><img src="/_posts/assets/squirrel.jpg" alt="squirrel.jpg" width="300" />
</p>
<p><span class="figure-number">Figure 1: </span>A Cute Squirrel</p>
</div>
</div>
</div>
-<div id="outline-container-orgd7df786" class="outline-2">
-<h2 id="orgd7df786">Creation Date</h2>
-<div class="outline-text-2" id="text-orgd7df786">
+<div id="outline-container-org328d8f8" class="outline-2">
+<h2 id="org328d8f8">Creation Date</h2>
+<div class="outline-text-2" id="text-org328d8f8">
<p>
Let's add the creation date below the title next. To start, we will modify the publish command to remove the title (<code>:with-title nil</code>) and, in its place, show a preamble bit of HTML that contains a formatted <code>div</code> with the title and the "last modified" span.z
</p>
@@ -430,9 +430,9 @@ Note that the downside of this is that the created date will change whenever you
</p>
</div>
</div>
-<div id="outline-container-org6b24595" class="outline-2">
-<h2 id="org6b24595">Generating the Directory</h2>
-<div class="outline-text-2" id="text-org6b24595">
+<div id="outline-container-orge04dce8" class="outline-2">
+<h2 id="orge04dce8">Generating the Directory</h2>
+<div class="outline-text-2" id="text-orge04dce8">
<p>
For every org file in my <code>_posts</code> folder, I would like to create a link to the generated HTML file at the <code>/posts.html</code> page of my website. You can think of this as the "directory" of all posts. My criteria is:
</p>
@@ -492,9 +492,9 @@ If you open the <code>sitemap.html</code> file in your browser, you will see a b
From here, you may customize it however you like. The following are my customizations.
</p>
</div>
-<div id="outline-container-orgde34f50" class="outline-3">
-<h3 id="orgde34f50">Sitemap Title</h3>
-<div class="outline-text-3" id="text-orgde34f50">
+<div id="outline-container-orgf0a663f" class="outline-3">
+<h3 id="orgf0a663f">Sitemap Title</h3>
+<div class="outline-text-3" id="text-orgf0a663f">
<p>
I changed the title to "Matthew's Blog Posts".
</p>
@@ -516,9 +516,9 @@ I changed the title to "Matthew's Blog Posts".
</div>
</div>
</div>
-<div id="outline-container-org8eecd95" class="outline-3">
-<h3 id="org8eecd95">Format blog entries in the list</h3>
-<div class="outline-text-3" id="text-org8eecd95">
+<div id="outline-container-orgbf15acf" class="outline-3">
+<h3 id="orgbf15acf">Format blog entries in the list</h3>
+<div class="outline-text-3" id="text-orgbf15acf">
<p>
I like to include the creation date on the blog posts. To do this, we can use <code>org-publish-find-property</code> to find the date property of the org file. Afterward, we can format a string that includes our formatted timestamp and the <code>org-publish-sitemap-default-entry</code>, which is just a link with the title of the post.
</p>
@@ -539,9 +539,9 @@ I like to include the creation date on the blog posts. To do this, we can use <c
</div>
</div>
</div>
-<div id="outline-container-orgcb3d39d" class="outline-2">
-<h2 id="orgcb3d39d">Tags &amp; Filtering</h2>
-<div class="outline-text-2" id="text-orgcb3d39d">
+<div id="outline-container-orgbbf6e44" class="outline-2">
+<h2 id="orgbbf6e44">Tags &amp; Filtering</h2>
+<div class="outline-text-2" id="text-orgbbf6e44">
<p>
I use <a href="https://www.orgroam.com/">Org-roam</a> for all of my note-taking and, in the next blog post, I plan to demonstrate how I will hook up my Org-roam note-taking workflow to my blogging. In the meantime, just know that we can add tags to the top of our org files like this:
</p>
@@ -761,9 +761,9 @@ Finally, let's append the following to <code>posts/posts.css</code> so that our
</div>
</div>
</div>
-<div id="outline-container-org937024c" class="outline-2">
-<h2 id="org937024c">Conclusion</h2>
-<div class="outline-text-2" id="text-org937024c">
+<div id="outline-container-org6068a86" class="outline-2">
+<h2 id="org6068a86">Conclusion</h2>
+<div class="outline-text-2" id="text-org6068a86">
<p>
There are many more customizations that I plan to do on this system in the future, but I plan to leave this for now so that I can actually get to some blogging. I will proofread and fix my mistakes as time goes on, but this should be a good jumping off point for anyone interested in using org for their own blogging system.
</p>
diff --git a/public/posts/jul_28_2025.html b/public/posts/jul_28_2025.html
index d93cf72..ea4e38f 100644
--- a/public/posts/jul_28_2025.html
+++ b/public/posts/jul_28_2025.html
@@ -29,9 +29,9 @@
</div>
</div>
<div id="content" class="content">
-<div id="outline-container-org7b55006" class="outline-2">
-<h2 id="org7b55006">What have I been up to?</h2>
-<div class="outline-text-2" id="text-org7b55006">
+<div id="outline-container-org030cdd7" class="outline-2">
+<h2 id="org030cdd7">What have I been up to?</h2>
+<div class="outline-text-2" id="text-org030cdd7">
<p>
Whoops! I missed this month's update by a <i>long</i> shot, but I still want to get it out there before the end of the month.
</p>
diff --git a/public/posts/june_08_2025.html b/public/posts/june_08_2025.html
index f467a84..9dc6840 100644
--- a/public/posts/june_08_2025.html
+++ b/public/posts/june_08_2025.html
@@ -29,9 +29,9 @@
</div>
</div>
<div id="content" class="content">
-<div id="outline-container-orgb3a2b27" class="outline-2">
-<h2 id="orgb3a2b27">What have I been up to?</h2>
-<div class="outline-text-2" id="text-orgb3a2b27">
+<div id="outline-container-org3a3efed" class="outline-2">
+<h2 id="org3a3efed">What have I been up to?</h2>
+<div class="outline-text-2" id="text-org3a3efed">
<p>
Another month has gone by, so I guess it's time to see what I've been up to.
</p>
diff --git a/public/posts/may_06_2025.html b/public/posts/may_06_2025.html
index 563bcab..0dbf222 100644
--- a/public/posts/may_06_2025.html
+++ b/public/posts/may_06_2025.html
@@ -29,9 +29,9 @@
</div>
</div>
<div id="content" class="content">
-<div id="outline-container-org8c64ed6" class="outline-2">
-<h2 id="org8c64ed6">What have I been up to?</h2>
-<div class="outline-text-2" id="text-org8c64ed6">
+<div id="outline-container-org447f3cf" class="outline-2">
+<h2 id="org447f3cf">What have I been up to?</h2>
+<div class="outline-text-2" id="text-org447f3cf">
<p>
I've been meaning to do these little blog-post type updates for a while, and I figured now is as good a time as any. So let's start :)
</p>
diff --git a/public/posts/sitemap.html b/public/posts/sitemap.html
index 9bcfbaf..fac5832 100644
--- a/public/posts/sitemap.html
+++ b/public/posts/sitemap.html
@@ -34,13 +34,13 @@
<li><p>
<a href="dec_29_2025.html">Update December 29, 2025</a>
</p>
-<div class="sitemap_date" id="org63552bd">
+<div class="sitemap_date" id="org5a5f43e">
<p>
December 29, 2025
</p>
</div>
-<div class="sitemap_tag" id="orgcae0db0">
+<div class="sitemap_tag" id="org1ea5026">
<p>
update
</p>
@@ -49,13 +49,13 @@ update
<li><p>
<a href="jul_28_2025.html">Update July 28, 2025</a>
</p>
-<div class="sitemap_date" id="org04d89e2">
+<div class="sitemap_date" id="orgf97cfcd">
<p>
July 28, 2025
</p>
</div>
-<div class="sitemap_tag" id="orgd7ad92d">
+<div class="sitemap_tag" id="org8ef1374">
<p>
update
</p>
@@ -64,13 +64,13 @@ update
<li><p>
<a href="june_08_2025.html">Update June 08, 2025</a>
</p>
-<div class="sitemap_date" id="org1099798">
+<div class="sitemap_date" id="org22eabd6">
<p>
June 08, 2025
</p>
</div>
-<div class="sitemap_tag" id="orgf02bca4">
+<div class="sitemap_tag" id="orgd49a1ce">
<p>
update
</p>
@@ -79,13 +79,13 @@ update
<li><p>
<a href="may_06_2025.html">Update May 06, 2025</a>
</p>
-<div class="sitemap_date" id="org901b9bf">
+<div class="sitemap_date" id="orgea2a74c">
<p>
May 06, 2025
</p>
</div>
-<div class="sitemap_tag" id="org0b4777f">
+<div class="sitemap_tag" id="org0b31acc">
<p>
update
</p>
@@ -94,13 +94,13 @@ update
<li><p>
<a href="hello.html">Hello, Org</a>
</p>
-<div class="sitemap_date" id="orgf000d64">
+<div class="sitemap_date" id="orgd9dcebd">
<p>
June 20, 2023
</p>
</div>
-<div class="sitemap_tag" id="orga3ba3e0">
+<div class="sitemap_tag" id="org52466f8">
<p>
technology,home
</p>
diff --git a/themes/dist/output.wasm b/themes/dist/output.wasm
index 550e5ff..d0b2635 100755
--- a/themes/dist/output.wasm
+++ b/themes/dist/output.wasm
Binary files differ
diff --git a/themes/meson.build b/themes/meson.build
index 563ddc6..f99bf5c 100644
--- a/themes/meson.build
+++ b/themes/meson.build
@@ -40,6 +40,8 @@ sources = files(
'src/shaders/sun_vert.cpp',
'src/shaders/snowflake_frag.cpp',
'src/shaders/snowflake_vert.cpp',
+ 'src/shaders/grass_frag.cpp',
+ 'src/shaders/grass_vert.cpp',
# Autumn theme
'src/autumn/autumn_theme.cpp',
@@ -84,7 +86,9 @@ shader_inputs = files(
'src/_shaders/sun.frag',
'src/_shaders/sun.vert',
'src/_shaders/snowflake.frag',
- 'src/_shaders/snowflake.vert'
+ 'src/_shaders/snowflake.vert',
+ 'src/_shaders/grass.frag',
+ 'src/_shaders/grass.vert'
)
# Custom target that runs whenever shader files change
diff --git a/themes/src/_shaders/grass.frag b/themes/src/_shaders/grass.frag
new file mode 100644
index 0000000..a72f078
--- /dev/null
+++ b/themes/src/_shaders/grass.frag
@@ -0,0 +1,11 @@
+varying lowp vec2 vUV;
+
+void main() {
+ lowp float halfWidth = 0.5 * (1.0 - vUV.y);
+ lowp float distFromCenter = abs(vUV.x - 0.5);
+ if (distFromCenter > halfWidth) discard;
+
+ lowp vec3 baseColor = vec3(0.15, 0.45, 0.10);
+ lowp vec3 tipColor = vec3(0.40, 0.75, 0.20);
+ gl_FragColor = vec4(mix(baseColor, tipColor, vUV.y), 1.0);
+}
diff --git a/themes/src/_shaders/grass.vert b/themes/src/_shaders/grass.vert
new file mode 100644
index 0000000..0cf0285
--- /dev/null
+++ b/themes/src/_shaders/grass.vert
@@ -0,0 +1,25 @@
+attribute vec2 position; // Local quad vertex: x in [-0.5, 0.5], y in [0, 1]
+attribute vec3 instancePos; // Per-instance: world-space base of blade
+attribute float instancePhase; // Per-instance: random phase offset for sway
+attribute float instanceHeight; // Per-instance: height scale multiplier
+
+uniform mat4 projection;
+uniform mat4 view;
+uniform float time;
+uniform float bladeWidth;
+uniform float bladeHeight;
+uniform float swayAmount;
+
+varying lowp vec2 vUV;
+
+void main() {
+ vec3 cameraRight = vec3(view[0][0], view[1][0], view[2][0]);
+ float h = bladeHeight * instanceHeight;
+ float sway = sin(time * 1.5 + instancePhase) * swayAmount * position.y;
+ vec3 worldPos = instancePos
+ + cameraRight * (position.x + sway) * bladeWidth
+ + vec3(0.0, 1.0, 0.0) * position.y * h;
+
+ gl_Position = projection * view * vec4(worldPos, 1.0);
+ vUV = vec2(position.x + 0.5, position.y);
+}
diff --git a/themes/src/main.cpp b/themes/src/main.cpp
index 60e6aed..ec7630b 100644
--- a/themes/src/main.cpp
+++ b/themes/src/main.cpp
@@ -1,117 +1,132 @@
-#include "webgl_context.h"
+#include "autumn/autumn_theme.hpp"
#include "main_loop.h"
-#include "renderer_2d.h"
#include "mathlib.h"
+#include "renderer_2d.h"
+#include "spring/spring_theme.hpp"
+#include "summer/summer_theme.h"
#include "theme.h"
#include "types.h"
-#include "summer/summer_theme.h"
-#include "autumn/autumn_theme.hpp"
-#include "spring/spring_theme.hpp"
+#include "webgl_context.h"
#include "winter/winter_theme.hpp"
#include <cstdio>
#include <emscripten/fetch.h>
void load(ThemeType theme);
void unload();
-void update(f32 dtSeconds, void* userData);
-EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
-EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
-EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
-EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
-EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData);
+void update(f32 dtSeconds, void *userData);
+EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData);
+EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData);
+EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData);
+EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData);
+EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData);
WebglContext context;
MainLoop mainLoop;
ThemeType type;
-Theme* active_theme;
+Theme *active_theme;
int main() {
- context.init("#theme_canvas");
- emscripten_set_click_callback("#theme_button_default", NULL, false, selectNone);
- emscripten_set_click_callback("#theme_button_autumn", NULL, false, selectAutumn);
- emscripten_set_click_callback("#theme_button_winter", NULL, false, selectWinter);
- emscripten_set_click_callback("#theme_button_spring", NULL, false, selectSpring);
- emscripten_set_click_callback("#theme_button_summer", NULL, false, selectSummer);
-
- return 0;
+ context.init("#theme_canvas");
+ emscripten_set_click_callback("#theme_button_default", NULL, false,
+ selectNone);
+ emscripten_set_click_callback("#theme_button_autumn", NULL, false,
+ selectAutumn);
+ emscripten_set_click_callback("#theme_button_winter", NULL, false,
+ selectWinter);
+ emscripten_set_click_callback("#theme_button_spring", NULL, false,
+ selectSpring);
+ emscripten_set_click_callback("#theme_button_summer", NULL, false,
+ selectSummer);
+
+ return 0;
}
// -- Scene loading, updating, and unloading logic
void load(ThemeType theme) {
- if (type == theme) {
- printf("This theme is already active.\n");
- return;
- }
-
- unload(); // Try and unload before we load, so that we start fresh
-
- type = theme;
- mainLoop.run(update);
-
- switch (type) {
- case ThemeType::Autumn:
- active_theme = new AutumnTheme(&context);
- break;
- case ThemeType::Winter:
- active_theme = new WinterTheme(&context);
- break;
- case ThemeType::Spring:
- active_theme = new SpringTheme(&context);
- break;
- case ThemeType::Summer:
- active_theme = new SummerTheme(&context);
- break;
- default:
- break;
- }
+ if (type == theme) {
+ printf("This theme is already active.\n");
+ return;
+ }
+
+ unload(); // Try and unload before we load, so that we start fresh
+
+ type = theme;
+ mainLoop.run(update);
+
+ switch (type) {
+ case ThemeType::Autumn:
+ active_theme = new AutumnTheme(&context);
+ break;
+ case ThemeType::Winter:
+ active_theme = new WinterTheme(&context);
+ break;
+ case ThemeType::Spring:
+ active_theme = new SpringTheme(&context);
+ break;
+ case ThemeType::Summer:
+ active_theme = new SummerTheme(&context);
+ break;
+ default:
+ break;
+ }
}
-void update(f32 dtSeconds, void* userData) {
- if (!active_theme)
- return;
- active_theme->update(dtSeconds);
- active_theme->render();
+void update(f32 dtSeconds, void *userData) {
+ if (!active_theme)
+ return;
+ active_theme->update(dtSeconds);
+ active_theme->render();
}
void unload() {
- delete active_theme;
- active_theme = nullptr;
-
- type = ThemeType::Default;
- glClearColor(0, 0, 0, 0);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
- if (mainLoop.isRunning) {
- mainLoop.stop();
- }
+ delete active_theme;
+ active_theme = nullptr;
+
+ type = ThemeType::Default;
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ if (mainLoop.isRunning) {
+ mainLoop.stop();
+ }
}
// -- HTML5 callbacks
-EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
- printf("Default theme selected\n");
- unload();
- return true;
+EM_BOOL selectNone(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData) {
+ printf("Default theme selected\n");
+ unload();
+ return true;
}
-EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
- printf("Autumn theme selected\n");
- load(ThemeType::Autumn);
- return true;
+EM_BOOL selectAutumn(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData) {
+ printf("Autumn theme selected\n");
+ load(ThemeType::Autumn);
+ return true;
}
-EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
- printf("Winter theme selected\n");
- load(ThemeType::Winter);
- return true;
+EM_BOOL selectWinter(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData) {
+ printf("Winter theme selected\n");
+ load(ThemeType::Winter);
+ return true;
}
-EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
- printf("Spring theme selected\n");
- load(ThemeType::Spring);
- return true;
+EM_BOOL selectSpring(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData) {
+ printf("Spring theme selected\n");
+ load(ThemeType::Spring);
+ return true;
}
-EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData) {
- printf("Summer theme selected\n");
- load(ThemeType::Summer);
- return true;
+EM_BOOL selectSummer(int eventType, const EmscriptenMouseEvent *mouseEvent,
+ void *userData) {
+ printf("Summer theme selected\n");
+ load(ThemeType::Summer);
+ return true;
}
diff --git a/themes/src/main_loop.cpp b/themes/src/main_loop.cpp
index e5397ca..743892e 100644
--- a/themes/src/main_loop.cpp
+++ b/themes/src/main_loop.cpp
@@ -2,30 +2,36 @@
#include <cstdio>
#include <cstdlib>
-EM_BOOL loop(double time, void* loop) {
- MainLoop* mainLoop = (MainLoop*) loop;
- if (!mainLoop->isRunning) {
- return false;
- }
-
- if (mainLoop->lastTime == 0) {
- mainLoop->lastTime = time;
- return true;
- }
+EM_BOOL loop(double time, void *loop) {
+ MainLoop *mainLoop = (MainLoop *)loop;
+ if (!mainLoop->isRunning) {
+ return false;
+ }
- long deltaTime = time - mainLoop->lastTime;
+ if (mainLoop->lastTime == 0) {
mainLoop->lastTime = time;
- mainLoop->elapsedTime += deltaTime;
- mainLoop->numFrames++;
- float deltaTimeSeconds = static_cast<float>(deltaTime) / 1000.f;
+ return true;
+ }
+
+ long deltaTime = time - mainLoop->lastTime;
+ mainLoop->lastTime = time;
+ mainLoop->elapsedTime += deltaTime;
+ mainLoop->numFrames++;
+ float deltaTimeSeconds = static_cast<float>(deltaTime) / 1000.f;
- if (mainLoop->elapsedTime >= 1000.0) {
- printf("FPS: %d\n", mainLoop->numFrames);
+ if (mainLoop->elapsedTime >= 1000.0) {
+ printf("FPS: %d\n", mainLoop->numFrames);
- mainLoop->elapsedTime = 0.0;
- mainLoop->numFrames = 0;
- }
+ mainLoop->elapsedTime = 0.0;
+ mainLoop->numFrames = 0;
+ }
- mainLoop->updateFunc(deltaTimeSeconds, NULL);
+ // Ignore any update with a greater than 0.1 change. We were
+ // probably tabbed away, so this is uninteresting to us.
+ if (deltaTimeSeconds > 0.1) {
return true;
-} \ No newline at end of file
+ }
+
+ mainLoop->updateFunc(deltaTimeSeconds, NULL);
+ return true;
+}
diff --git a/themes/src/renderer_2d.h b/themes/src/renderer_2d.h
index d572533..16c5cbe 100644
--- a/themes/src/renderer_2d.h
+++ b/themes/src/renderer_2d.h
@@ -1,61 +1,59 @@
#pragma once
-#include "webgl_context.h"
-#include "types.h"
-#include "shader.h"
#include "mathlib.h"
+#include "shader.h"
+#include "types.h"
+#include "webgl_context.h"
struct WebglContext;
/// Responsible for rendering Mesh2Ds
struct Renderer2d {
- WebglContext* context = NULL;
- Mat4x4 projection;
- u32 shader;
- Vector4 clearColor;
-
- struct {
- i32 position;
- i32 color;
-
- // TODO: vMatrix is not standard and does not belong here
- i32 vMatrix;
- } attributes;
-
- struct {
- i32 projection;
- i32 model;
- } uniforms;
-
- /// Load with the provided context and shader programs. If the shaders are NULL, the default
- /// shader is used
- void load(WebglContext* context, const char* vertexShader = NULL, const char* fragmentShader = NULL);
- void render();
- void unload();
- f32 get_width();
- f32 get_height();
+ WebglContext *context = NULL;
+ Mat4x4 projection;
+ u32 shader;
+ Vector4 clearColor;
+
+ struct {
+ i32 position;
+ i32 color;
+
+ // TODO: vMatrix is not standard and does not belong here
+ i32 vMatrix;
+ } attributes;
+
+ struct {
+ i32 projection;
+ i32 model;
+ } uniforms;
+
+ /// Load with the provided context and shader programs. If the shaders are
+ /// NULL, the default shader is used
+ void load(WebglContext *context, const char *vertexShader = NULL,
+ const char *fragmentShader = NULL);
+ void render();
+ void unload();
+ f32 get_width();
+ f32 get_height();
};
struct Vertex2D {
- Vector2 position;
- Vector4 color;
- Mat4x4 vMatrix;
+ Vector2 position;
+ Vector4 color;
+ Mat4x4 vMatrix;
};
struct Mesh2D {
- u32 vao;
- u32 vbo;
- u32 ebo = 0;
- u32 numVertices = 0;
- u32 numIndices = 0;
- Mat4x4 model;
-
- void load(Vertex2D* vertices, u32 numVertices, Renderer2d* renderer);
- void load(Vertex2D* vertices,
- u32 numVertices,
- u32* indices,
- u32 numIndices,
- Renderer2d* renderer);
- void render(Renderer2d* renderer, GLenum drawType = GL_TRIANGLES);
- void unload();
+ u32 vao;
+ u32 vbo;
+ u32 ebo = 0;
+ u32 numVertices = 0;
+ u32 numIndices = 0;
+ Mat4x4 model;
+
+ void load(Vertex2D *vertices, u32 numVertices, Renderer2d *renderer);
+ void load(Vertex2D *vertices, u32 numVertices, u32 *indices, u32 numIndices,
+ Renderer2d *renderer);
+ void render(Renderer2d *renderer, GLenum drawType = GL_TRIANGLES);
+ void unload();
};
diff --git a/themes/src/shaders/grass_frag.cpp b/themes/src/shaders/grass_frag.cpp
new file mode 100644
index 0000000..5a62cf2
--- /dev/null
+++ b/themes/src/shaders/grass_frag.cpp
@@ -0,0 +1,14 @@
+#include "grass_frag.h"
+
+const char* shader_grass_frag = "varying lowp vec2 vUV; \n"
+" \n"
+"void main() { \n"
+" lowp float halfWidth = 0.5 * (1.0 - vUV.y); \n"
+" lowp float distFromCenter = abs(vUV.x - 0.5); \n"
+" if (distFromCenter > halfWidth) discard; \n"
+" \n"
+" lowp vec3 baseColor = vec3(0.15, 0.45, 0.10); \n"
+" lowp vec3 tipColor = vec3(0.40, 0.75, 0.20); \n"
+" gl_FragColor = vec4(mix(baseColor, tipColor, vUV.y), 1.0); \n"
+"} \n"
+" \n";
diff --git a/themes/src/shaders/grass_frag.h b/themes/src/shaders/grass_frag.h
new file mode 100644
index 0000000..16cc29a
--- /dev/null
+++ b/themes/src/shaders/grass_frag.h
@@ -0,0 +1,4 @@
+#ifndef SHADER_GRASS_FRAG
+#define SHADER_GRASS_FRAG
+extern const char* shader_grass_frag;
+#endif
diff --git a/themes/src/shaders/grass_vert.cpp b/themes/src/shaders/grass_vert.cpp
new file mode 100644
index 0000000..c9d2955
--- /dev/null
+++ b/themes/src/shaders/grass_vert.cpp
@@ -0,0 +1,28 @@
+#include "grass_vert.h"
+
+const char* shader_grass_vert = "attribute vec2 position; // Local quad vertex: x in [-0.5, 0.5], y in [0, 1] \n"
+"attribute vec3 instancePos; // Per-instance: world-space base of blade \n"
+"attribute float instancePhase; // Per-instance: random phase offset for sway \n"
+"attribute float instanceHeight; // Per-instance: height scale multiplier \n"
+" \n"
+"uniform mat4 projection; \n"
+"uniform mat4 view; \n"
+"uniform float time; \n"
+"uniform float bladeWidth; \n"
+"uniform float bladeHeight; \n"
+"uniform float swayAmount; \n"
+" \n"
+"varying lowp vec2 vUV; \n"
+" \n"
+"void main() { \n"
+" vec3 cameraRight = vec3(view[0][0], view[1][0], view[2][0]); \n"
+" float h = bladeHeight * instanceHeight; \n"
+" float sway = sin(time * 1.5 + instancePhase) * swayAmount * position.y; \n"
+" vec3 worldPos = instancePos \n"
+" + cameraRight * (position.x + sway) * bladeWidth \n"
+" + vec3(0.0, 1.0, 0.0) * position.y * h; \n"
+" \n"
+" gl_Position = projection * view * vec4(worldPos, 1.0); \n"
+" vUV = vec2(position.x + 0.5, position.y); \n"
+"} \n"
+" \n";
diff --git a/themes/src/shaders/grass_vert.h b/themes/src/shaders/grass_vert.h
new file mode 100644
index 0000000..7ab52b6
--- /dev/null
+++ b/themes/src/shaders/grass_vert.h
@@ -0,0 +1,4 @@
+#ifndef SHADER_GRASS_VERT
+#define SHADER_GRASS_VERT
+extern const char* shader_grass_vert;
+#endif
diff --git a/themes/src/spring/grass_renderer.cpp b/themes/src/spring/grass_renderer.cpp
index 685f733..e4a210c 100644
--- a/themes/src/spring/grass_renderer.cpp
+++ b/themes/src/spring/grass_renderer.cpp
@@ -1,29 +1,138 @@
#include "grass_renderer.hpp"
#include "../renderer_3d.h"
+#include "../shader.h"
+#include "../shaders/grass_frag.h"
+#include "../shaders/grass_vert.h"
+#include "mathlib.h"
+#include <cmath>
+#include <cstddef>
-void GrassRenderer::load(GrassRendererLoadData params, Renderer3d* renderer) {
- const f32 COLUMN_INCREMENT = GRASS_BLADES_PER_COL / params.area.x;
- const f32 ROW_INCREMENT = GRASS_BLADES_PER_ROW / params.area.y;
- for (i32 r = 0; r < GRASS_BLADES_PER_ROW; r++) {
- i32 indexOffset = r * GRASS_BLADES_PER_ROW;
- f32 y = ROW_INCREMENT * r;
- for (i32 c = 0; c < GRASS_BLADES_PER_COL; c++) {
- f32 x = COLUMN_INCREMENT * c;
- i32 index = indexOffset + c;
- grassBlades[index].position = Vector3(x, y, 0);
- grassBlades[index].top_offset = Vector2(0, 0);
- }
- }
-}
+void GrassRenderer::load(GrassRendererLoadData params, Renderer3d *renderer) {
+ bladeHeight = params.grassHeight;
+
+ // Place blades randomly within a circle. Using r = R*sqrt(u) with a
+ // uniform u in [0,1] gives uniform areal density (no center clustering).
+ const f32 radius = fminf(params.area.x, params.area.y) * 0.5f;
+ for (i32 i = 0; i < NUM_GRASS_BLADES; i++) {
+ f32 r = radius * sqrtf(randomFloatBetween(0.f, 1.f));
+ f32 theta = randomFloatBetween(0.f, 2.f * PI);
+ f32 x = params.origin.x + r * cosf(theta);
+ f32 z = params.origin.y + r * sinf(theta);
+ grassBlades[i].position = Vector3(x, 0, z);
+ grassBlades[i].top_offset =
+ Vector2(randomFloatBetween(0.f, 2.f * PI), // sway phase
+ randomFloatBetween(0.5f, 1.5f) // height scale
+ );
+ }
+
+ // Compile grass shader
+ shader = loadShader(shader_grass_vert, shader_grass_frag);
+ useShader(shader);
+
+ // Attribute locations
+ attributes.position = getShaderAttribute(shader, "position");
+ attributes.instancePos = getShaderAttribute(shader, "instancePos");
+ attributes.instancePhase = getShaderAttribute(shader, "instancePhase");
+ attributes.instanceHeight = getShaderAttribute(shader, "instanceHeight");
+
+ // Uniform locations
+ uniforms.projection = getShaderUniform(shader, "projection");
+ uniforms.view = getShaderUniform(shader, "view");
+ uniforms.time = getShaderUniform(shader, "time");
+ uniforms.bladeWidth = getShaderUniform(shader, "bladeWidth");
+ uniforms.bladeHeight = getShaderUniform(shader, "bladeHeight");
+ uniforms.swayAmount = getShaderUniform(shader, "swayAmount");
+
+ // Base quad: two triangles forming a unit quad
+ // x in [-0.5, 0.5], y in [0, 1]
+ Vector2 quadVertices[] = {Vector2(-0.5f, 0.0f), Vector2(0.5f, 0.0f),
+ Vector2(0.5f, 1.0f), Vector2(-0.5f, 0.0f),
+ Vector2(0.5f, 1.0f), Vector2(-0.5f, 1.0f)};
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ // Static quad VBO
+ glGenBuffers(1, &quadVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, quadVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices,
+ GL_STATIC_DRAW);
+ glEnableVertexAttribArray(attributes.position);
+ glVertexAttribPointer(attributes.position, 2, GL_FLOAT, GL_FALSE,
+ sizeof(Vector2), (GLvoid *)0);
-void GrassRenderer::update(f32 seconds) {
-
+ // Dynamic instance VBO
+ glGenBuffers(1, &instanceVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, instanceVbo);
+ glBufferData(GL_ARRAY_BUFFER, NUM_GRASS_BLADES * sizeof(GrassInstanceData),
+ NULL, GL_DYNAMIC_DRAW);
+
+ // instancePos: vec3 (x, y, z) at offset 0
+ glEnableVertexAttribArray(attributes.instancePos);
+ glVertexAttribPointer(attributes.instancePos, 3, GL_FLOAT, GL_FALSE,
+ sizeof(GrassInstanceData),
+ (GLvoid *)offsetof(GrassInstanceData, x));
+ glVertexAttribDivisor(attributes.instancePos, 1);
+
+ // instancePhase: float at offset 12 (after 3 floats)
+ glEnableVertexAttribArray(attributes.instancePhase);
+ glVertexAttribPointer(attributes.instancePhase, 1, GL_FLOAT, GL_FALSE,
+ sizeof(GrassInstanceData),
+ (GLvoid *)offsetof(GrassInstanceData, phaseOffset));
+ glVertexAttribDivisor(attributes.instancePhase, 1);
+
+ // instanceHeight: float at offset 16 (after phaseOffset)
+ glEnableVertexAttribArray(attributes.instanceHeight);
+ glVertexAttribPointer(attributes.instanceHeight, 1, GL_FLOAT, GL_FALSE,
+ sizeof(GrassInstanceData),
+ (GLvoid *)offsetof(GrassInstanceData, heightScale));
+ glVertexAttribDivisor(attributes.instanceHeight, 1);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
}
-void GrassRenderer::render(Renderer3d* renderer) {
+void GrassRenderer::update(f32 dtSeconds) { time += dtSeconds; }
+void GrassRenderer::render(Renderer3d *renderer) {
+ useShader(shader);
+ setShaderMat4(uniforms.projection, renderer->projection);
+ setShaderMat4(uniforms.view, renderer->view);
+ setShaderFloat(uniforms.time, time);
+ setShaderFloat(uniforms.bladeWidth, bladeWidth);
+ setShaderFloat(uniforms.bladeHeight, bladeHeight);
+ setShaderFloat(uniforms.swayAmount, swayAmount);
+
+ // Build and upload instance data
+ GrassInstanceData instanceData[NUM_GRASS_BLADES];
+ for (i32 i = 0; i < NUM_GRASS_BLADES; i++) {
+ instanceData[i].x = grassBlades[i].position.x;
+ instanceData[i].y = grassBlades[i].position.y;
+ instanceData[i].z = grassBlades[i].position.z;
+ instanceData[i].phaseOffset = grassBlades[i].top_offset.x;
+ instanceData[i].heightScale = grassBlades[i].top_offset.y;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, instanceVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0,
+ NUM_GRASS_BLADES * sizeof(GrassInstanceData), instanceData);
+
+ glBindVertexArray(vao);
+ glDrawArraysInstanced(GL_TRIANGLES, 0, 6, NUM_GRASS_BLADES);
+ glBindVertexArray(0);
}
void GrassRenderer::unload() {
-
+ if (vao)
+ glDeleteVertexArrays(1, &vao);
+ if (quadVbo)
+ glDeleteBuffers(1, &quadVbo);
+ if (instanceVbo)
+ glDeleteBuffers(1, &instanceVbo);
+ if (shader)
+ glDeleteProgram(shader);
+ vao = 0;
+ quadVbo = 0;
+ instanceVbo = 0;
+ shader = 0;
}
diff --git a/themes/src/spring/grass_renderer.hpp b/themes/src/spring/grass_renderer.hpp
index 88879f3..14ef067 100644
--- a/themes/src/spring/grass_renderer.hpp
+++ b/themes/src/spring/grass_renderer.hpp
@@ -5,29 +5,59 @@
#include "mathlib.h"
#include "types.h"
-const i32 GRASS_BLADES_PER_ROW = 24;
-const i32 GRASS_BLADES_PER_COL = 24;
+const i32 GRASS_BLADES_PER_ROW = 48;
+const i32 GRASS_BLADES_PER_COL = 48;
const i32 NUM_GRASS_BLADES = GRASS_BLADES_PER_ROW * GRASS_BLADES_PER_COL;
struct GrassRendererLoadData {
- Vector2 origin = Vector2(0, 0);
- Vector2 area = Vector2(480, 480);
- f32 grassHeight = 12.f;
+ Vector2 origin = Vector2(0, 0);
+ Vector2 area = Vector2(480, 480);
+ f32 grassHeight = 4.f;
};
struct GrassUpdateData {
- Vector3 position;
- Vector2 top_offset;
+ Vector3 position;
+ Vector2 top_offset; // top_offset.x stores per-blade sway phase offset
+};
+
+struct GrassInstanceData {
+ float x, y, z;
+ float phaseOffset;
+ float heightScale;
};
struct GrassRenderer {
-
- GrassUpdateData grassBlades[NUM_GRASS_BLADES];
-
- void load(GrassRendererLoadData params, Renderer3d* renderer);
- void update(f32 dtSeconds);
- void render(Renderer3d* renderer);
- void unload();
+ GrassUpdateData grassBlades[NUM_GRASS_BLADES];
+
+ u32 vao = 0;
+ u32 quadVbo = 0;
+ u32 instanceVbo = 0;
+ u32 shader = 0;
+ f32 time = 0.f;
+ f32 bladeWidth = 1.5f;
+ f32 bladeHeight = 6.f;
+ f32 swayAmount = 0.3f;
+
+ struct {
+ i32 position;
+ i32 instancePos;
+ i32 instancePhase;
+ i32 instanceHeight;
+ } attributes;
+
+ struct {
+ i32 projection;
+ i32 view;
+ i32 time;
+ i32 bladeWidth;
+ i32 bladeHeight;
+ i32 swayAmount;
+ } uniforms;
+
+ void load(GrassRendererLoadData params, Renderer3d *renderer);
+ void update(f32 dtSeconds);
+ void render(Renderer3d *renderer);
+ void unload();
};
#endif
diff --git a/themes/src/spring/spring_theme.cpp b/themes/src/spring/spring_theme.cpp
index 8b09366..4b62795 100644
--- a/themes/src/spring/spring_theme.cpp
+++ b/themes/src/spring/spring_theme.cpp
@@ -1,6 +1,8 @@
#include "spring_theme.hpp"
#include "../renderer_3d.h"
+#include "../shader.h"
#include "../shader_fetcher.hpp"
+#include "../shapes_2d.h"
#include <cstdio>
#include <emscripten/fetch.h>
@@ -49,7 +51,15 @@ SpringTheme::~SpringTheme() { unload(); }
void SpringTheme::load(WebglContext *context) {
state = SpringThemeState::Loading;
renderer.context = context;
- renderer.clearColor = Vector4(160, 231, 160, 255.f).toNormalizedColor();
+ renderer.clearColor = Vector4(174, 216, 230, 255.f).toNormalizedColor();
+
+ renderer2d.load(context);
+ background = new RectangularGradient(
+ renderer2d, Vector4(174, 216, 230, 255).toNormalizedColor(),
+ Vector4(144, 238, 144, 255).toNormalizedColor(), renderer2d.get_width(),
+ renderer2d.get_height(), {0, 0});
+
+ grassRenderer.load({Vector2(0, -20), Vector2(96, 96), 3.f}, &renderer);
fetch_shader({"themes/src/_shaders/renderer3d.vert",
"themes/src/_shaders/renderer3d.frag"},
@@ -74,6 +84,9 @@ inline f32 rotationLerp(f32 start, f32 target, f32 t) {
}
void SpringTheme::update(f32 dtSeconds) {
+ if (state != SpringThemeState::Loading) {
+ grassRenderer.update(dtSeconds);
+ }
switch (state) {
case SpringThemeState::Loading:
return;
@@ -98,6 +111,15 @@ void SpringTheme::update(f32 dtSeconds) {
yDir = -1;
bunnyTarget = bunnyPosition + Vector3(randomFloatBetween(0, xDir * 25), 0,
randomFloatBetween(0, yDir * 25));
+ // Clamp bunnyTarget to within the grass circle (origin=(0,-20), radius=48)
+ const Vector3 grassCenter(0, 0, -20);
+ const f32 grassRadius = 48.f;
+ Vector3 toTarget = bunnyTarget - grassCenter;
+ toTarget.y = 0;
+ if (toTarget.length() > grassRadius) {
+ toTarget = toTarget.normalize() * grassRadius;
+ bunnyTarget = Vector3(grassCenter.x + toTarget.x, 0, grassCenter.z + toTarget.z);
+ }
auto direction = (bunnyTarget - bunnyPosition);
auto distance = direction.length();
direction = direction.normalize();
@@ -197,12 +219,29 @@ void SpringTheme::update(f32 dtSeconds) {
void SpringTheme::render() {
renderer.render();
+
+ // Draw the 2D gradient background without writing to the depth buffer so
+ // the 3D content rendered afterwards is unobstructed.
+ glDepthMask(GL_FALSE);
+ useShader(renderer2d.shader);
+ setShaderMat4(renderer2d.uniforms.projection, renderer2d.projection);
+ background->render();
+ glDepthMask(GL_TRUE);
+
if (state != SpringThemeState::Loading) {
+ grassRenderer.render(&renderer);
+ // Restore the 3D renderer's shader after the grass shader took over
+ useShader(renderer.shader);
+ setShaderMat4(renderer.uniforms.projection, renderer.projection);
+ setShaderMat4(renderer.uniforms.view, renderer.view);
bunnyMesh.render(&renderer);
}
}
void SpringTheme::unload() {
renderer.unload();
+ renderer2d.unload();
+ delete background;
bunnyMesh.unload();
+ grassRenderer.unload();
}
diff --git a/themes/src/spring/spring_theme.hpp b/themes/src/spring/spring_theme.hpp
index 6079958..4ee5684 100644
--- a/themes/src/spring/spring_theme.hpp
+++ b/themes/src/spring/spring_theme.hpp
@@ -2,44 +2,50 @@
#define SPRING_THEME_HPP
#include "../mathlib.h"
-#include "../types.h"
+#include "../renderer_2d.h"
#include "../renderer_3d.h"
#include "../theme.h"
+#include "../types.h"
+#include "grass_renderer.hpp"
+class RectangularGradient;
enum class SpringThemeState {
- Loading = 0,
- LoadedShader,
- LoadedBunny,
- PreHop,
- Hopping,
- Idle
+ Loading = 0,
+ LoadedShader,
+ LoadedBunny,
+ PreHop,
+ Hopping,
+ Idle
};
class SpringTheme : public Theme {
public:
- SpringTheme(WebglContext*);
- ~SpringTheme();
- Renderer3d renderer;
- SpringThemeState state;
- f32 bunnySpeed = 5.f;
- Vector3 bunnyPosition = Vector3(0, 0, 0);
- Vector3 bunnyTarget = Vector3(0, 0, 0);
- Vector3 hopIncrement = Vector3(0, 0, 0);
-
- f32 numHops = 0;
- f32 hopCount = 0;
- f32 bunnyHopAnimationTimer = 0.f;
- f32 stateTimer = 0.f;
- f32 bunnyRotation = 0.f;
- f32 targetRotation = 0.f;
-
- Mesh3d bunnyMesh;
-
- void load(WebglContext*);
- void update(f32 dtSeconds);
- void render();
- void unload();
+ SpringTheme(WebglContext *);
+ ~SpringTheme();
+ Renderer3d renderer;
+ SpringThemeState state;
+ f32 bunnySpeed = 5.f;
+ Vector3 bunnyPosition = Vector3(0, 0, 0);
+ Vector3 bunnyTarget = Vector3(0, 0, 0);
+ Vector3 hopIncrement = Vector3(0, 0, 0);
+
+ f32 numHops = 0;
+ f32 hopCount = 0;
+ f32 bunnyHopAnimationTimer = 0.f;
+ f32 stateTimer = 0.f;
+ f32 bunnyRotation = 0.f;
+ f32 targetRotation = 0.f;
+
+ Mesh3d bunnyMesh;
+ GrassRenderer grassRenderer;
+ Renderer2d renderer2d;
+ RectangularGradient *background;
+
+ void load(WebglContext *);
+ void update(f32 dtSeconds);
+ void render();
+ void unload();
};
#endif