<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Acko.net]]></title>
  <link href="https://acko.net/atom.xml" rel="self"/>
  <link href="https://acko.net/"/>
  <updated>2026-03-05T12:10:39+01:00</updated>
  <id>https://acko.net</id>
  <author>
    <name><![CDATA[Steven Wittens]]></name>
    
  </author>

  
  <entry>
    <title type="html"><![CDATA[The L in "LLM" Stands for Lying]]></title>
    <link href="https://acko.net/blog/the-l-in-llm-stands-for-lying/"/>
    <updated>2026-03-04T00:00:00+01:00</updated>
    <id>https://acko.net/blog/the-l-in-llm-stands-for-lying</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  <h2 class="sub">On Evitability in Use of AI</h2>
</div></div>

<div class="c"></div>

<p><img src="https://acko.net/files/ai-llms/cover.jpg" style="position: absolute; left: -5000px; top: 0;" alt="Cover Image" /></p>

<div class="g8 i2"><div class="pad">

<p>If the hype is to be believed, software development as we know it is over. Strangely though, despite now years of LLM-powered tooling, the results look, feel and function mostly the same as they ever did: barely.</p>

<p>It's undeniable there's a metric gigaton of hype surrounding the technology. It drives the enormous amounts of money and infrastructure being poured into it, which in turn demands more hype to justify the investment. The history of hyperbole is already evident, as new models continue to be trained to reach promises which now-retired models were already supposed to deliver.</p>

<p>So allow me to drop a line that would shock a weathered San Franciscan more than open defecation on Market Street: <i>it's perfectly okay not to use AI.</i></p>

<p>It doesn't make you a troglodyte. It won't leave you choking behind in the dust as self-fashioned techno-wizards bring their agents to bear. In fact, it seems far less stressful and far more satisfying than the&nbsp;alternative.</p>


</div></div>

<div class="c"></div>
<div class="mt1"></div>

<div class="g10 i1"><div class="pad tc">
  <img src="https://acko.net/files/ai-llms/escher-reptiles.jpg" alt="Escher - Reptiles" class="" />
  <a target="_blank" class="credit" href="https://www.wbur.org/news/2018/02/08/mfa-mc-escher">Source</a>
  <p class="tc"><i class="text-muted">M.C. Escher, "Reptiles" – 1943</i></p>
</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">
  
<h2 class="mt2">Craft vs Kraft</h2>

<p>In all the talk of what it is that LLMs do and don't, there are a lot of ways to frame what is happening. The positive spin includes helpfulness, cleverness, creativity and productivity. The negative spin points at lazyness, disposability, theft, and decay of knowledge. But there's one word that's remarkably absent in the discourse. That word is forgery.</p>

<ul class="indent">
<li>If someone produces a painting in the style of Van Gogh, and passes it off as being made by Van Gogh, by putting his signature on it, that painting is a forgery.</li>
<li class="mt1">If someone produces a legal document by mimicking the format, impersonating the parties, and faking their agreement, that document is a forgery.</li>
<li class="mt1">If someone produces a study by inventing or altering data, making up citations, and cherry-picking results to fit a particular conclusion, that study is a forgery.</li>
</ul>

<p>Whether something is a forgery is innate in the object and the methods used to produce it. It doesn't matter if nobody else ever sees the forged painting, or if it only hangs in a private home. It's a forgery because it's not authentic.</p>

</div></div>

<div class="g4 mt1"><div class="pad">
  <a href="https://www.wright20.com/auctions/2013/04/pablo-picasso-master-drawings/5" target="_blank"><img src="https://acko.net/files/ai-llms/picasso-buste-de-femme.jpg" alt="Picasso - Buste de Femme" class="" /></a>
  <p class="tc"><i class="text-muted">P. Picasso, "Buste De Femme" – 1942</i></p>
</div></div>

<div class="g8"><div class="pad">

<p>In this perspective, LLMs do something very specific: they allow individuals to make forgeries of their own potential output, or that of someone else, faster than they could make it themselves.</p>

<p>The act of forgery is the act of imitation. This by itself is strictly-speaking legal, as a form of fiction or self-expression. It's only when one attempts to use a forgery as a substitute for the authentic thing that it creates problems. How this plays out in practice depends on the situation, and mainly depends on what authenticity would&nbsp;signify.</p>

<p>That is, nobody will be arrested for "forging" a letter from Santa Claus, but also, no jurisdiction would allow you to have extremely convincing "imitation money" purely as a collector's item.</p>

</div></div>

<div class="g8 i2 mt1"><div class="pad">

<p>This sort of protectionism is also seen in e.g. controlled-appelation foods like <a href="https://www.youtube.com/watch?v=YQGai2PVHBs
" target="_blank">artisanal cheese</a> or cured ham. These require not just traditional manufacturing methods and high-quality ingredients from farm to table, but also a specific geographic origin. There's a good reason for this.</p>

</div></div>

<div class="g10 i1 mt1"><div class="pad">
  <img src="https://acko.net/files/ai-llms/brie.jpg" alt="Fromagerie Dongé à Triconville" class="" />
  <a target="_blank" class="credit" href="https://www.estrepublicain.fr/edition-de-bar-le-duc/2015/08/30/fabrication-en-images-du-brie-de-meaux-par-la-fromagerie-donge-en-meuse">©</a>
</div></div>

<div class="g8 i2 mt1"><div class="pad">

<p>Producing French <i>"Brie de Meaux"</i> abroad isn't allowed, because it would open the floodgates to inevitable cheaper imitations. This would degrade the brand of the authentic product, and threaten the rare local expertise necessary to produce it, passed down from generation to&nbsp;generation.</p>

<p>The judgement of an individual end-consumer simply isn't sufficient here to ensure proper market function. The range of products that you can get in the store, between which you can choose, has already been pre-decided by factors out of your control. The quality of the artisanal cheese is a stand-in for an entire supply chain, often run on modern methods, which cannot simply be transplanted elsewhere without enormous investments in human capital, infrastructure and agriculture. This isn't mere romanticism.</p>

<p>Every society has to draw a line somewhere on the spectrum between <i>"traditional artisanal cheese"</i> and <i>"fake eggs made from industrial chemicals"</i>, if they don't want people to die from malnutrition or poisoning. But it's the ones that understand and maintain the value of foodcraft that don't end up with <a href="https://en.wikipedia.org/wiki/Obesity_in_Nauru" target="_blank">70%+ obesity rates</a>.</p>

</div></div>

<div class="g10 i1 mt1"><div class="pad">
  <img src="https://acko.net/files/ai-llms/spam.jpg" alt="Cans of Span" class="" />
  <a target="_blank" class="credit" href="https://edition.cnn.com/2023/08/18/business/maui-fire-spam-hormel/index.html">©</a>
</div></div>

<div class="g8 i2"><div class="pad">

<h2 class="mt3">Distrust and Verify</h2>

<p>The parallels to LLM-driven software coding are not difficult to find. The craft of writing software is being threatened by a literal flood of cheap imitations.</p>

<p>Open source software maintainers have been one of the first to feel the downsides. They already had a ton of difficulty finding motivated contributors and bringing them up to speed on the project's goals and engineering mindset. The last thing they needed was to receive slop-coded pull requests from contributors merely looking to cheat their way into having a credible GitHub resumé.</p>

</div></div>

<div class="g2 i1 mt1">
  <div class="pad mobile-vanish">
    <img src="https://acko.net/files/ai-llms/github-squares-1.png" alt="Github Squares" class="" />
  </div>
  <div class="pad mobile-appear">
    <img src="https://acko.net/files/ai-llms/github-squares-1-m.png" alt="Github Squares" class="" />  
  </div>
</div>

<div class="g8"><div class="pad">

<p>Being on the receiving end of this is both demeaning and absurd, as the only thing the vibe-coder can do with the feedback you give them is paste it back into the tool that produced the errors in the first place.</p>

<p>As a result, projects have <a href="https://github.com/tldraw/tldraw/issues/7695" target="_blank">closed down public contributions</a> and <a href="https://hackaday.com/2026/01/26/the-curl-project-drops-bug-bounties-due-to-ai-slop/">dropped their bug bounties</a>. Others just <a href="https://406.fail" target="_blank">mock the posers</a> and hope they go away. What this certainly isn't is helpful, clever, creative or productive.</p>

<p>In day to day coding, working alongside vibe-coding co-workers has similar effects. While new employees might seem to get up to speed much quicker, in reality they're merely offloading those arduous first weeks to a bot, hoping no-one else notices.</p>

<p>In the process, they'll inject run-of-the-mill mediocrity all over the place, when what you were really hoping for was their specific perspective. Anno 2026, if a new employee produces an extremely detailed PR with lots of explanation and comments, doubt every word.</p>

</div></div>

<div class="g2 ir1 mt1 r">
  <div class="pad mobile-vanish">
    <img src="https://acko.net/files/ai-llms/github-squares-2.png" alt="Github Squares" class="" />
  </div>
  <div class="pad mobile-appear">
    <img src="https://acko.net/files/ai-llms/github-squares-2-m.png" alt="Github Squares" class="" />  
  </div>
</div>

<div class="g8 i1"><div class="pad">

<p>Experienced veterans who turn to AI are said to supposedly fare better, producing 10x or even 100x the lines of code from before. When I hear this, I wonder what sort of senior software engineer still doesn't understand that every line of code they run and depend on is a&nbsp;liability.</p>

<p>One of the most remarkable things I've <a href="https://x.com/buccocapital/status/2022782677523345670" target="_blank">heard someone say</a> was that AI coding is a great application of the technology because everything an agent needs to know is explained in the codebase. This is catastrophically wrong and absurd, because if it were true, there would be no actual coding work to do.</p>

<p>It's also a huge tell. The salient difference here is whether an engineer has mostly spent their career solving problems created by other software, or solving problems people already had before there was any software at all. Only the latter will teach you to think about the constraints a problem actually has, and the needs of the users who solve it, which are always far messier than a novice would think.</p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>When software is seen as an end in itself, you end up with a massively over-engineered infrastructure cloud, when it could instead be running on a $10/month VPS, with plenty of money left for both backups and beer.</p>


<h2 class="mt3">Tools for Tools</h2>

<p>Engineers who know their craft can still smell the slop from miles away when reviewing it, despite the "advances" made. It comes in the form of overly repetitive code, unnecessary complexity, and a reluctance to really refactor anything at all, even when it's clearly stale and overdue.</p>

<p>I've also observed several times now that even being senior, with years of familiarity, will not save them from vibe-coding some highly embarrassing goofs, and passing them on like an unpleasant fart.</p>

<p>Trying to imagine what thought-process produced the odd work in question will quickly lead to the answer: none at all. It's not a co-pilot, it's just on auto-pilot.</p>

<p>The same applies to vibe-coders themselves, and the reactions are largely predictable. The notion is being felt that slop code is bad code, full of bugs, with e.g. Microsoft's Co-pilot Discord recently <a href="https://www.windowscentral.com/artificial-intelligence/microsoft-copilot/microsoft-accidentally-kicked-off-a-copilot-revolt-by-banning-the-word-microslop-on-discord">banning</a> the insult <i>"Microslop"</i>. The user backlash was then framed as <i>"spam"</i> and even outright <i>"harmful"</i>, demonstrating that the promise is often worth more than the actual result, and also, that the universe still has a sense of humor.</p>

