From da0eedbf1733e40613215ecd117e1a4e049089ad Mon Sep 17 00:00:00 2001 From: Matt Kosarek Date: Thu, 19 Feb 2026 16:58:58 -0500 Subject: Removed photo gallery + added cute little grass rendering for the rabbit and a nice gradient background --- _posts/sitemap.org | 2 +- public/images/dog_and_me-min.jpg | Bin 391836 -> 0 bytes public/images/dog_and_me.jpg | Bin 1095273 -> 0 bytes public/images/ebi.jpg | Bin 727109 -> 0 bytes public/images/friends-min.jpg | Bin 935190 -> 0 bytes public/images/friends.jpg | Bin 2094114 -> 0 bytes public/images/gardens-min.jpg | Bin 41165 -> 0 bytes public/images/gardens.jpg | Bin 44843 -> 0 bytes public/images/portrait-min.jpg | Bin 329679 -> 0 bytes public/images/portrait.jpg | Bin 2324531 -> 0 bytes public/images/resume.jpg | Bin 44843 -> 0 bytes public/index.html | 34 ------- public/posts/dec_29_2025.html | 18 ++-- public/posts/feed.xml | 2 +- public/posts/hello.html | 74 +++++++-------- public/posts/jul_28_2025.html | 6 +- public/posts/june_08_2025.html | 6 +- public/posts/may_06_2025.html | 6 +- public/posts/sitemap.html | 20 ++-- themes/dist/output.wasm | Bin 101674 -> 106007 bytes themes/meson.build | 6 +- themes/src/_shaders/grass.frag | 11 +++ themes/src/_shaders/grass.vert | 25 +++++ themes/src/main.cpp | 175 +++++++++++++++++++---------------- themes/src/main_loop.cpp | 48 +++++----- themes/src/renderer_2d.h | 90 +++++++++--------- themes/src/shaders/grass_frag.cpp | 14 +++ themes/src/shaders/grass_frag.h | 4 + themes/src/shaders/grass_vert.cpp | 28 ++++++ themes/src/shaders/grass_vert.h | 4 + themes/src/spring/grass_renderer.cpp | 145 +++++++++++++++++++++++++---- themes/src/spring/grass_renderer.hpp | 58 +++++++++--- themes/src/spring/spring_theme.cpp | 41 +++++++- themes/src/spring/spring_theme.hpp | 64 +++++++------ 34 files changed, 570 insertions(+), 311 deletions(-) delete mode 100644 public/images/dog_and_me-min.jpg delete mode 100644 public/images/dog_and_me.jpg delete mode 100644 public/images/ebi.jpg delete mode 100644 public/images/friends-min.jpg delete mode 100644 public/images/friends.jpg delete mode 100644 public/images/gardens-min.jpg delete mode 100644 public/images/gardens.jpg delete mode 100644 public/images/portrait-min.jpg delete mode 100644 public/images/portrait.jpg delete mode 100644 public/images/resume.jpg create mode 100644 themes/src/_shaders/grass.frag create mode 100644 themes/src/_shaders/grass.vert create mode 100644 themes/src/shaders/grass_frag.cpp create mode 100644 themes/src/shaders/grass_frag.h create mode 100644 themes/src/shaders/grass_vert.cpp create mode 100644 themes/src/shaders/grass_vert.h 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 Binary files a/public/images/dog_and_me-min.jpg and /dev/null differ diff --git a/public/images/dog_and_me.jpg b/public/images/dog_and_me.jpg deleted file mode 100644 index 1dd3e13..0000000 Binary files a/public/images/dog_and_me.jpg and /dev/null differ diff --git a/public/images/ebi.jpg b/public/images/ebi.jpg deleted file mode 100644 index 8f4b77e..0000000 Binary files a/public/images/ebi.jpg and /dev/null differ diff --git a/public/images/friends-min.jpg b/public/images/friends-min.jpg deleted file mode 100644 index 0fbb4c1..0000000 Binary files a/public/images/friends-min.jpg and /dev/null differ diff --git a/public/images/friends.jpg b/public/images/friends.jpg deleted file mode 100644 index b2c0e5e..0000000 Binary files a/public/images/friends.jpg and /dev/null differ diff --git a/public/images/gardens-min.jpg b/public/images/gardens-min.jpg deleted file mode 100644 index 3503bfc..0000000 Binary files a/public/images/gardens-min.jpg and /dev/null differ diff --git a/public/images/gardens.jpg b/public/images/gardens.jpg deleted file mode 100644 index 450dbd2..0000000 Binary files a/public/images/gardens.jpg and /dev/null differ diff --git a/public/images/portrait-min.jpg b/public/images/portrait-min.jpg deleted file mode 100644 index 49e8cde..0000000 Binary files a/public/images/portrait-min.jpg and /dev/null differ diff --git a/public/images/portrait.jpg b/public/images/portrait.jpg deleted file mode 100644 index 26e2d81..0000000 Binary files a/public/images/portrait.jpg and /dev/null differ diff --git a/public/images/resume.jpg b/public/images/resume.jpg deleted file mode 100644 index 450dbd2..0000000 Binary files a/public/images/resume.jpg and /dev/null 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 @@ -
-
- -
- Hanging with my son, circa July 2023. -
-
-
- -
- Me at Longwood Gardens, circa December 2021. -
-
-
- -
- Me in front of my desktop, circa August 2021. -
-
-
- -
- Hanging with my dog named Rizzy, circa May 2020. -
-
-
- -
- Hanging with my friends, circa July 2019. -
-
- - -

