{"id":80,"date":"2025-01-14T23:47:41","date_gmt":"2025-01-14T23:47:41","guid":{"rendered":"https:\/\/blogs.oregonstate.edu\/codedcat\/?p=80"},"modified":"2025-01-14T23:47:41","modified_gmt":"2025-01-14T23:47:41","slug":"back-to-work-winter-term-adventures-with-bsp-and-procedural-maps","status":"publish","type":"post","link":"https:\/\/blogs.oregonstate.edu\/codedcat\/2025\/01\/14\/back-to-work-winter-term-adventures-with-bsp-and-procedural-maps\/","title":{"rendered":"Back to Work: Winter Term Adventures with BSP and Procedural Maps"},"content":{"rendered":"\n<p>Hello again, and welcome back to The Coded Cat! After a much-needed holiday break, I&#8217;m diving back into our capstone project for winter term. This term, we&#8217;re continuing work on <strong>Wildfire Command<\/strong> (formerly known as Sim Firefighter), our browser-based wildfire management simulation. One of my recent tasks has been to improve our map generation system, and I wanted to share how we tackled this using Binary Space Partitioning (BSP) and Perlin Noise.<\/p>\n\n\n\n<p>So, grab a cozy drink and let&#8217;s talk about procedural map generation (and how cats make debugging more fun).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfaf <strong>What is Binary Space Partitioning?<\/strong><\/h2>\n\n\n\n<p>Imagine you&#8217;re slicing a cake into smaller pieces. Binary Space Partitioning (BSP) is a method where we split a large space (like our map) into smaller regions by dividing it either vertically or horizontally. We keep slicing until all pieces are small enough to meet our minimum size requirement.<\/p>\n\n\n\n<p>For our game, we use BSP to create distinct terrain zones (like forests, lakes, and firebreaks), which we then populate with Perlin Noise to add natural-looking variations.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udee0 <strong>Step 1: Implementing BSP Partitioning<\/strong><\/h2>\n\n\n\n<p>Here\u2019s a simplified version of how BSP works in our project:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nfunction partitionMap(width, height, minSize) {\n    if (width &amp;lt;= minSize || height &amp;lt;= minSize) {\n        return &#x5B;createRegion(width, height)];\n    }\n\n    let splitVertical = Math.random() &amp;gt; 0.5;\n    let splitPoint = randomPoint(minSize, splitVertical ? width : height);\n\n    let region1, region2;\n    if (splitVertical) {\n        region1 = partitionMap(splitPoint, height, minSize);\n        region2 = partitionMap(width - splitPoint, height, minSize);\n    } else {\n        region1 = partitionMap(width, splitPoint, minSize);\n        region2 = partitionMap(width, height - splitPoint, minSize);\n    }\n\n    return &#x5B;...region1, ...region2];\n}\n\n<\/pre><\/div>\n\n\n<p>In plain English, we:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Check if the current region is small enough. If yes, stop splitting.<\/li>\n\n\n\n<li>Randomly decide whether to split the region vertically or horizontally.<\/li>\n\n\n\n<li>Calculate where to split the region.<\/li>\n\n\n\n<li>Recursively split the resulting subregions.<\/li>\n<\/ol>\n\n\n\n<p>The result? A beautifully partitioned map, ready for more detailed terrain generation!<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udf04 <strong>Step 2: Integrating BSP with the Map Generator<\/strong><\/h2>\n\n\n\n<p>After implementing BSP, we integrated it into our existing map generation system. The <code>MapGenerator<\/code> class was modified to use BSP to partition the map grid and apply Perlin Noise within each partitioned region.<\/p>\n\n\n\n<p>If you&#8217;re unfamiliar with Perlin Noise, I covered it in a previous post. You can check out <a href=\"https:\/\/blogs.oregonstate.edu\/codedcat\/2024\/12\/02\/developing-procedural-terrain-for-sim-firefighter\/\">this blog post<\/a> for a deep dive into how Perlin Noise works and how we use it for natural-looking terrain.<\/p>\n\n\n\n<p>Here\u2019s a high-level overview of how we did it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nclass MapGenerator {\n    constructor(width, height, minSize) {\n        this.width = width;\n        this.height = height;\n        this.minSize = minSize;\n        this.perlin = new PerlinNoise(width, height);\n        this.bsp = new BSPPartition(width, height, minSize);\n\n        this.grid = this.generateMap();\n    }\n\n    generateMap() {\n        const grid = &#x5B;];\n        const partitions = this.bsp.partition({ x: 0, y: 0, width: this.width, height: this.height });\n\n        partitions.forEach(partition =&amp;gt; {\n            for (let y = partition.y; y &amp;lt; partition.y + partition.height; y++) {\n                const row = &#x5B;];\n                for (let x = partition.x; x &amp;lt; partition.x + partition.width; x++) {\n                    const noiseValue = this.perlin.getNoise(x, y);\n                    const terrain = this.getTerrainFromNoise(noiseValue);\n                    row.push(new TerrainTile(x, y, terrain));\n                }\n                grid.push(row);\n            }\n        });\n\n        return grid;\n    }\n\n    getTerrainFromNoise(value) {\n        if (value &amp;lt; -0.5) return &#039;water&#039;;\n        if (value &amp;lt; 0) return &#039;forest&#039;;\n        if (value &amp;lt; 0.5) return &#039;grass&#039;;\n        return &#039;shrub&#039;;\n    }\n}\n\n<\/pre><\/div>\n\n\n<p>By integrating BSP, we ensured that each partitioned region could have unique terrain features, making our maps more diverse and engaging.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udee0 <strong>Step 3: Debugging with Utility Functions<\/strong><\/h2>\n\n\n\n<p>Debugging procedural generation can be tricky, so we added some utility functions to help visualize the results. For example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nfunction printMap(grid) {\n    grid.forEach(row =&amp;gt; {\n        console.log(row.map(tile =&amp;gt; tile.terrain).join(&#039; | &#039;));\n    });\n}\n\n<\/pre><\/div>\n\n\n<p>This prints the terrain types for each tile in the grid, making it easier to spot any anomalies.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 <strong>Why BSP and Perlin Noise Work Well Together<\/strong><\/h2>\n\n\n\n<p>The combination of BSP and Perlin Noise gives us structured yet natural-looking maps. BSP ensures the map is divided into logical regions, while Perlin Noise adds the randomness and variation that makes each map feel unique.<\/p>\n\n\n\n<p>By combining these techniques, we can generate diverse maps with different terrain features, making our game more engaging and realistic.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udc3e <strong>Bonus Cat Photo!<\/strong><\/h3>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"1024\" src=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/8085\/files\/2025\/01\/IMG_3606-768x1024.jpg\" alt=\"\" class=\"wp-image-81\" srcset=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/8085\/files\/2025\/01\/IMG_3606-768x1024.jpg 768w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/8085\/files\/2025\/01\/IMG_3606-225x300.jpg 225w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/8085\/files\/2025\/01\/IMG_3606-1152x1536.jpg 1152w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/8085\/files\/2025\/01\/IMG_3606-1536x2048.jpg 1536w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/8085\/files\/2025\/01\/IMG_3606-1200x1600.jpg 1200w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/8085\/files\/2025\/01\/IMG_3606-scaled.jpg 1920w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px\" \/><\/figure>\n<\/div>\n\n\n<p>So this isn&#8217;t a photo of one of my cats, but it is a cat I saw in a Lego store when I went to visit family over the Holidays! I thought this display was really fun so I definitely had to share it!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello again, and welcome back to The Coded Cat! After a much-needed holiday break, I&#8217;m diving back into our capstone project for winter term. This term, we&#8217;re continuing work on Wildfire Command (formerly known as Sim Firefighter), our browser-based wildfire management simulation. One of my recent tasks has been to improve our map generation system, &hellip; <a href=\"https:\/\/blogs.oregonstate.edu\/codedcat\/2025\/01\/14\/back-to-work-winter-term-adventures-with-bsp-and-procedural-maps\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Back to Work: Winter Term Adventures with BSP and Procedural Maps&#8221;<\/span><\/a><\/p>\n","protected":false},"author":14468,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-80","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/posts\/80","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/users\/14468"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/comments?post=80"}],"version-history":[{"count":1,"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/posts\/80\/revisions"}],"predecessor-version":[{"id":82,"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/posts\/80\/revisions\/82"}],"wp:attachment":[{"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/media?parent=80"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/categories?post=80"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/codedcat\/wp-json\/wp\/v2\/tags?post=80"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}