</div></div>

<div class="c"></div>

<div class="g8 i2 mt2"><div class="pad tc">
  <img src="https://acko.net/files/ai-llms/escher-print-gallery.jpg" alt="Escher - Print Gallery" class="" />
  <a target="_blank" class="credit" href="https://www.wikiart.org/en/m-c-escher/print-gallery">Source</a>
  <p class="tc"><i class="text-muted">M.C. Escher, "Print Gallery" – 1956</i></p>
</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p>Less encouraging is that you'll see these tools referred to as <i>"addicting"</i> or even <i>"the best friend you can have"</i>. While nerds being utterly drawn to computers is as old as the PC revolution itself, there doesn't seem to be an associated cambrian explosion of creativity and accomplishment to go with it.</p>

<p>I can understand why outsiders would be impressed by it, what I don't understand is how so many insiders didn't stop and think about it.</p>

</div></div>

<div class="c"></div>

<div class="g8 i2 mt1"><div class="pad tc">
  <img src="https://acko.net/files/ai-llms/macintosh.jpg" alt="Apple Macintosh, 1984" class="" />
  <a target="_blank" class="credit" href="https://clickamericana.com/media/advertisements/apple-introduces-the-macintosh-personal-computer-1984">©</a>
</div></div>

<div class="c"></div>

<div class="g8 i2 mt1"><div class="pad">

<p>What gets built with AI is really all the glue that's become necessary since said PC revolution, as software applications have gotten more closed, more distributed and more corporate. The options here for end-users are terrible. HTTP APIs don't make things open if every endpoint requires a barely documented JSON blob whose schema changes overnight. Slinging raw database dumps is also not viable, and is only used for disaster recovery. Software has largely rusted shut.</p>

<p>Consider that many companies still primarily run on Excel. What's the Excel of JSON? There is none. So yeah, of course users think they need a machine to translate their intent into code so they can run it. Even then, what's the Jupyter notebooks of&nbsp;JSON?</p>

<p>There's <code>jq</code> of course, but keep in mind that originally it was SQL that was framed as the solution that was going to free businesses and their workers from having to rely on dedicated tools. Look how that worked out... the more things change, the more they stay the same. Is there a standard CRDT-like protocol for syncing editable graphs yet?</p>

<p>Surprisingly, we haven't seen a return to native apps either. It turns out vibe-coding an Electron app is still preferable to vibe-coding on multiple platforms and delivering a tailored experience for each. So where is this famed 100x? If even Apple can't maintain proper form and iconography in their latest OS anymore, what chance does an AI trained on web-slop&nbsp;have?</p>

<p>It says a lot about our industry, it just doesn't say much about engineering at&nbsp;all.</p>

</div></div>

<div class="c"></div>

<div class="g10 i1 mt2"><div class="pad tc">
  <img src="https://acko.net/files/ai-llms/turner-shipwreck.jpg" alt="J.M.W. Turner - The Shipwreck" class="" />
  <a target="_blank" class="credit" href="https://www.tate.org.uk/art/research-publications/the-sublime/joseph-mallord-william-turner-the-shipwreck-r1105577">Source</a>
  <p class="tc"><i class="text-muted">J.M.W. Turner, "The Shipwreck" – 1805</i></p>
</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<h2 class="mt2">And a Bottle of Rum</h2>

<p>Software engineers have largely jumped in without a life-jacket, but not every industry has been as eager. The frame of inevitability is just that, a frame, and one which you should&nbsp;question.</p>

<p>Video games stand out as one market where consumers have pushed back effectively. Numerous titles have already apologized for unlabeled AI content and removed it. Platforms like Steam have <a href="https://store.steampowered.com/news/group/4145017/view/3862463747997849618" target="_blank">clearly signposted policies</a> about it, and <a href="https://www.gamingonlinux.com/2025/02/steamdb-now-lets-you-filter-out-steam-games-with-ai-generation/" target="_blank">tools exist</a> to filter it out.</p>

<p>That said, Steam's policy has been <a href="https://www.techpowerup.com/345302/steam-ai-disclosure-gets-clarification-for-ai-in-dev-tools">recently updated</a> to exclude dev tools used for <i>"efficiency gains"</i>, but which are not used to generate content presented to players.</p>

</div></div>

<div class="c"></div>

<div class="g5 mt1"><div class="pad tc">
  <img src="https://acko.net/files/ai-llms/news-games-removed-slop.png" alt="Games which have removed AI content after release" class="" />
</div></div>

<div class="g7"><div class="pad">

<p>This isn't all that surprising, for two reasons.</p>

<p>The first is that video games are a pure direct-to-consumer market with digital delivery. Gamers really do have all the choices on tap. When they don't like a game or its pricing model, it's the result of choices made by those specific producers. Other titles exist without those flaws, and those get promoted and bought instead. The taste-makers are gamers themselves, who demand transparency.</p>

<p>But the second is that most video games are artistic, and bought for their specific artistic appeal. In art, copy-catting is frowned upon, as it devalues the original and steals the credit. Artists are rationally very sensitive to this, as part of the appeal of art is a creator's unique vision. The art is supposed to function as a personal proof-of-work, whose integrity must be preserved. The proper form of imitation is instead an homage, which respects and evolves an idea at the same time.</p>

</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p>This stands in stark contrast to code, which generally doesn't suffer from re-use at all, or may even benefit from it, if it's infrastructure. It also explains why open source projects are so particularly ill-suited to attracting talented, artistic creatives. The ethos of zero-cost sharing means any artistic design would be instantly pilfered and repurposed without its original context.</p>

<p>Classic procedural generation is noteworthy here as a precedent, which gamers were already familiar with, because by and large it has failed to deliver. The promise of exponential content from a limited source <a href="https://jphanderson.wordpress.com/2016/10/01/joseph-anderson-vs-no-mans-sky/" target="_blank">quickly turns sour</a>, as the main thing a procedural generator does is make the variety in its own outputs worthless.</p>

</div></div>

<div class="c"></div>

<div class="g10 i1 mt1"><div class="pad tc">
  <img src="https://acko.net/files/ai-llms/no-mans-sky-2016.jpg" alt="No Man's Sky - 2016 version" class="" />
  <a target="_blank" class="credit" href="https://www.washingtonpost.com/news/comic-riffs/wp/2016/08/30/no-mans-sky-review-a-game-lost-in-infinite-space/">©</a>
  <p class="tc"><i class="text-muted">No Man's Sky - 2016 version</i></p>
</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">
  
<p>So it's no wonder artists would denounce generative AI as mass-plagiarism when it showed up. It's also no wonder that a bunch of tech entrepreneurs and data janitors wouldn't understand this at all, and would in fact embrace the plagiarism wholesale, <a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/nvidia-accused-of-trying-to-cut-a-deal-with-annas-archive-for-high-speed-access-to-the-massive-pirated-book-haul-allegedly-chased-stolen-data-to-fuel-its-llms">training their models</a> on every pirated shadow library they can get. Or indeed, every code repository out there.</p>

<p>If the output of this is generic, gross and suspicious, there's a very obvious reason for it. The different training samples in the source material are themselves just slop for the machine. Whatever makes the weights go brrr during training.</p>

<p>This just so happens to create the plausible deniability that makes it impossible to say what's a citation, what's a hallucination, and what, if anything, could be considered novel or creative. This is what keeps those shadow libraries illegal, but ChatGPT&nbsp;"legal".</p>

<p>Labeling AI content as AI generated, or watermarking it, is thus largely an exercise in ass-covering, and not in any way responsible&nbsp;disclosure.</p>

<p>It's also what provides the fig leaf that allows many a developer to knock-off for early lunch and early dinner every day, while keeping the meter running, without ever questioning whether the intellectual property clauses in their contract still mean anything at all.</p>

<p>This leaves the engineers in question in an awkward spot however. In order for vibe-coding to be acceptable and justifiable, they have to consider their own output disposable, highly uncreative, and not worthy of credit.</p>

<p class="mt2 mb2 tc" style="opacity: .5">* * *</p>

<p>If you ask me, no court should have ever rendered a judgement on whether AI output as a category is legal or copyrightable, because none of it is sourced. The judgement simply cannot be made, and AI output should be treated like a forgery unless and until proven otherwise.</p>

<p>The solution to the LLM conundrum is then as obvious as it is elusive: the only way to separate the gold from the slop is for LLMs to perform correct source attribution along with&nbsp;inference.</p>

<p>This wouldn't just help with the artistic side of things. It would also reveal how much vibe code is merely just copy/pasted from an existing codebase, while conveniently omitting the original author, license and link.</p>

<p>With today's models, real attribution is a technical impossibility. The fact that an LLM can even mention and cite sources at all is an emergent property of the data that's been ingested, and the prompt being completed. It can only do so when appropriate according to the current position in the text.</p>

<p>There's no reason to think that this is generalizable, rather, it is far more likely that LLMs are merely good at citing things that are frequently and correctly cited. It's citation&nbsp;role-play.</p>

<p>The implications of sourcing-as-a-requirement are vast. What does backpropagation even look like if the weights have to be attributable, and the forward pass auditable? You won't be able to fit <i>that</i> in an <code>int4</code>, that's for sure.</p>

<p>Nevertheless, I think this would be quite revealing, as this is what "AI detection tools" are really trying to solve for backwards. It's crazy that the next big thing after the World Wide Web, and the Google-scale search engine to make use of it, was a technology that cannot tell you where the information comes from, by design. It's...&nbsp;sloppy.</p>

<p>To stop the machines from lying, they have to cite their sources properly. And spoiler, so do the AI companies.</p>