About Me

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 @@

-
-

What have I been up to?

-
+
+

What have I been up to?

+

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.

-
-

Miracle Update

-
+
+

Miracle Update

+

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,

@@ -108,9 +108,9 @@ v0.9.0 of Miracle will probably be a long time in the making. My estimate is tha

-
-

Conclusion

-
+
+

Conclusion

+

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.

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 @@ https://matthewkosarek.xyz/ The RSS feed for Matthew Kosarek's Blog en-us - Wed, 18 February 2026 17:14:00 -0400 + Thu, 19 February 2026 15:52:00 -0400 Update December 29, 2025 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 @@
-
-

TLDR

-
+
+

TLDR

+
-
-

Introduction

-
+
+

Introduction

+

I've recently fallen in love with org-mode, specifically when I use it with org-roam. 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: org-mode. 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:

@@ -70,9 +70,9 @@ And that's pretty much it for now. Without further ado, let's jump into getting

-
-

Basic HTML File

-
+
+

Basic HTML File

+

As a pilot, we are going to use this org file that I am currently writing (hello.org) as our guinea pig. The goal is to have this org file be our very first blog post.

@@ -112,9 +112,9 @@ We then do a chmod +x publish.sh to make it an executable and run i

-
-

Disabling features that we don't want

-
+
+

Disabling features that we don't want

+

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.

@@ -143,9 +143,9 @@ The next thing will be to remove some of the generated items that I didn't ask f
-
-

Styling & Code Highlighting

-
+
+

Styling & Code Highlighting

+

Next thing on our list is custom styling. This can be achieved by first installing the htmlize package from melpa / elpa. 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" (reference). 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 <span style="text-decoration: underline">...</span>). However, we are more interested in styling everything by ourselves: we don't want htmlize making assumptions about what underlining means to us! Luckily, htmlize gives us the option to export with class names instead of inline styles so that we can specify each style for ourselves.

@@ -315,9 +315,9 @@ If we run the publish again, we can see that we have full styling on our code sn

-
-

Images

-
+
+

Images

+

Our first two criteria have been met! Next on the list is solving images. As an example, let's use this squirrel image that I found online with an open source license. The ideal situation would be:

@@ -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:

-
+

squirrel.jpg

Figure 1: A Cute Squirrel

-
-

Creation Date

-
+
+

Creation Date

+

Let's add the creation date below the title next. To start, we will modify the publish command to remove the title (:with-title nil) and, in its place, show a preamble bit of HTML that contains a formatted div with the title and the "last modified" span.z

@@ -430,9 +430,9 @@ Note that the downside of this is that the created date will change whenever you

-
-

Generating the Directory

-
+
+

Generating the Directory

+

For every org file in my _posts folder, I would like to create a link to the generated HTML file at the /posts.html page of my website. You can think of this as the "directory" of all posts. My criteria is:

@@ -492,9 +492,9 @@ If you open the sitemap.html file in your browser, you will see a b From here, you may customize it however you like. The following are my customizations.

-
-

Sitemap Title

-
+
+

Sitemap Title

+

I changed the title to "Matthew's Blog Posts".

@@ -516,9 +516,9 @@ I changed the title to "Matthew's Blog Posts".
-
-

Format blog entries in the list

-
+
+

Format blog entries in the list

+

I like to include the creation date on the blog posts. To do this, we can use org-publish-find-property to find the date property of the org file. Afterward, we can format a string that includes our formatted timestamp and the org-publish-sitemap-default-entry, which is just a link with the title of the post.

@@ -539,9 +539,9 @@ I like to include the creation date on the blog posts. To do this, we can use
-
-

Tags & Filtering

-
+
+

Tags & Filtering

+

I use Org-roam 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:

@@ -761,9 +761,9 @@ Finally, let's append the following to posts/posts.css so that our
-
-

Conclusion

-
+
+

Conclusion

+

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.

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 @@
-
-

What have I been up to?

-
+
+

What have I been up to?

+

Whoops! I missed this month's update by a long shot, but I still want to get it out there before the end of the month.

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 @@
-
-

What have I been up to?

-
+
+

What have I been up to?

+

Another month has gone by, so I guess it's time to see what I've been up to.

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 @@
-
-

What have I been up to?

-
+
+

What have I been up to?

+

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 :)

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 @@
  • Update December 29, 2025

    -
    +

    December 29, 2025

    -
    +

    update

    @@ -49,13 +49,13 @@ update
  • Update July 28, 2025

    -
    +

    July 28, 2025

    -
    +

    update

    @@ -64,13 +64,13 @@ update
  • Update June 08, 2025

    -
    +

    June 08, 2025

    -
    +

    update

    @@ -79,13 +79,13 @@ update
  • Update May 06, 2025

    -
    +

    May 06, 2025

    -
    +

    update

    @@ -94,13 +94,13 @@ update
  • Hello, Org

    -
    +

    June 20, 2023

    -
    +

    technology,home

    diff --git a/themes/dist/output.wasm b/themes/dist/output.wasm index 550e5ff..d0b2635 100755 Binary files a/themes/dist/output.wasm and b/themes/dist/output.wasm 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 #include 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 #include -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(deltaTime) / 1000.f; + return true; + } + + long deltaTime = time - mainLoop->lastTime; + mainLoop->lastTime = time; + mainLoop->elapsedTime += deltaTime; + mainLoop->numFrames++; + float deltaTimeSeconds = static_cast(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 +#include -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 #include @@ -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 -- cgit v1.2.1