<div class="c"></div>
<div class="mt2"></div>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Zero to Sixty in One Second]]></title>
    <link href="https://acko.net/blog/zero-to-sixty-in-one-second/"/>
    <updated>2013-08-23T00:00:00+02:00</updated>
    <id>https://acko.net/blog/zero-to-sixty-in-one-second</id>
    <content type="html"><![CDATA[<style type="text/css" media="screen">
  .visualizer-activate {
    display: none;
  }
  .visualizer-activate a {
    display: block;
    position: absolute;
    left: 50%;
    top: 37%;
    width: 50px;
    height: 50px;
    margin-left: -25px;
    margin-top: -25px;
    text-align: center;
    vertical-align: middle;
    line-height: 58px;
    font-size: 28px;
    box-sizing: border-box;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    padding-left: 4px;
    border-radius: 25px;
    color: #fff;
    background: rgb(108,123,132);
    box-shadow: 0 2px 0 rgb(84,98,105);
    box-shadow: 0 2px 0 rgb(84,98,105),
                0 0px 5px rgb(255,255,255),
                0 0px 20px rgb(255,255,255);
  }
  .visualizer-activate a.pause {
    display: none;
    opacity: .5;
    transition: opacity .1s ease-in-out;
    -webkit-transition: opacity .1s ease-in-out;
    line-height: 56px;
  }
  .visualizer-activate:hover a.pause {
    opacity: 1;
  }

</style>

<script type="text/javascript">
// <!--
window.Acko && Acko.queue(function () {
  if (window.gl) {
    var p = document.querySelector('.visualizer-activate');
    p.innerHTML = '<a href="#" class="play"><span class="icon-play"><span></span></span></a>'+
                  '<a href="#" class="pause"><span class="icon-pause"><span></span><span></span></span></a>';
    p.style.display = 'block';
    p.children[0].addEventListener('click', function (e) {
      e.preventDefault();
      gl.exports.visualizer.quietFrames = -420*60;
      gl.exports.visualizer.start();
      this.style.display = 'none';
      this.parentNode.children[1].style.display = 'block';
    });
    p.children[1].addEventListener('click', function (e) {
      e.preventDefault();
      gl.exports.visualizer.quietFrames = 0;
      gl.exports.visualizer.stop();
      this.style.display = 'none';
      this.parentNode.children[0].style.display = 'block';
    });
  }
});
// -->
</script>

<div class="g8 i2"><div class="pad">

<h2 class="sub">Fusing WebGL, CSS 3D and HTML</h2>

<p><em>Ladies and gentlemen, this is your captain speaking. Today's flight on WebGL Air will take us high above the cloud. Those of you sitting in Chrome class, on the desktop side of the plane, will have the clearest view. Internet Explorer class passengers may turn to their in-seat entertainment system instead. Passengers are reminded to put away their iPads and iPhones during take off and landing.</em></p>

<p><em>Edit: You can now also watch the <a href="http://www.youtube.com/watch?v=zjwA1VmuPnw">YouTube video</a> kindly provided by Leland Batey.</em></p>

<p>Acko.net, the domain, just turned 13, so it's time for a birthday present in the form of a complete front-end rewrite. The last design was entirely based on CSS 3D: it was a fun experiment, but created at a time when browser implementations were still wonky. It ultimately proved impractical: the DOM is not a good place to store complicated geometry. It's too bulky and there is a huge difference between a styled <code>&lt;div&gt;</code> and a shaded quad. After adding WebGL to the mix with MathBox and typesetting with MathJax, it turned into a catastrophic worst case for loading, and the smoothness was often lost even on fast computers.</p>

<p>Since then, we've seen a big push for rendering performance, both from the native and JS side. Hardware-accelerated DOM compositing is better understood, <code>requestAnimationFrame</code> is now common-place and reliable, and we have excellent profiling tools down to the frame level.</p>

<p>Hence the goal: to fuse 3D elements into the page like before, but with full 60 <span title="frames per second">fps</span> rendering. Plus to use WebGL instead of CSS 3D where possible, and be free of the constraints of the DOM.</p>

<p>Like <a href="http://www.voodoojs.com/">Voodoo.js</a> I use a fixed full-screen canvas and sync up scrolling with a 3D camera. The scene is mapped to CSS pixels and CSS perspective is locked to the camera. Once HTML, CSS 3D and WebGL are all in sync there's a truckload of <a href="http://acko.net/files/fullfrontal/fullfrontal/webglmath/online.html">linear algebra</a> and easing functions to keep you amused. The code is based on the platform I kludged together for the christmas demo, at times a mess of ad hoc demo formulas and spaghetti, though robust enough in the parts that count.</p>

</div></div>

<div class="c"></div>

<div class="wide mt2">
  <a target="_blank" href="http://daim.org/"><img src="https://acko.net/files/zerotosixty/daim.jpg" alt="Daim Graffiti" /></a>
</div>

<div class="g8 i2 mb1">
  <a target="_blank" class="credit" href="http://daim.org/">Daim</a>
</div>

<div class="g8 i2"><div class="pad">

<h2>Procedural Wildstyle</h2>

<p>The <em>so Designers Republic</em> kitsch of last time was starting to grate, and I wanted something more stylish. Taking inspiration from street art and graffiti, in particular long-time favorite <a href="http://daim.org/">Daim</a>, Acko's gone wild, though with a math twist. This design was built procedurally using some nifty vector calculus and more difference equations than you can shake a stick at.</p>

<p>At its heart it's really a game of Snake, albeit a complicated one. Starting from a line-based skeleton of the model, the curves are traced out, smoothed, oriented and finally extruded on the fly into ribbons. The final pose shows 261 lines mapped to 2,783 curve segments, tessellated into 43,168 triangles, though that amount is just a knob that can be tuned. In other words, it's a <em>scalable vector graphic</em>. No images were harmed in the making of this header, or the rest of the window dressing for that matter. These pixels are local, organic and bespoke, though I'm pretty sure they're not vegan.</p>

</div></div>

<div class="c"></div>

<div class="wide mt2 mb2 draggable">
  <img src="https://acko.net/files/zerotosixty/skeleton.png" alt="Mesh Skeleton" data-replace="gl.exports.tracks.exportTracks()" data-position="[0, -150, 150]" data-rotation="[.3, 0, 0]" />
</div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p>The ribbons need to animate, so I'm using a slightly exotic set up: a single Three.js <code>BufferGeometry</code> mesh is created with all the ribbons' topology in it. It only contains the connectivity of the triangles that span the vertices, stored as indices. The actual positions and normals are read from a texture that is updated on the fly by JavaScript using <code>gl.texSubImage2D</code>.</p>

</div></div>

<div class="c"></div>

<aside class="g6 mt1 tc pr"><div class="pad">
  <img src="https://acko.net/files/zerotosixty/geometrytexture.png" alt="Ribbon Geometry Texture" data-replace="gl.exports.tracks.exportTexture()" />
  <p>This is the actual geometry data for the header, stored in a 288×256 RGBA texture (floating point). The columns alternate between position and normal.</p>
  <p class="visualizer-activate"></p>
</div></aside>

<div class="g6"><div class="pad">

<p>As a ribbon follows its path, only its head and tail change, growing new segments or collapsing older ones. Each ribbon scrolls through the texture, which alters only a fraction of the total data per frame. Both the head and tail can add and remove segments at will, which allows me to vary the geometrical detail along each path. As long as the number of drawn segments at any given time never exceeds the texture height, a ribbon could in theory follow an infinitely long trail, wrapping around the edge endlessly. They're basically semi-virtualized meshes, allocated out of a fixed memory space. The arrows are an easy upgrade: I allocate a couple additional segments at the end and shape them to the right widths.</p>

<p>To draw a ribbon, I draw one contiguous subset of the total mesh (or two, if wrapping around the edge) which maps into a rectangular area of the texture. Ribbon color is stored statically as a vertex attribute, which means the entire hero piece can be drawn using a single shader, texture and vertex buffer.</p>

<p>For the background, I added a static <code>BufferGeometry</code> clocking in at 3,344 triangles. It consists of rounded boxes laid out in a randomized hyperboloid pattern. Aside from breaking the uniform whiteness, it's also quite the parallax enhancer down here.</p>

</div></div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<h2>DJ Ambient</h2>

<p>All of this is shaded with some custom GLSL. I use a basic Blinn-Phong lighting model tweaked for aesthetics, and there's some edge highlights and fog to top it off. But if I left it there, the result would still look pretty flat, without shadows. A common approach is to use shadow mapping, but that only works for point sources like the sun. The diffuse shadows that you see on an overcast day require an entirely different approach.</p>

</div></div>

<aside class="g3 i2 mt1 tc ol1"><div class="pad">
  <a href="http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter14.html" target="_blank"><img src="https://acko.net/files/zerotosixty/ao1.jpg" alt="Ambient Occlusion" /></a>
  <p>No shadows</p>
</div></aside>

<aside class="g3 mt1 tc ol1"><div class="pad">
  <a href="http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter14.html" target="_blank"><img src="https://acko.net/files/zerotosixty/ao2.jpg" alt="Ambient Occlusion" /></a>
  <p>Ambient occlusion</p>
</div></aside>

<aside class="g3 mt1 tc ol1"><div class="pad">
  <a href="http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter14.html" target="_blank"><img src="https://acko.net/files/zerotosixty/ao3.jpg" alt="Ambient Occlusion" /></a>
  <p>One indirect light bounce</p>
</div></aside>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p>What's needed is <a href="http://en.wikipedia.org/wiki/Ambient_occlusion" target="_blank">ambient occlusion</a>, a measure of how much skylight is obscured by the surrounding geometry at every point. The less sky you can see from a point, the darker it should be. This is slow to compute analytically for large scenes and hence is typically either faked or baked in. An easy hack is <a href="http://en.wikipedia.org/wiki/Screen_space_ambient_occlusion" target="_blank">Screen-Space AO</a>, which is really just <em>crease darkening</em> using the final image's depth buffer. It's clever but expensive, creating only local shadows. Quality can vary a lot between implementations, often creating distracting dark or light halos around silhouettes. They all share the same blind spot: only the currently visible pixels can cast any shadow. So while I added <a href="http://alteredqualia.com/three/examples/webgl_postprocessing_ssao.html" target="_blank">alteredq's SSAO shader</a>, it's disabled by default and only rendered at quarter resolution, upscaled with a bilateral filter to hide this fact.</p>

<aside class="tc mt2 mb2">
  <p><img src="https://acko.net/files/zerotosixty/ssao.jpg" alt="Screen-space Ambient Occlusion" /></p>
  <p class="mt0">Screen-space ambient occlusion (SSAO) in CryEngine 2</p>
</aside>

<p>Instead, I used a technique from NVidia's <em>GPU Gems 2</em>. It uses a <a href="http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter14.html">disc-based model</a> to calculate occlusion and even indirect lighting on the fly. Rather than go real-time on the GPU, I decided to only do it statically in JS, and just do plain occlusion without light bounces.</p>

<p>The discs for all the ribbons are generated ahead of time, and the disc-to-disc shadowing is done once for the final model in two passes. The first pass overestimates due to overlapping shadows and thus is too dark. The second pass uses the first to assign lesser weights to discs in shadow to compensate, then runs the algorithm anew.</p>

<p>There's another simplification: instead of small one-sided discs based on the real geometry, I use large two-sided discs and treat each ribbon as flat. Each disc occludes in both directions, but accumulates shadow on its front and back separately.  This gives a decent approximation of light around and across the entire object.</p>
  
<p>Below is the actual disc model. In this diagram, the two radii of each disc represent its front and back illumination, with smaller discs receiving more shadow. The crosshairs mark the real disc radius.</p>

</div></div>

<div class="c"></div>

<div class="wide mt2 mb2 draggable">
  <img src="https://acko.net/files/zerotosixty/discs.png" alt="Occlusion Model" data-replace="gl.exports.tracks.exportDiscs()" data-position="[0, -150, 150]" data-rotation="[.3, 0, 0]" />
</div>

<div class="c"></div>

<div class="g8 i2"><div class="pad">

<p>When streaming out new pieces of ribbon, the occlusion is baked in on the fly, stored in the unused alpha channel next to the position (RGB). The four nearest discs on the ribbon are interpolated bilinearly, and the vertex's normal and position is used to mix the front and back shadow values appropriately. Thus it's effectively trilinear filtering a 2×2×N voxel grid per ribbon, which snakes and twists along its length.</p>

<p>I'm very happy with how good it looks, even with very coarse divisions like this. Unlike SSAO it costs practically nothing once generated. If you wish, you can view the design <a href="http://acko.net/#white" target="_blank">entirely in white</a> to examine the lighting—or in <a href="http://acko.net/#sepia" target="_blank">black and white</a>, or in <a href="http://acko.net#70s" target="_blank">the seventies</a>, tone mapping is fun.</p>

<p>To ensure smooth rendering, the resolution is scaled down if it drops below 45 fps for several frames. This is essential on slower GPUs, but can sometimes over&shy;compensate, as WebGL is easily interrupted by other tasks on the machine. The browser doesn't tell you how much you're currently pushing it, so it's impossible to safely scale back up without causing more stutters. To mitigate this problem, I built in some strategic reset points, like when you focus the window or scroll to the top. In the end, 60fps happens more often than not, though it remains a goal rather than a guarantee. If you fullscreen your browser, you are now the bane of my existence, especially if you keep two dozen other windows open on the second monitor at the same time. On an underpowered retina MacBook.</p>

<p>Finally I also turned on <a href="http://mynameismjp.wordpress.com/2012/10/24/msaa-overview/" target="_blank">MultiSample Anti-Aliasing</a> to avoid the dreaded jaggies and make the vector style shine. It's substituted with <a href="http://en.wikipedia.org/wiki/Fast_approximate_anti-aliasing" target="_blank">Fast Approximate AA</a> if it's unavailable. FXAA is also used if SSAO is enabled, as you can't post-process multisampled images with WebGL.</p>

<p>Preparing all of this would take ~400ms on my 4 year old laptop, but most of that is spent on the occlusion which is static. Hence, I saved the lighting data into a JSON file for speedier loading, which cuts it down to ~100ms. The data gzips into 18KB, about the size of a small PNG, so no worries there. Add to that the time required to fetch and run the rest of the page, and we can call it an even second.</p>

<p>Of course, it only works on a subset of browsers and leaves the vast majority of mobile devices out in the cold. It does work in Chrome Beta on Android, though performance and stability is still pretty crap and more fixes are needed, both in the browser and in my own code. Rather than try and emulate some of the bling for CSS 3D-only environments, it's all or nothing. Without WebGL, you get plain images.</p>

<h2>Achievement Unlocked</h2>

<p>Reading all that, you might mistake the header as a mere love letter to hackery. But there's a twist, quite a literal one. The twisting of each ribbon is not generated arbitrarily, but mathematically derived. It embodies the differential principle of <a href="https://en.wikipedia.org/wiki/Parallel_transport" target="_blank">parallel transport</a>. The up direction changes parallel to each curve, which means the ribbons never rotate in place. They only turn when they naturally want to. Hence, the design kind of has a will of its own. I can set the initial up direction of a ribbon, but only affect it through its curved path. Arranging and tuning all the ribbons was a nice puzzle in itself, and it's a nice nod to the math that's all over the place, even if it is invisible.</p>

<p>As I started playing around with some screensaver-style shots, I realized it would make a pretty neat demo just by itself, so I built that in too, to the dulcet tones of <a href="http://www.selahsue.com/" target="_blank">Selah Sue</a>—whose last name I hope is not indicative. Here parallel transport would ensure a perfectly smooth ride, but that's not exciting, so there's some springy exponential easing with adaptive lookahead instead. That's on top of the secondary demo, which features an audio visualizer and the smooth drum and bass of <a href="http://www.secretoperations.com/" target="_blank">Seba</a>. There's maybe a third one too.</p>

<p>The songs are used here <em>entirely for educational purposes</em> of course. Not that it matters, since they're all on YouTube anyway. Click the Growl-like notifications to find out more about the artists. There's also a handful of achievements to be gathered, eight to be precise. Some of these are experiments that were turned off in the final version. Others are... trickier. Oh hey, did you notice the JS console?</p>

<p>By the way, special mention also goes to Time and how some browsers keep terrible track of it. Like when you're trying to sync a demo to a stuttery <code>&lt;audio&gt;</code> clock, Firefox ಠ_ಠ. There's also the matter of what to do when the user switches to another tab, and the answer is.... it'll desync, because I don't want to be sapping resources in a background tab. Minor constraints of putting on a live act, doors close after the show starts.</p>

<p class="tc mt2">
  <img src="https://acko.net/css/images/growl-large-01.png" class="inline flat" width="112" height="112" alt="Achievement Unlocked" />
  <img src="https://acko.net/css/images/growl-large-02.png" class="inline flat" width="112" height="112" alt="Information" />
  <img src="https://acko.net/css/images/growl-large-03.png" class="inline flat" width="112" height="112" alt="Music" />
</p>

<h2>Turn Right Past The Header</h2>

<p>The refresh-less navigation is also back from last time, slightly cleaned up. It still works on the same basic principle of fetching static, full HTML documents. However the transition mechanism now carefully choreographs the necessary DOM manipulation to avoid stutters: any image, iframe or video inserted in the content would mean future paints at an unknown time, so they're postponed until after the transition is complete. I did it the dirty but fast way, with string manipulation before it hits the DOM: such is the luxury of being the only one, person or machine, writing the markup.</p>

<p>Which leaves of course the content. But don't fix what ain't broke: distraction-free publishing is easy enough with Jekyll and a responsive 960 pixel grid with sideburns, so things still look and work mostly the same. I only added a home for my math talks and expanded the front page to highlight some demos and experiments. I aim to keep focusing on quality rather than quantity, and to remind visitors what the web looks like when you take away everything social media's done to it.</p>

<p>It's also fun to observe that after many years, the unified front-end convergence really has arrived. There is little difference between this site and the games that use HTML for their UI, such as the latest Sim City. For dealing with typography, illustration and UI, you want the comfort of DOM and CSS. For real-time graphical content, you'll want to draw it yourself, either in 2D, or 3D. Combine the two, and you get what game developers have been doing for years. Only this lacks all the sharp C bits, which game devs been replacing with Lua for years anyhow. That's Portuguese for "JavaScript" by the way.</p>

<p>So there you have it. A fresh look with a pile of juicy hax, and hopefully not too many bugs in the wild. Thanks go to all those who came before and provided all these toys for me to play with. You can too, for the source is <a href="https://github.com/unconed/fuse10/" target="_blank">entirely on Github</a>. Like I said, <em>educational</em>. Meanwhile I'll be over here, listening to the <a href="http://en.wikipedia.org/wiki/Wipeout_(video_game)" target="_blank">wipE'out"</a> <a href="http://www.coldstorage.org.uk/" target="_blank">soundtrack</a> on repeat in memory of the old one. Comments welcome on <a href="https://plus.google.com/112457107445031703644/posts/6xhyep1357J">Google Plus</a>.</p>

<p><em>PS: The previous version <a href="http://acko.net/files/slacko/" target="_blank">has been archived</a> with its fallbacks disabled so you can see the current state of CSS 3D in browsers. Still pretty broken. Still a great test case. So is the <a href="/blog/this-is-your-brain-on-css/">disembodied head</a>.</em></p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[My JS1K Demo - The Making Of]]></title>
    <link href="https://acko.net/blog/js1k-demo-the-making-of/"/>
    <updated>2010-08-06T00:00:00+02:00</updated>
    <id>https://acko.net/blog/js1k-demo-the-making-of</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<p>If you haven't seen it yet, check out the <a href="http://js1k.com">JS1K demo contest</a>. The goal is to do something neat in 1 kilobyte of JavaScript code.</p>
<p>I couldn't resist making one myself, so I pulled out my bag of tricks from my <a href="/design/avs">Winamp music visualization</a> days and started coding. I'm really happy with how it turned out. And no, it won't work in Internet Explorer 8 or less.</p>

<p><em>Edit: OH SNAP! I just rewrote the demo to include volumetric light beams and still fit in 1K:</em></p>

</div></div>

<div class="g6"><div class="pad">

<h3>Original Version</h3>
<p><iframe id="js1kjs" frameborder="0" style="background:#000;border:0" width="460" height="345" src="about:blank"></iframe></p>
<p><button style="display: none" id="js1kjs-stop" onclick="document.querySelector('#js1kjs').setAttribute('src','about:blank');this.style.display='none';document.querySelector('#js1kjs-start').style.display='inline-block';">Stop Demo</button><button id="js1kjs-start" onclick="document.querySelector('#js1kjs').setAttribute('src','/files/making-of-js1k/1022b.html');this.style.display='none';document.querySelector('#js1kjs-stop').style.display='inline-block';">Start Demo</button><button onclick="document.querySelector('#js1kjs').setAttribute('src','/files/making-of-js1k/1022s.html');document.querySelector('#js1kjs-start').style.display='inline-block';document.querySelector('#js1kjs-stop').style.display = 'none';">View Source</button></p>

</div></div>
<div class="g6"><div class="pad">

<h3>Improved Version</h3>

<p><iframe id="js1kjs2" frameborder="0" style="background:#000;border:0" width="460" height="345" src="about:blank"></iframe></p>
<p><button style="display: none" id="js1kjs2-stop" onclick="document.querySelector('#js1kjs2').setAttribute('src','about:blank');this.style.display = 'none';document.querySelector('#js1kjs2-start').style.display='inline-block';">Stop Demo</button><button id="js1kjs2-start" onclick="document.querySelector('#js1kjs2').setAttribute('src','/files/making-of-js1k/1024b.html');this.style.display='none';document.querySelector('#js1kjs2-stop').style.display='inline-block';">Start Demo</button><button onclick="document.querySelector('#js1kjs2').setAttribute('src','/files/making-of-js1k/1024s.html');document.querySelector('#js1kjs2-start').style.display='inline-block';document.querySelector('#js1kjs2-stop').style.display='none';">View Source</button></p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>Now, whenever size is an issue, the best way to make a small program is to generate all data on the fly, i.e. procedurally. This saves valuable storage space. While this might seem like a black art, often it just comes down to clever use of (high school) math. And as is often the case, the best tricks are also the simplest, as they use the least amount of code.</p>
<p>To illustrate this, I'm going to break down my demo and show you all the major pieces and shortcuts used. Unlike the actual 1K demo, the code snippets here will feature legible spacing and descriptive variable names.</p>

<h3>Initialization</h3>

<p>JS1K's rules give you a Canvas tag to work with, so the first piece of code initializes it and makes it fill the window.</p>
<p>From then on, it just renders frames of the demo. There are four major parts to this:</p>
<ul>
<li>Animating the wires</li>
<li>Rotating and projecting the wires into the camera view</li>
<li>Coloring the wires</li>
<li>Animating the camera</li>
</ul>
<p>All of this is done 30 times per second, using a normal <code>setInterval</code> timer:</p>
<p class="codeblock"><code>setInterval(function () { ... }, 33);</code></p>

<h3>Drawing Wires</h3>

<p>The most obvious trick is that everything in the demo is drawn using only a single primitive: a line segment of varying color and stroke width. This allows the whole drawing process to be streamlined into two tight, nested loops. Each inner iteration draws a new line segment from where the previous one ended, while the outer iteration loops over the different wires.</p>

<p><img class="natural" src="/files/making-of-js1k/drawing-process.png" alt="drawing process for demo" title="Drawing each wire in sequence." /></p>

<p>The lines are blended additively, using the built-in 'lighten' mode, which means they can be drawn in any order. This avoids having to manually sort them back-to-front.</p>
<p>To simplify the perspective transformations, I use a coordinate system that places the point (0, 0) in the center of the canvas and ranges from -1 to 1 in both coordinates. This is a compact and convenient way of dealing with varying window sizes, without using up a lot of code:</p>
<p class="codeblock"><code>with (graphics) {<br />&nbsp; ratio = width / height;<br />&nbsp; globalCompositeOperation = &#039;lighter&#039;;<br />&nbsp; scale(width / 2 / ratio, height / 2);<br />&nbsp; translate(ratio, 1);<br />&nbsp; lineWidthFactor = 45 / height;<br />&nbsp; ...</code></p>
<p>I add a correction <code>ratio</code> for non-square windows and calculate a reference line width <code>lineWidthFactor</code> for later. Here, I'm using the <code>with</code> construct to save valuable code space, though its use is generally discouraged. </p>
<p>Then there's the two nested <code>for</code> loops: one iterating over the wires, and one iterating over the individual points along each wire. In pseudo-code they look like:</p>
<p class="codeblock"><code>For (12 wires =&gt; wireIndex) {<br />&nbsp; Begin new wire<br />&nbsp; For (45 points along each wire =&gt; pointIndex) {<br />&nbsp;&nbsp;&nbsp; Calculate path of point on a sphere: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Extrude outwards in swooshes: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Translate and Rotate into camera view: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Project to 2D: (x,y)<br />&nbsp;&nbsp;&nbsp; Calculate color, width and luminance of this line: (r,g,b) (w,l)<br />&nbsp;&nbsp;&nbsp; If (this point is in front of the camera) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If (the last point was visible) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Draw line segment from last point to (x,y)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mark this point as invisible<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; Mark beginning of new line segment at (x,y)<br />&nbsp; }<br />}</code></p>

<h3>Mathbending</h3>

<p>To generate the wires, I start with a formula which generates a sinuous path on a sphere, using latitude/longitude. This controls the tip of each wire and looks like:</p>
<p class="codeblock"><code>offset = time - pointIndex * 0.03 - wireIndex * 3;<br />longitude = cos(offset + sin(offset * 0.31)) * 2<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + sin(offset * 0.83) * 3 + offset * 0.02;<br />latitude = sin(offset * 0.7) - cos(3 + offset * 0.23) * 3;</code></p>
<p>This is classic procedural coding at its best: take a time-based <code>offset</code> and plug it into a random mish-mash of easily available functions like cosine and sine. Tweak it until it 'does the right thing'. It's a very cheap way of creating interesting, organic looking patterns.</p>
<p>This is more art than science, and mostly just takes practice. Any time spent with a graphical calculator will definitely pay off, as you will know better which mathematical ingredients result in which shapes or patterns. Also, there are a couple of things you can do to maximize the appeal of these formulas.</p>
<p>First, always include some non-linear combinations of operators, e.g. nesting the sin() inside the cos() call. Combined, they are more interesting than when one is merely overlaid on the other. In this case, it turns regular oscillations into time-varying frequencies.</p>
<p>Second, always scale different wave periods using prime numbers. Because primes have no factors in common, this ensures that it takes a very long time before there is a perfect repetition of all the individual periods. Mathematically, the <a href="http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+31+83+70+23%29%29+%2F+100">least common multiple of the chosen periods</a> is huge (414253 units ~ 4.8 hours). Plotting the longitude/latitude for <code>offset = 0..600</code> you get:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-2.jpg" alt="a pseudo-random set of oscillations" title="Looks pretty random" /></p>
<p>The graph looks like a random tangled curve, with no apparent structure, which makes for motions that never seem to repeat. If however, you reduce each constant to only a single significant digit (e.g. 0.31 -&gt; 0.3, 0.83 -&gt; 0.8), then suddenly repetition becomes apparent:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-3.jpg" alt="a not so pseudo-random set of oscillations" title="Not so random" /></p>
<p>This is because the least common multiple has <a href="http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+30+80+70+20%29%29+%2F+100">dropped to 84 units ~ 3.5 seconds</a>. Note that both formulas have the same code complexity, but radically different results. This is why all procedural coding involves some degree of creative fine tuning.</p>
<h3>Extrusion</h3>

<p>Given the formula for the tip of each wire, I can generate the rest of the wire by sweeping its tail behind it, delayed in time. This is why <code>pointIndex</code> appears as a negative in the formula for <code>offset</code> above. At the same time, I move the points outwards to create long tails.</p>
<p>I also need to convert from lat/long to regular 3D XYZ, which is done using the <a href="http://en.wikipedia.org/wiki/Spherical_coordinate_system">spherical coordinate transform</a>:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-4.jpg" alt="spherical coordinates" /><center>Source: <a href="http://en.wikipedia.org/wiki/File:Coord_system_SE_0.svg">Wikipedia</a></center></p>
<p class="codeblock"><code>distance = f.sqrt(pointIndex+.2);<br />x = cos(longitude) * cos(latitude) * distance;<br />y = sin(longitude) * cos(latitude) * distance;<br />z = sin(latitude) * distance;</code></p>
<p>You might notice that rather than making <code>distance</code> a straight up function of the length <code>pointIndex</code> along the wire, I applied a square root. This is another one of those procedural tricks that seems arbitrary, but actually serves an important visual purpose. This is what the square root looks like (solid curve):</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-5.jpg" alt="square root" /></p>
<p>The dotted curve is the square root's derivative, i.e. it indicates the slope of the solid curve. Because the slope goes down with increasing distance, this trick has the effect of slowing down the outward motion of the wires the further they get. In practice, this means the wires are more tense in the middle, and more slack on the outside. It adds just enough faux-physics to make the effect visually appealing.</p>
<h3>Rotation and Projection</h3>

<p>Once I have absolute 3D coordinates for a point on a wire, I have to render it from the camera's point of view. This is done by moving the origin to the camera's position (X,Y,Z), and applying two rotations: one around the vertical (yaw) and one around the horizontal (pitch). It's like spinning on a wheely chair, while tilting your head up/down.</p>
<p class="codeblock"><code>x -= X; y -= Y; z -= Z;<br /><br />x2 = x * cos(yaw) + z * sin(yaw);<br />y2 = y;<br />z2 = z * cos(yaw) - x * sin(yaw);<br /><br />x3 = x2;<br />y3 = y2 * cos(pitch) + z2 * sin(pitch);<br />z3 = z2 * cos(pitch) - y2 * sin(pitch);</code></p>
<p>The camera-relative coordinates are projected in perspective by dividing by Z — the further away an object, the smaller it is. Lines with negative Z are behind the camera and shouldn't be drawn. The width of the line is also scaled proportional to distance, and the first line segment of each wire is drawn thicker, so it looks like a plug of some kind:</p>
<p class="codeblock"><code>plug = !pointIndex;<br />lineWidth = lineWidthFactor * (2 + plug) / z3;<br />x = x3 / z3;<br />y = y3 / z3;<br /><br />lineTo(x, y);<br />if (z3 &gt; 0.1) {<br />&nbsp; if (lastPointVisible) {<br />&nbsp;&nbsp;&nbsp; stroke();<br />&nbsp; }<br />&nbsp; else {<br />&nbsp;&nbsp;&nbsp; lastPointVisible = true;<br />&nbsp; }<br />}<br />else {<br />&nbsp; lastPointVisible = false;<br />}<br />beginPath();<br />moveTo(x, y);</code></p>
<p>There's a subtle optimization hiding in plain sight here. Because I'm drawing lines, I need both the previous and the current point's coordinates at each step. To avoid using variables for these, I place the <code>moveTo</code> command at the end of the loop rather than at the beginning. As a result, the previous coordinates are remembered transparently into the next iteration, and all I need to do is make sure the first call to <code>stroke()</code> doesn't happen.</p>

<h3>Coloring</h3>
<p>Each line segment also needs an appropriate coloring. Again, I used some trial and error to find a simple formula that works well. It uses a sine wave to rotate overall luminance in and out of the (Red, Green, Blue) channels in a deliberately skewed fashion, and shifts the R component slowly over time. This results in a nice varied palette that isn't overly saturated.</p>
<p class="codeblock"><code>pulse = max(0, sin(time * 6 - pointIndex / 8) - 0.95) * 70;<br />luminance = round(45 - pointIndex) * (1 + plug + pulse);<br />strokeStyle=&#039;rgb(&#039; + <br />&nbsp; round(luminance * (sin(plug + wireIndex + time * 0.15) + 1)) + &#039;,&#039; + <br />&nbsp; round(luminance * (plug + sin(wireIndex - 1) + 1)) + &#039;,&#039; +<br />&nbsp; round(luminance * (plug + sin(wireIndex - 1.3) + 1)) +<br />&nbsp; &#039;)&#039;;</code></p>
<p>Here, <code>pulse</code> causes bright pulses to run across the wires. I start with a regular sine wave over the length of the wire, but truncate off everything but the last 5% of each crest to turn it into a sparse pulse train:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-6.jpg" alt="sine pulse train" /></p>
<h3>Camera Motion</h3>

<p>With the main visual in place, almost all my code budget is gone, leaving very little room for the camera. I need a simple way to create consistent motion of the camera's X, Y and Z coordinates. So, I use a neat low-tech trick: repeated interpolation. It looks like this:</p>
<p class="codeblock"><code>sample += (target - sample) * fraction</code></p>
<p><code>target</code> is set to a random value. Then, every frame, <code>sample</code> is moved a certain fraction towards it (e.g. <code>0.1</code>). This turns <code>sample</code> into a smoothed version of <code>target</code>. Technically, this is a <em>one-pole low-pass filter</em>.</p>
<p>This works even better when you apply it twice in a row, with an intermediate value being interpolated as well:</p>
<p class="codeblock"><code>intermediate += (target - intermediate) * fraction<br />sample += (intermediate - sample) * fraction</code></p>
<p>A sample run with <code>target</code> being changed at random might look like this:</p>
<p><img class="natural" src="/files/making-of-js1k/js1k-7.jpg" alt="sine pulse train" /></p>
<p>You can see that with each interpolation pass, more discontinuities get filtered out. First, jumps are turned into kinks. Then, those are smoothed out into nice bumps.</p>
<p>In my demo, this principle is applied separately to the camera's X, Y and Z positions. Every ~2.5 seconds a new target position is chosen:</p>
<p class="codeblock"><code>if (frames++ &gt; 70) {<br />&nbsp; Xt = random() * 18 - 9;<br />&nbsp; Yt = random() * 18 - 9;<br />&nbsp; Zt = random() * 18 - 9;<br />&nbsp; frames = 0;<br />}<br /><br />function interpolate(a,b) {<br />&nbsp; return a + (b-a) * 0.04;<br />}<br /><br />Xi = interpolate(Xi, Xt);<br />Yi = interpolate(Yi, Yt);<br />Zi = interpolate(Zi, Zt);<br /><br />X&nbsp; = interpolate(X,&nbsp; Xi);<br />Y&nbsp; = interpolate(Y,&nbsp; Yi);<br />Z&nbsp; = interpolate(Z,&nbsp; Zi);</code></p>
<p>The resulting path is completely smooth and feels quite dynamic.</p>
<h3>Camera Rotation</h3>

<p>The final piece is orienting the camera properly. The simplest solution would be to point the camera straight at the center of the object, by calculating the appropriate <code>pitch</code> and <code>yaw</code> directly off the camera's position (X,Y,Z):</p>
<p class="codeblock"><code>yaw&nbsp;&nbsp; = atan2(Z, -X);<br />pitch = atan2(Y, sqrt(X * X + Z * Z));</code></p>
<p>However, this gives the demo a very static, artificial appearance. What's better is making the camera point in the right direction, but with just enough freedom to pan around a bit.</p>
<p>Unfortunately, the 1K limit is unforgiving, and I don't have any space to waste on more 'magic' formulas or interpolations. So instead, I cheat by replacing the formulas above with:</p>
<p class="codeblock"><code>yaw&nbsp;&nbsp; = atan2(Z, -X * 2);<br />pitch = atan2(Y * 2, sqrt(X * X + Z * Z));</code></p>
<p>By multiplying X and Y by 2 strategically, the formula is 'wrong', but the error is limited to about 45 degrees and varies smoothly. Essentially, I gave the camera a lazy eye, and got the perfect dynamic motion with only 4 bytes extra!</p>
<h3>Addendum</h3>

<p>After seeing the other demos in the contest, I wasn't so sure about my entry, so I started working on a version 2. The main difference is the addition of glowy light beams around the object.</p>
<p>As you might suspect, I'm cheating massively here: rather than do physically correct light scattering calculations, I'm just using a 2D effect. Thankfully it comes out looking great.</p>
<p>Essentially, I take the rendered image, and process it in a second Canvas that is hidden. This new image is then layered on the original.</p>
<p>I take the image and repeatedly blend it with a zoomed out copy of itself. With every pass the number of copies doubles, and the zoom factor is squared every time. After 3 passes, the image has been smeared out into an 8 = 2<sup>3</sup> 'tap' radial blur. I lock the zooming to the center of the 3D object. This makes the beams look like they're part of the 3D world rather than drawn on later.</p>
<p>For additional speed, the beam image is processed at half the resolution. As a side effect, the scaling down acts like a slight blur filter for the beams.</p>
<p>Unfortunately, this effect was not very compact, as it required a lot of drawing mode changes and context switches. I had no room for it  in the source code.</p>
<p>So, I had to squeeze out some more room in the original. First, I simplified the various formulas to the bare minimum required for interesting visuals. I replaced the camera code with a much simpler one, and started aggressively shaving off every single byte I could find. Then I got creative, and ended up recreating the secondary canvas every frame just to avoid switching back its state to the default.</p>
<p>Eventually, after a lot of bit twiddling, a version came out that was 1024 bytes long. I had to do a lot of unholy things to get it to fit, but I think the end result is worth it ;). </p>
<h3>Closing Thoughts</h3>

<p>I've long been a fan of the demo scene, and fondly remember <a href="http://www.youtube.com/watch?v=dQveVMQDJlg">Second Reality</a> in 1993 as my introduction to the genre. Since then, I've always looked at math as a tool to be mastered and wielded rather than subject matter to be absorbed.</p>
<p>With this blog post, I hope to inspire you to take the plunge and see where some simple formulas can take you.</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds 4 - The Devil's in the Details]]></title>
    <link href="https://acko.net/blog/making-worlds-4-the-devils-in-the-details/"/>
    <updated>2009-12-25T00:00:00+01:00</updated>
    <id>https://acko.net/blog/making-worlds-4-the-devils-in-the-details</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<aside class="r"><img class="natural" src="/files/making-worlds/planet-3-th.jpg" alt="" /></aside>

<p>Last time I'd reached a pretty neat milestone: being able to render a somewhat realistic rocky surface from space. The next step is to add more detail, so it still looks good up close.
</p>

<p>
Adding detail is, at its core, quite straightforward. I need to increase the resolution of the surface textures, and further subdivide the geometry. Unfortunately I can't just crank both up, because the resulting data is too big to fit in graphics memory. Getting around this will require several changes.
</p>

<h3>Strategy</h3>

<p>
Until now, the level-of-detail selection code has only been there to decide which portions of the planet should be <em>drawn</em> on screen. But the geometry and textures to choose from are all prepared up front, at various scales, before the first frame is started. The surface is generated as one high-res planet-wide map, using typical cube map rendering:
</p>

<p>
<img src="/files/making-worlds/cubemap-rendering.png" class="natural" alt="" title="Rendering an entire cube face at a time" />
</p>

<p>
This map is then divided into a quad-tree structure of surface tiles. It allows me to adaptively draw the surface at several pre-defined levels of detail, in chunks of various sizes.
<!--break-->
</p>

<p class="tc">
<img src="/files/making-worlds/planets-1-quadtree.png" class="natural" alt="Quadtree terrain" /><small><a href="http://tulrich.com/geekstuff/sig-notes.pdf">Source</a></small>
</p>

<p>
This strategy won't suffice, because each new level of detail doubles the work up-front, resulting in exponentially increasing time and memory cost. Instead, I need to write an adaptive system to generate and represent the surface on the fly. This process is driven by the Level-of-Detail algorithm deciding if it needs more detail in a certain area. Unlike before, it will no longer be able to make snap decisions and instant transitions between pre-loaded data: it will need to wait several frames before higher detail data is available.
</p>

<p>
<img src="/files/making-worlds/planet-lod-tree.png" class="natural" alt="Configuration of chunks to render" />
</p>

<p>
Uncontrolled growth of increasingly detailed tiles is not acceptable either: I only wish to maintain tiles useful for rendering views from the current camera position. So if a specific detailed portion of the planet is no longer being used—because the camera has moved away from it—it will be discarded to make room for other data.
</p>

<h2>Generating Individual Tiles</h2>

<p>
The first step is to be able to generate small portions of the surface on demand. Thankfully, I don't need to change all that much. Until now, I've been generating the cube map one cube face at a time, using a virtual camera at the middle of the cube. To generate only a portion of the surface, I have to narrow the virtual camera's viewing cone and skew it towards a specific point, like so:
</p>

<p>
<img src="/files/making-worlds/cubemap-rendering-subdivision.png" class="natural" alt="" title="Rendering one face tile at a time" />
</p>

<p>
This is easy using a mathematical trick called <a href="http://en.wikipedia.org/wiki/Homogeneous_coordinates">homogeneous coordinates</a>, which are commonly used in 3D engines. This turns 2D and 3D vectors into respectively 3D and 4D. Through this dimensional redundancy, we can then represent most geometrical transforms as a 4x4 matrix multiplication. This covers all transforms that translate, scale, rotate, shear and project, in any combination. The right sequence (i.e. multiplication) of transforms will map regular 3D space onto the skewed camera viewing cone.
</p>

<p>
Given the usual centered-axis projection matrix, the off-axis projection matrix is found by multiplying with a scale and translate matrix in so-called "screen space", i.e. at the very end. The thing with homogeneous coordinates is that it seems like absolute crazy talk until you get it. I can only recommend you read a <a href="http://www.cim.mcgill.ca/~langer/558/lecture3.pdf">good introduction to the concept</a>.
</p>

<p>
With this in place, I can generate a zoomed height map tile anywhere on the surface. As long as the underlying brushes are detailed enough, I get arbitrarily detailed height textures for the surface. The normal map requires a bit more work however. 
</p>

<h3>Normals and Edges</h3>

<p>
As I described in <a href="http://acko.net/blog/making-worlds-3-thats-no-moon">my last entry</a>, normals are generated by comparing neighbouring samples in the height map. At the edges of the height map texture, there are no neighbouring samples to use. This wasn't an issue before, because the height map was a seamless planet-wide cube map, and samples were fetched automatically from adjacent cube faces. In an adaptive system however, the map resolution varies across the surface, and there's no guarantee that those neighbouring tiles will be available at the desired resolution.
</p>

<p>
The easy way out is to make sure the process of generating any single tile is entirely self-sufficient. To do this, I expand each tile with a 1 pixel border when generating it. Each such tile is a perfectly dilated version of its footprint and overlaps with its neighbours in the border area:
</p>

<p>
<img src="/files/making-worlds/cubemap-rendering-subdivision-dilated.png" class="natural" alt="" title="Rendering one face tile at a time" />
</p>

<p>
This way all the pixels in the undilated area have easily accessible neighbour pixels to sample from. This border is only used during tile generation, and cropped out at the end. Luckily I did something similar when I <a href="http://acko.net/blog/making-worlds-2-scaling-heights">played with dilated cube maps</a> before, so I already had the technique down. When done correctly, the tiles match up seamlessly without any additional correction.
</p>

<h3>Adaptive Tree</h3>

<p>
Now I need to change the data structure holding the mesh. To make it adaptive, I've rewritten it in terms of real-time 'split' and 'merge' operations.
</p>

<p>
Just like before, the Level-of-Detail algorithm traverses the tree to determine which tiles to render. But if the detail available is not sufficient, the algorithm can decide that a certain tile in the tree needs a more detailed surface texture, or that its geometry should be split up further. Starting with only a single root tile for each cube face, the algorithm divides up the planet surface recursively, quickly converging to a stable configuration around the camera.
</p>

<p>
As the camera moves around, new tiles are generated, increasing memory usage. To counter this steady stream of new data, the code identifies tiles that fall into disuse and merges them back into their parent. The overall effect is that the tree grows and shrinks depending on the camera position and angle.
</p>

<h3>Queuing and scheduling</h3>

<p>
To do all this real-time, I need to queue up the various operations that modify the tree, such as 'split', 'merge' and 'generate new tile'. They need to be executed in between rendering regular frames on screen. Whenever the renderer decides a certain tile is not detailed enough, a request is placed in a job queue to address this.
</p>

<p>
While continuing to render regular frames, these requests need to be processed. This is harder than it sounds, because both planet rendering and planet generation have to share the GPU, preferably without causing major stutters in rendering speed.
</p>

<p>
The solution is to spread this process over enough visible frames so that the overal rendering speed is not significantly affected. For example, if a new surface texture is requested, several passes are made. First the height map is rendered, the next frame the normal map is derived from it, then the height/normal maps are analyzed and put into the tree, after which they will finally appear on screen:
</p>

<p>
<img src="/files/making-worlds/queued-frame-pipeline.png" class="natural" alt="" title="Rendering one frame" />
</p>

<p>
I took some inspiration from <a href="http://s09.idav.ucdavis.edu/talks/05-JP_id_Tech_5_Challenges.pdf">id Tech 5</a>, the next engine coming from technology powerhouse id Software. They describe a queued job system that covers any frame-to-frame computation in a game engine (from texture management to collision detection), and which schedules tasks intelligently.
</p>

<h2>Do the Google Earth</h2>

<p>
With all the above in place, the engine can now progressively increase the detail of the planet across several orders of magnitude. Here's a video that highlights it:
</p>

<p>
  <iframe style="margin: 0 auto;" width="560" height="380" src="https://www.youtube.com/embed/ck27Xu5XAJE" frameborder="0" allowfullscreen="allowfullscreen"></iframe>
</p>

<p>
And some shots that show off the detail:
</p>

</div></div>

<div class="g12"><div class="pad">

<p class="flat">
<img src="/files/making-worlds/planet-1.jpg" class="natural" alt="" />
</p>

<p class="flat">
<img src="/files/making-worlds/planet-2.jpg" class="natural" alt="" />
</p>

<p class="flat">
<img src="/files/making-worlds/planet-3.jpg" class="natural" alt="" />
</p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>
W00t, certainly one of the niftiest things I've built.
</p>

<h3>Engine tweaks</h3>

<p>
Along with the architecture changes, I implemented some engine tweaks, noted here for completeness.
</p>

<p>
In previous comments, <a href="/blog/making-worlds-part-1-of-spheres-and-cubes"> Erlend suggested</a> using displacement mapping, so I gave it a shot. Before, the mesh for every tile was calculated on the CPU once, then copied into GPU memory. However, this mesh data was redundant, because it was derived literally from the height map data. Instead I changed it so that now, the transformation of mesh points onto the sphere surface happens real-time on the GPU in a per-vertex program.
</p>

<p>
This saves memory and pre-calculation time, but increases the rendering load. I'll have to see whether this technique is sustainable, but overall, it seems to be performing just fine. As a side effect, the terrain height map can be changed real-time with very low cost.
</p>

<h3>Technical hurdles</h3>

<p>
I spent some time tweaking the engine to run faster, but there's still plenty of work and some technical hurdles to cover.
</p>

<p>
One involves the Ogre Scene Manager, which is the code object that manages the location of objects in space. In my case, I have to deal with both the 'real world' in space as well as the 'virtual world' of brushes that generate the planet's surface. I chose to use two independent scene managers to represent this, as it seemed like a natural choice. However, it turns out this is <a href="http://www.ogre3d.org/mantis/view.php?id=130">unsupported</a> by Ogre and causes <a href="http://www.ogre3d.org/forums/viewtopic.php?p=189032">random crashes and edge cases</a>. Argh. It looks like I'll have to refactor my code to fix this.
</p>

<p>
Another major hurdle involves the planet surface itself. Currently I'm still just using a single distored-crater-brush to create it, and the lack of variation is showing.
</p>

<p>
Finally, surfaces are being generated using 16-bit floating point height values, and their accuracy is not sufficient beyond a couple levels of zooming. This results in ugly bands of flat terrain. To fix this I'll need to increase the surface accuracy.
</p>

<h2>Future steps</h2>

<p>
With the basic planet surface covered, I can now start looking at color, atmosphere and clouds. I have plenty of reading and experimentation to do. Thankfully the web is keeping me supplied with a steady stream of awesome papers... nVidia's GPU Gems series has proven to be a gold mine, for example.
</p>

<p>
Random factoid: what game developers call a "cube map", cartographers call a "cubic gnomonic grid". It turns out that knowing the right terminology is important when you're looking for reference material...
</p>

<h3>Code</h3>

<p>
The code is <a href="https://github.com/unconed/NFSpace">available on GitHub</a>.
</p>

<h3>References</h3>

<p>
Great ideas are best discovered when standing on the shoulders of giants:
</p>

<ul>
<li><a href="http://s09.idav.ucdavis.edu/talks/05-JP_id_Tech_5_Challenges.pdf">id Tech 5 Challenges, From Texture Virtualization to Massive Parallelization</a>, J.M.P. van Waveren, id Software</li>
<li><a href="http://developer.nvidia.com/object/gpu_gems_home.html">GPU Gems</a>, nVidia</li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds - Intermission]]></title>
    <link href="https://acko.net/blog/making-worlds-intermission/"/>
    <updated>2009-11-07T00:00:00+01:00</updated>
    <id>https://acko.net/blog/making-worlds-intermission</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><p>Today at <a href="http://bazcampyvr.pbworks.com">BazCamp YVR</a> I gave a short presentation and demo of my "Making Worlds" project, as well as an overview of procedural content generation in general.
</p>

<p>
The <a href="/files/making-worlds/Making-Worlds-BazCampYVR.pdf">slides are available for download</a>.
</p>

<p>
<img class="natural" src="/files/making-worlds/lightcycles.jpg" alt="Tron" />
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds 2 - Scaling Heights]]></title>
    <link href="https://acko.net/blog/making-worlds-2-scaling-heights/"/>
    <updated>2009-08-31T00:00:00+02:00</updated>
    <id>https://acko.net/blog/making-worlds-2-scaling-heights</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<p>Last time, I had a working, smooth sphere mesh. The next step is to create terrain.
</p>

<h2>Scale</h2>

<p>
Though my goal is to render at a huge range of scales, I'm going to focus on views from space first. That strongly limits how much detail I need to store and render. Aside from being a good initial sandbox in terms of content generation, it also means I can comfortably keep using my current code, which doesn't do any sophisticated memory or resource management yet. I'd much rather work on getting something interesting up first rather than work on invisible infrastructure.
</p>

<p>
That said, this is not necessarily a limitation. The interesting thing about procedural content is that every generator you build can be combined with many others, including a copy of itself. In the case of terrain, there are definite fractal properties, like self-similarity at different levels of scale. This means that once I've generated the lowest resolution terrain, I can generate smaller scale variations and combine them with the larger levels for more detail. This can be repeated indefinitely and is only limited by the amount of memory available.
</p>

<p class="tc"><img class="natural" src="/files/making-worlds/planet-2-perlin-noise.png" alt="Example of Perlin Noise" />
<a href="http://en.wikipedia.org/wiki/Perlin_noise">Perlin Noise</a> is a celebrated classic procedural algorithm,<br />
often used as a fractal generator.
</p>

<h2>Height</h2>
<p>
To build terrain, I need to create heightmaps for all 6 cube faces. Shamelessly stealing more ideas from Spore, I'm doing this on the GPU instead of the CPU, for speed. The GPU normally processes colored pixels, but there's no reason why you can't bind a heightmap's contents as a grayscale (one channel) image and 'draw' into it. As long I build my terrain using simple, repeated drawing operations, this will run incredibly fast.
</p>

<p>
In this case, I'm stamping various brushes onto the sphere's surface to create bumps and pits. Each brush is a regular PNG image which is projected onto the surface around a particular point. The luminance of the brush's pixels determines whether to raise or lower terrain and by how much.
</p>

<p class="tc">
<img class="natural" src="/files/making-worlds/planet-2-spore-brushes.png" alt="Brushes from Spore" /><br />
Three example brushes from Spore. (<a href="http://www.cs.cmu.edu/~ajw/s2007/0251-SphericalWorlds.pdf">source</a>)
</p>

<p>
However, while the brushes need to appear seamless on the final sphere, the drawing area consists only of the straight, square set of cube map faces. It might seem tricky to make this work so that the terrain appears undistorted on the curved sphere grid, but in fact, this distortion is neatly compensated for by good old perspective. All I need to do is set up a virtual scene in 3D, where the brushes are actual shapes hovering around the origin and facing the center. Then, I place a camera in the middle and take a snapshot both ways along each of the main X, Y and Z directions with a perfect 90 degree field of view. The resulting 6 images can then be tiled to form a distortion-free cube map.
</p>

<p>
<img class="natural" src="/files/making-worlds/planet-2-cubemap-rendering.png" alt="Rendering a cubemap" />
Rendering two different cube map faces. The red area is the camera's viewing cone/pyramid, which extends out to infinity.
</p>

<p>
To get started I built a very simple prototype, using Ogre's scene manager facilities. I'm starting with just a simple, smooth crater/dent brush. I generate all 6 faces in sequence on the GPU, pull the images back to the CPU to create the actual mesh, and push the resulting chunks of geometry into GPU memory. This is only done once at the beginning, although the possibility is there to implement live updates as well.
</p>

<p>
Here's a demo showing a planet and the brushes that created it, hovering over the surface. I haven't implemented any shading yet, so I have to toggle back and forth to wireframe mode so you can see the dents made by the brushes:
</p>

<p>
  <iframe style="margin: 0 auto;" width="560" height="380" src="https://www.youtube.com/embed/AX_LiBnZJTc" frameborder="0" allowfullscreen="allowfullscreen"></iframe>
</p>

<p>
The cubemap for this 'planet' looks like this when flattened. You can see that I haven't actually implemented real terrain carving, because brushes cause sharp edges when they overlap:
</p>

<p>
<img class="natural" src="/files/making-worlds/planet-2-cubemap.png" alt="Generated planet cubemap" />
</p>

<p>
The narrow dent on the left gets distorted and angular where it crosses the cube edge. This is a normal consequence of the cubemapping, as it looks perfectly normal when mapped onto the sphere in the video.
</p>

<h2>Engine Tweaks</h2>

<p>
The demo above also incorporates a couple of engine improvements. With a real heightmap in place, I can implement real level-of-detail selection. That means the resolution of any terrain tile is decided based on how much detail would be lost if a simpler tile was used. The flatter a tile, the less detail is necessary. This ensures complex geometry is used only on those sections that really need it. This is great for visual fidelity, but causes a lot of geometry to pop up if sharp ridges are present in the terrain. In this case, my rendering engine was happily trying to push 700k triangles through the GPU per frame. While even my laptop GPU can actually do that at pretty smooth frame rates nowadays, some optimizations are in order to give me some breathing room.
</p>

<p>
The culprit was that I wasn't really doing any early removal of geometry that was hidden or otherwise out of frame. To fix that, I now do visibility checks together with the level-of-detail selection. First it checks if a chunk is over the horizon or not before considering it for selection. This is easy to calculate and eliminates a lot of unnecessary drawing, especially when looking straight down. If that first visibility check passes, I perform a tighter check using the camera's viewing cone. With these two measures in place, I'm only averaging about 50,000-100,000 triangles visible per frame, with room for more optimization. These optimizations only remove geometry that's already off screen, so there is no visual difference.
</p>

<h3>Cubemap Seams and Dilation</h3>

<p>
When rendering into cube maps, each side is rendered independently. In theory each face should match perfectly with adjacent ones due to the way they've been created. In practice however, slight mismatches can occur due to rounding errors at the edges, creating seams. This can be fixed by explicitly copying one pixel-wide edges from one face into the adjacent ones, until they all match up.
</p>

<p>
The next big step is to start shading the surface, but in order to do that I need to be able to run filters on the cube map. Specifically, I need to be able to compare neighbouring height samples anywhere on the surface. In the straight forward cubemap scenario this is non-trivial, because neighbouring samples at the edges need to be fetched from different cube faces at different orientations in space.
</p>

<p>
I decided to implement something I call 'dilated cubemaps'. I've never really heard this described formally, though I doubt it's never been thought of before:
</p>

<p>
<img class="natural" src="/files/making-worlds/planet-2-dilated-cubemap.png" alt="A dilated cubemap" />
</p>

<p>
Instead of every face neatly matching with the next, I dilate the cube faces so they stick through eachother. At the same time, I use a larger texture size to compensate, and I adjust the field-of-view of the rendering camera to match. If done right, the resulting cubemap is a pixel-perfect expanded version of the undilated map.
</p>

<p>
The dilated cubemap provides reliable neighbouring samples for all samples in the original cube map up to a distance as wide as the new border. Unlike regular cubemap wrapping, the dilated regions are distorted to conform to the current face's grid. This matches the real change in grid direction that occurs on the final sphere mesh and lets you sample exactly across cube map edges.
</p>

<p>
I played with the cubemap dilation because I was thinking of some complicated filters to run that require regular grids (like CFD). But in retrospect, I probably don't need the exact spacing of sample points at the edges for this, so regular undilated cubemapping will probably do. Still, it's good to have around, and certainly was an interesting exercise in pixel-exact rendering.
</p>

<h2>What Next?</h2>

<p>
With basic heightmap generation in place, I can now start putting in some 'tech artist' time to play with various brushes and drawing behaviour. Lighting and shading is another big one and should provide a massive improvement to the visuals.
</p>

<p>
Right now I've taken a week between postings, though it remains to be seen whether I can maintain that. Creating these blog entries is turning into a pretty time consuming endeavour, especially as I get into territory where I have to make my own diagrams and illustrations.
</p>

<h3>References</h3>

<p>
The techniques I used were pioneered by people smarter and older than me, I'm just building my own little digital machine with them.
</p>

<ul>
<li><a href="http://www.cs.cmu.edu/~ajw/s2007/0251-SphericalWorlds.pdf">Creating Spherical Worlds</a>, Maxis/Electronic Arts. (<a href="http://www.andrewwillmott.com/s2007">source</a>).</li>
<li><a href="http://en.wikipedia.org/wiki/Ken_Perlin">Ken Perlin</a>, who invented a lot of this stuff.</li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds 1 - Of Spheres and Cubes]]></title>
    <link href="https://acko.net/blog/making-worlds-1-of-spheres-and-cubes/"/>
    <updated>2009-08-23T00:00:00+02:00</updated>
    <id>https://acko.net/blog/making-worlds-1-of-spheres-and-cubes</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<p>Let's start making some planets! Now, while this started as a random idea kind of project, it was clear from the start that I'd actually need to do a lot of homework for this. Before I could get anywhere, I needed to define exactly what I was aiming for.
</p>

<p>
The first step in this was to shop around for some inspirational art and reference pictures. While there is plenty space art to be found online, in this case, nothing can substitute for the real thing. So I focused my search on real pictures, both of landscapes (terran or otherwise) as well as from space. I found classy shots like these:
</p>

</div></div>

<div class="g3 m1"><div class="pad">
  
  <p>
    <a href="http://en.wikipedia.org/wiki/Enceladus_(moon)"><img class="inline" src="/files/making-worlds/planets-1-enceladus.jpg" alt="The moon Enceladus" /></a>
  </p>

</div></div>

<div class="g6"><div class="pad">

<p class="flat">
  <img src="/files/making-worlds/planets-1-landscape.jpg" alt="Landscape" />
</p>

</div></div>

<div class="g3 m1"><div class="pad">
  
  <p>
    <a href="http://en.wikipedia.org/wiki/Mars"><img class="inline" src="/files/making-worlds/planets-1-mars.png" alt="Mars" /></a>
  </p>

</div></div>

<div class="g8 i2"><div class="pad">
<p>
Hopefully I'll be able to render something similar in a while. At the same time, I eagerly devoured any paper I could find on rendering techniques from the past decade, some intended for real-time rendering, some old enough to be real-time today.
<!--break-->
Out of all this, I quickly settled on my goals:
<ul>
  <li>Represent spherical or lumpy heavenly bodies from asteroids to suns.</li>
  <li>With realistic looking topography and features.</li>
  <li>Viewable across all scales from surface to space.</li>
  <li>At flight-simulator levels of detail.</li>
  <li>Rendered with convincing atmosphere, water, clouds, haze.</li>
</ul>
</p>

<p>
For most of these points, I found one or more papers describing a useful technique I could use or adapt. At the same time, there are still plenty of unknowns I'll need to figure out along the way, not to mention significant amounts of fudging and experimentation.
</p>

<h3>The Spherical Grid</h3>

<p>
To get started I needed to build some geometry, and to do that I needed to figure out what geometry I should use. After reviewing some options, I quickly settled on a regular spherical displacement map (AKA a heightmap). That is, starting with a smooth sphere, move every surface point up or down, perpendicular to the surface, to create terrain on the surface.
</p>

<p>
If these vertical displacements are very small compared to the sphere radius, this can represent the surface of a typical planet (like Earth) at the levels of detail I'm looking for. If the displacements are of the same order as the sphere radius, you can deform it into very irregular potato-like shapes. The only thing heightmaps can't do is caves, tunnels, overhang and other kinds of holes, which is fine for now.
</p>

<p>
The big question is, how should the spherical surface be divided up and represented? With a sphere, this is not an easy question, because there is no single obvious way to divide a spherical surface into regular sections or grids. Various techniques exist, each with their own benefits and specific use cases, and I spent quite some time looking into them. Here's a comparison between four different tesselations:
</p>

<p class="tr">
<img class="natural" src="/files/making-worlds/planets-1-sphere-tesselations.png" alt="Different tesselations of a sphere" /><small><a href="http://adsabs.harvard.edu/cgi-bin/nph-bib_query?bibcode=2005ApJ...622..759G">Source</a></small>
</p>

<p>
Note that the tesselation labeled ECP is just the regular geographic latitude-longitude grid.
</p>

<p>
The main features I was looking for were speed and simplicity, so I settled on the 'quadcube'. This is where you start with a cube whose faces have been divided into regular grids, and project every surface point out from the middle to an enclosing sphere. This results in a perfectly smooth sphere, built out of 6 identical round shells with curved edges. This arrangement is better known as the 'cube map' and often used for storing arbitrary 360 degree panorama views.
</p>

<p>
Here's a cube and its spherical projection:
</p>

<p>
<img class="natural" src="/files/making-worlds/planets-1-cubemap.png" alt="Mapping a cube to a sphere" />
<small>The projected cube edges are indicated in red. Note that the resulting sphere is perfectly smooth and round, even though the grid has a bulgy appearance.</small>
</p>

<p>
Cube maps are great, because they are very easy to calculate and do not require complicated trigonometry. In reverse, mapping arbitrary spherical points back onto the cube is even simpler and in fact natively supported by GPUs as a texture mapping feature.
</p>

<p>
This is important, because I'll be generating the surface terrain and texture dynamically and will need to index and access each surface 'pixel' efficiently. Using a cube map, I simply identify the corresponding face, and then index it using x/y coordinates on the face's grid.
</p>

<p>
The downside of cube maps is that the distance and area between points varies along the grid, which makes it harder to perform certain operations on a surface equally. However, these area distortions are much smaller than e.g. a lat-long grid, where the grid spacing actually approaches zero near the poles. Even more, the distortions made by a cube map are the exact opposite of those you get with a regular perspective projection. This makes it easy to render into cube maps, which will be useful for texture generation.
</p>

<h3>Level of Detail</h3>

<p>
There's another reason I picked the cube map approach, and that has to do with the level of detail requirements. My goal is to make a planet that can be viewed from the ground, the air as well as from space. It would be incredibly slow to always render everything at maximum detail, so I need to adaptively add and remove detail as the viewer gets closer to the surface.
</p>

<p>
However, increasing the level of detail uniformly across the entire sphere is not enough, because I only want to render detail where the viewer will see it. To a viewer on the ground, most of the planet is hidden by the horizon, and the engine should be able to effectively cut away the unseen pieces, so no wasteful processing takes place.
</p>

<p>
It is here that I get a huge benefit from the cube map layout of the sphere, because it lets me apply the well-researched realm of grid-based flat terrain rendering with only minor adjustments. Specifically, I am using a 'chunked LOD' approach. Every face of the cube map becomes a quadtree, with each level splitting four ways to form the next level with more detail:
</p>

<p class="tr">
<img class="natural" src="/files/making-worlds/planets-1-quadtree.png" alt="Quadtree terrain" /><small><a href="http://tulrich.com/geekstuff/sig-notes.pdf">Source</a></small>
</p>

<p>
The chunks for the various levels of detail are all loaded into GPU memory, ready to be accessed at any time. When the terrain has to be rendered, the engine walks down the quad-tree, determines the appropriate level-of-detail for each section, and outputs the list of chunks to be rendered for a particular frame. Then, the GPU does its work, blasting through each chunk at a blistering pace, leaving the CPU to do other things.
</p>

<p>
<img class="natural" src="/files/making-worlds/planet-lod-tree.png" alt="Configuration of chunks to render" />
</p>

<p>
Because all the data is already in memory, changing the level of detail just means rendering a different set of chunks. Each chunk has the same geometrical complexity, and performance is directly proportional to how many are rendered on screen. More detail means more chunks, but that usually also means you can cut away pieces of the terrain that are far away.
</p>

<p>
The chunked approach is also very easy to work with, because there is no data dependency between the different chunks. Each chunk has a copy of its own vertex data, which means individual chunks can be paged in and out of GPU memory at will. This is important for keeping memory usage down while still being able to scale to massive sizes.
</p>

<h3>Putting It All Together</h3>

<p>
At this point, I have all the pieces in place to render an adaptive sphere mesh. This is what it looks like (sorry, the video capture is a bit jerky):
</p>

<p>
  
  <iframe style="margin: 0 auto;" width="560" height="380" src="https://www.youtube.com/embed/LxZhWrSmrOY" frameborder="0" allowfullscreen="allowfullscreen"></iframe>

</p>

<p>
The detail increases as the camera gets closer to the sphere and shifts around the surface as it moves.
</p>

<p>
Far from being a little coding experiment, it actually took me quite some time to get to this point, because I was learning OGRE, sharpening my C++ skills, as well as researching the techniques to use.
</p>

<p>
The next step is to look at generating heightmaps and textures for the surface.
</p>

<h3>References</h3>

<p>
The techniques I used were pioneered by people smarter and older than me, I'm just building my own little digital machine with them.
</p>

<ul>
<li><a href="http://www.cs.cmu.edu/~ajw/s2007/0251-SphericalWorlds.pdf">Creating Spherical Worlds</a>, Maxis/Electronic Arts. (<a href="http://www.andrewwillmott.com/s2007">source</a>).</li>
<li><a href="http://tulrich.com/geekstuff/sig-notes.pdf">Rendering Massive Terrains using Chunked Level of Detail Control</a>, Thatcher Ulrich. (<a href="http://tulrich.com/geekstuff/chunklod.html">source</a>)</li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds: Introduction]]></title>
    <link href="https://acko.net/blog/making-worlds-introduction/"/>
    <updated>2009-08-22T00:00:00+02:00</updated>
    <id>https://acko.net/blog/making-worlds-introduction</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<p>For the past year or so I've been reacquainting myself with an old friend: C++.
</p>

<p>
More specifically, I've been exploring graphics programming again, this time with the luxurious flexibility of the modern GPU at my fingertips. To get me started, I shopped around for an open source engine to play with. After trying Irrlicht and finding its promises to be a bit lacking, <a href="http://www.ogre3d.org">Ogre</a> turned out to be a really good choice. Though its architecture is a bit intimidating at first, it is all the more sound. More importantly, it seems to have a relatively healthy open-source community around it.
</p>

<p>
So with Ogre as my weapon of choice, I've started a new project: Making Worlds. More specifically, I want to procedurally generate a 3D planet, viewable from outer space as well as the ground (at flight-sim levels of detail), which can be rendered real-time on recent graphics hardware.
</p>

<p>
Why? Because I really like procedural content generation. It's an odd discipline where anything goes, and techniques from across mathematics, engineering and physics are applied. Then, you add a good dose of creativity and artistic sense, and perhaps mix in some real-world data too, until you find something that looks right.
</p>

<p>
Plus, far from being an exercise in pointlessness, procedural content is <a href="http://www.escapistmagazine.com/articles/view/columns/experienced-points/6418-The-Future-is-Procedural">gaining in popularity</a>, especially for video games.
</p>

<p>
So, in the style of Shamus Young's excellent <a href="http://www.shamusyoung.com/twentysidedtale/?p=2940">Procedural city</a> series, I'm going to start blogging about Making Planets. Unlike him however, I'm not going to adhere to a strict schedule.
</p>

<p>
<img class="natural" src="/files/making-worlds/geosphere.png" alt="Geosphere" />
</p>

<p>
Here's a teaser for the first installment.
</p>

</div></div>
]]></content>
  </entry>
  
</feed>
