<?xml version="1.0"?>
<rss version="2.0"><channel><title>Josh's Dev Blog</title><link>https://www.leadwerks.com/community/blogs/blog/1-development-blog/</link><description><![CDATA[
<p>
	Learn about game development technology
</p>
]]></description><language>en</language><item><title>Fix Your Alpha-masked Vegetation Textures</title><link>https://www.leadwerks.com/community/blogs/entry/2898-fix-your-alpha-masked-vegetation-textures/</link><description><![CDATA[<p>
	This is a very common mistake I see in 3D models. Probably the majority of the time, artists will use either black or white for the background in a masked vegetation texture.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="23372" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2026_05/before.jpg.78e0f5f7e8558eb090cf02087fcc4b29.jpg" rel=""><img alt="before.thumb.jpg.2ef969c00a7d404dcdc9dd6c17f22833.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="23372" data-ratio="100.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2026_05/before.thumb.jpg.2ef969c00a7d404dcdc9dd6c17f22833.jpg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	This often goes unnoticed because support for viewing alpha channels is almost universally bad in all paint programs and image viewers. Here is what the image looks like with the alpha channel active:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="23373" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2026_05/MI_YMC_Grass_01_BaseColor_fixed.png.b35f1c0bacd4406f56684a1a57fc3df0.png" rel=""><img alt="MI_YMC_Grass_01_BaseColor_fixed.thumb.png.8770ceb6ec0b9f8aabf6a8587c6b42da.png" class="ipsImage ipsImage_thumbnailed" data-fileid="23373" data-ratio="100.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2026_05/MI_YMC_Grass_01_BaseColor_fixed.thumb.png.8770ceb6ec0b9f8aabf6a8587c6b42da.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Although Leadwerks is smart enough to weight pixels by their alpha value when creating the downsampled mipmap chain, the linear filter the GPU uses will still show the background color on the edge of the solid area. This results in a strange darkening or lightening of your vegetation. If you see the vegetation getting darker with distance, the background of the transparent area is probably black. If you see flickering white pixels, the transparent area is probably white.
</p>

<p>
	The script below will downsample the image, using the alpha-weighted technique Leadwerks uses, and then apply the blurred color to the transparent background of the original image, resulting in an image that looks like this:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="23375" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2026_05/after.jpg.bcf0b58280b74515c352f14a18259c28.jpg" rel=""><img alt="after.thumb.jpg.0062d5797e11ee94ec056989f1ada67b.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="23375" data-ratio="100.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2026_05/after.thumb.jpg.0062d5797e11ee94ec056989f1ada67b.jpg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	This will eliminate the artifacts you are seeing in your masked vegetation and provide clear but smooth alpha-masked surfaces, free of pixelation.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">local</span><span class="pun"> </span><span class="pln">file</span><span class="pun"> = </span><span class="pln">RequestFile</span><span class="pun">(</span><span class="str">"Open File"</span><span class="pun">, </span><span class="pln">CurrentDir</span><span class="pun">()..</span><span class="str">"/"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">if</span><span class="pun"> </span><span class="pln">file</span><span class="pun"> == </span><span class="str">""</span><span class="pun"> </span><span class="kwd">then</span><span class="pun"> </span><span class="kwd">return</span><span class="pun"> </span><span class="kwd">end</span><span class="pln">

</span><span class="kwd">local</span><span class="pun"> </span><span class="pln">pixmap</span><span class="pun"> = </span><span class="pln">LoadPixmap</span><span class="pun">(</span><span class="pln">file</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">if</span><span class="pun"> </span><span class="pln">pixmap</span><span class="pun"> == </span><span class="kwd">nil</span><span class="pun"> </span><span class="kwd">then</span><span class="pun"> </span><span class="kwd">return</span><span class="pun"> </span><span class="kwd">end</span><span class="pln">

</span><span class="kwd">local</span><span class="pun"> </span><span class="pln">mipmaps</span><span class="pun"> = </span><span class="pln">pixmap</span><span class="pun">:</span><span class="pln">BuildMipchain</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">local</span><span class="pun"> </span><span class="pln">n</span><span class="pun"> = #</span><span class="pln">mipmaps</span><span class="pun"> - </span><span class="lit">3</span><span class="pln">
</span><span class="kwd">if</span><span class="pun"> </span><span class="pln">n</span><span class="pun"> &lt; </span><span class="lit">1</span><span class="pun"> </span><span class="kwd">then</span><span class="pun"> </span><span class="pln">n</span><span class="pun"> = </span><span class="lit">1</span><span class="pun"> </span><span class="kwd">end</span><span class="pln">

</span><span class="kwd">local</span><span class="pun"> </span><span class="pln">x</span><span class="pun">, </span><span class="pln">y</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">, </span><span class="pln">r</span><span class="pun">, </span><span class="pln">g</span><span class="pun">, </span><span class="pln">b</span><span class="pun">, </span><span class="pln">a
</span><span class="kwd">local</span><span class="pun"> </span><span class="pln">changed</span><span class="pun"> = </span><span class="kwd">false</span><span class="pln">

</span><span class="kwd">for</span><span class="pun"> </span><span class="pln">x</span><span class="pun"> = </span><span class="lit">0</span><span class="pun">, </span><span class="pln">pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x</span><span class="pun"> - </span><span class="lit">1</span><span class="pun"> </span><span class="kwd">do</span><span class="pln">
    </span><span class="kwd">for</span><span class="pun"> </span><span class="pln">y</span><span class="pun"> = </span><span class="lit">0</span><span class="pun">, </span><span class="pln">pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">y</span><span class="pun"> - </span><span class="lit">1</span><span class="pun"> </span><span class="kwd">do</span><span class="pln">
        rgba</span><span class="pun"> = </span><span class="pln">pixmap</span><span class="pun">:</span><span class="pln">Sample</span><span class="pun">(</span><span class="pln">iVec2</span><span class="pun">(</span><span class="pln">x</span><span class="pun">, </span><span class="pln">y</span><span class="pun">))</span><span class="pln">
        </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">rgba</span><span class="pun">.</span><span class="pln">a</span><span class="pun"> &lt; </span><span class="lit">1.0</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
            background</span><span class="pun"> = </span><span class="pln">mipmaps</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]:</span><span class="pln">Sample</span><span class="pun">((</span><span class="pln">x</span><span class="pun"> + </span><span class="lit">0.5</span><span class="pun">) / </span><span class="pln">pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x</span><span class="pun">, (</span><span class="pln">y</span><span class="pun"> + </span><span class="lit">0.5</span><span class="pun">) / </span><span class="pln">pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln">
            rgba</span><span class="pun">.</span><span class="pln">r</span><span class="pun"> = </span><span class="pln">Mix</span><span class="pun">(</span><span class="pln">background</span><span class="pun">.</span><span class="pln">r</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">.</span><span class="pln">r</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">.</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
            rgba</span><span class="pun">.</span><span class="pln">g</span><span class="pun"> = </span><span class="pln">Mix</span><span class="pun">(</span><span class="pln">background</span><span class="pun">.</span><span class="pln">g</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">.</span><span class="pln">g</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">.</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
            rgba</span><span class="pun">.</span><span class="pln">b</span><span class="pun"> = </span><span class="pln">Mix</span><span class="pun">(</span><span class="pln">background</span><span class="pun">.</span><span class="pln">b</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">.</span><span class="pln">b</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">.</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
            pixmap</span><span class="pun">:</span><span class="pln">WritePixel</span><span class="pun">(</span><span class="pln">x</span><span class="pun">, </span><span class="pln">y</span><span class="pun">, </span><span class="pln">rgba</span><span class="pun">)</span><span class="pln">
            changed</span><span class="pun"> = </span><span class="kwd">true</span><span class="pln">
        </span><span class="kwd">end</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span><span class="pln">

</span><span class="kwd">if</span><span class="pun"> </span><span class="pln">changed</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
    pixmap</span><span class="pun">:</span><span class="pln">Save</span><span class="pun">(</span><span class="pln">StripExt</span><span class="pun">(</span><span class="pln">file</span><span class="pun">)..</span><span class="str">"_fixed."</span><span class="pun">..</span><span class="pln">ExtractExt</span><span class="pun">(</span><span class="pln">file</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">else</span><span class="pln">
    Print</span><span class="pun">(</span><span class="str">"No transparent pixels"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">end</span></pre>

<p>
	 
</p>
]]></description><guid isPermaLink="false">2898</guid><pubDate>Thu, 28 May 2026 15:32:37 +0000</pubDate></item><item><title>Breakable Objects</title><link>https://www.leadwerks.com/community/blogs/entry/2884-breakable-objects/</link><description><![CDATA[<p>
	I love the physics in games like Amnesia and the Half-Life series. Realistic interactive physics add a degree of interactivity that immerses the player in the world like nothing else, and I plan on physics being a key feature in <a href="https://www.leadwerks.com/scp" rel="external nofollow">our SCP game</a>. In this article, I will walk you through the steps I took to make a breakable crate.
</p>

<p>
	<span style="font-size:18px;">The Breakable Model</span>
</p>

<p>
	As usual, I like to start with the artwork, because it's key to everything. I usually learn so much just by using actual game-ready artwork, rather than blocky "programmer art". Plus, at the end I get something I am proud of showing off. <a contenteditable="false" data-ipshover="" data-ipshover-target="https://www.leadwerks.com/community/profile/12469-reepblue/?do=hovercard" data-mentionid="12469" href="https://www.leadwerks.com/community/profile/12469-reepblue/" rel="">@reepblue</a> created this model and provided it for use with Leadwerks, and <a contenteditable="false" data-ipshover="" data-ipshover-target="https://www.leadwerks.com/community/profile/30010-rich-d/?do=hovercard" data-mentionid="30010" href="https://www.leadwerks.com/community/profile/30010-rich-d/" rel="">@Rich D</a> textured it and broke it up into fragments, for your destructive enjoyment.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22504" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.d775701f6051fb2516728346be26274d.png" rel=""><img alt="image.thumb.png.927415219c3fe4a4989c66835f602f94.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22504" data-ratio="60.93" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.927415219c3fe4a4989c66835f602f94.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	In fact, breakable objects are primarily an art problem. The basic concept is very simple: When an object is damaged, hide it and replace it with a series of fragments that represent the object broken into pieces. This is not a complicated programming problem at all, but let's dig into the details and see what happens.
</p>

<p>
	<span style="font-size:18px;">Colliders</span>
</p>

<p>
	The first question is how the colliders for the model fragments should be made.
</p>

<p>
	If your colliders fit too perfectly, the broken object will form a strong interlocking structure, and won't fall apart unless a large force is applied to it. Even worse, if your fragment colliders intersect the moment they are enabled, the object will form explosive forces that could send the crate or the player flying! We definitely don't want this happening.
</p>

<p>
	The main question is, should fragments of the same object even intersect at all? I was curious how Half-Life 2 handled this, so I fired it up, broke a crate, picked up one of the pieces, and saw that fragments of a broken object don't collide with each other at all! Here I am holding one piece, and it freely intersects the other fragments with no collision, even if I drop it. I have 36 hours of play time in this game, and I never noticed this until now!
</p>

<p>
	<img alt="debugging23.gif.d2055399f5b87c9d3920426b0e153341.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="22513" data-ratio="56.25" style="height:auto;" width="640" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/debugging23.gif.d2055399f5b87c9d3920426b0e153341.gif" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	Well, if this solution is good enough for Valve, then I don't feel a need to make it more complicated than it needs to be. This simplifies our goals because we can just set the collision type of the fragments to "Debris", which will collide with the Prop and Scene collision types, but not with the player, and not with other broken pieces. That makes it perfect for this use, and we don't have to worry too much about the exact shape of the colliders for each piece of our broken object. If the fragment colliders overlap, it won't cause explosive forces or lock the pieces into place like a jigsaw puzzle.
</p>

<p>
	The next question is how should the content be structured? I have a single-mesh unbroken version of the model, as well as a version that contains all the fragments separated into child limbs. I could save each limb as a separate model and maybe use some naming scheme to identify them, like crate_frag1.mdl, crate_frag2.mdl, etc. However, I think the simplest way to handle this is to make the unbroken version of the model the top-level entity in the hierarchy, and then just add the fragments as children in the model file. The script will just make the assumption that all children in the model are fragments, and hide them when the game starts:
</p>

<ul>
	<li>
		Unbroken mesh
		<ul>
			<li>
				Fragment 1
			</li>
			<li>
				Fragment 2
			</li>
			<li>
				Fragment 3
			</li>
		</ul>
	</li>
</ul>

<p>
	I think this design accounts for the most likely use case, and making it more complicated would be more trouble than its worth.
</p>

<p>
	I noticed that in the current build of Leadwerks Editor, the model editor only allows assigning a collider to the whole object. Let's fix that, so we can assign a separate collider to any object in the hierarchy!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22507" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.c60ae27406cc25138067168039815ca4.png" rel=""><img alt="image.thumb.png.820aa553120fc8fb557904b2b78b7d5b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22507" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.820aa553120fc8fb557904b2b78b7d5b.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	I performed that for each limb in the broken crate model.
</p>

<p>
	I then opened the unbroken crate model, and assigned a box collider to it, and saved it:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22508" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.53e593fa4a7c254f7c156cde66b6598c.png" rel=""><img alt="image.thumb.png.138e64de77474b28327942578d889462.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22508" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.138e64de77474b28327942578d889462.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	<span style="font-size:18px;">Merging the Models</span>
</p>

<p>
	Now how to we combine these two models? An artist might want to export the two models to glTF and combine them in Blender or another program, but since I am a programmer, the easiest solution for me is to just write a simple script.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">local</span><span class="pun"> </span><span class="pln">mdl1</span><span class="pun"> = </span><span class="pln">LoadModel</span><span class="pun">(</span><span class="kwd">nil</span><span class="pun">, </span><span class="str">"Models/Crate/crate01.mdl"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">local</span><span class="pun"> </span><span class="pln">mdl2</span><span class="pun"> = </span><span class="pln">LoadModel</span><span class="pun">(</span><span class="kwd">nil</span><span class="pun">, </span><span class="str">"Models/Crate/cratebroken01.mdl"</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">while</span><span class="pun"> #</span><span class="pln">mdl2</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> &gt; </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">do</span><span class="pln">
    </span><span class="kwd">local</span><span class="pun"> </span><span class="pln">child</span><span class="pun"> = </span><span class="pln">mdl2</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
    child</span><span class="pun">:</span><span class="pln">SetParent</span><span class="pun">(</span><span class="pln">mdl1</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">end</span><span class="pln">

mdl1</span><span class="pun">:</span><span class="pln">Save</span><span class="pun">(</span><span class="str">"Models/Crate/Crate01_Combined.mdl"</span><span class="pun">)</span></pre>

<p>
	The only thing to watch out for is that when were call SetParent we are modifying the list of objects we are iterating through. Therefore, it's best to use a while loop and just get the first child each time. This tripped me up for a few minutes because I started with a simple for loop! <img alt=":blink:" data-emoticon="" src="https://leadwerksstorage.s3.us-east-2.amazonaws.com/emoticons/default_blink.png" title=":blink:" /> Here's the wrong way that I tried first:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="com">--Don't do this!</span><span class="pln">
</span><span class="kwd">for</span><span class="pun"> </span><span class="pln">n</span><span class="pun"> = </span><span class="lit">1</span><span class="pun">, #</span><span class="pln">mdl2</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> </span><span class="kwd">do</span><span class="pln">
    </span><span class="kwd">local</span><span class="pun"> </span><span class="pln">child</span><span class="pun"> = </span><span class="pln">mdl2</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln">
    child</span><span class="pun">:</span><span class="pln">SetParent</span><span class="pun">(</span><span class="pln">mdl1</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">end</span></pre>

<p>
	I just copied and pasted my script code into the editor console, pressed enter, and it saves the model with the unbroken single mesh as the top-level limb, and all the fragments as children. Separate colliders for each limb are retained correctly:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22509" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.ac55f60b0989f3446e46d099ae4829e1.png" rel=""><img alt="image.thumb.png.abadc21552eb70c255d34017d96a41b3.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22509" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.abadc21552eb70c255d34017d96a41b3.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Awesome!
</p>

<p>
	Now that the artwork is correctly prepared, the actual code this requires will be very easy.
</p>

<p>
	<span style="font-size:18px;">Breakable Object Script</span>
</p>

<p>
	Let's create a new entity script called "Breakable" in the Physics folder. We'll add a property for health, and another for the speed of a collision that will cause the object to break:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">Breakable</span><span class="pun"> = {}</span><span class="pln">
Breakable</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> = </span><span class="lit">10</span><span class="pun"> </span><span class="com">--"Health"</span><span class="pln">
Breakable</span><span class="pun">.</span><span class="pln">breakspeed</span><span class="pun"> = </span><span class="lit">10</span><span class="pun"> </span><span class="com">--"Break speed"</span></pre>

<p>
	In the Start function, we'll just hide all the child limbs, since we have decided already that those will form the fragments of the object when it breaks:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Start</span><span class="pun">()</span><span class="pln">	
	</span><span class="kwd">local</span><span class="pun"> </span><span class="pln">n
	</span><span class="kwd">for</span><span class="pun"> </span><span class="pln">n</span><span class="pun"> = </span><span class="lit">1</span><span class="pun">, #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> </span><span class="kwd">do</span><span class="pun"> </span><span class="com">--No problems using a for loop here since we are not modifying the entity limbs ;)</span><span class="pln">
		self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]:</span><span class="pln">SetMass</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="com">--This should already be zero, but if not let's set it automatically</span><span class="pln">
		self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]:</span><span class="pln">SetCollisionType</span><span class="pun">(</span><span class="pln">COLLISION_NONE</span><span class="pun">)</span><span class="pln">
		self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
		</span><span class="com">--self.kids[n]:SetColor(0,4,0,1) --optional visual hint for testing</span><span class="pln">
	</span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span></pre>

<p>
	We will add a function so that the object will accept damage if something hits it. Most likely this will be caused by the player shooting or hitting object with a melee weapon, but if an enemy attacks the crate for any reason, it will also work.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Damage</span><span class="pun">(</span><span class="pln">amount</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> &gt; </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> = </span><span class="pln">self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> - </span><span class="pln">amount
        </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> &lt;= </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
            self</span><span class="pun">:</span><span class="pln">Break</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">end</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span></pre>

<p>
	In the collide function, we can add a check to see if the break speed was hit, and break the object if it was:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Collide</span><span class="pun">(</span><span class="pln">entity</span><span class="pun">, </span><span class="pln">position</span><span class="pun">, </span><span class="pln">normal</span><span class="pun">, </span><span class="pln">speed</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">speed</span><span class="pun"> &gt; </span><span class="pln">self</span><span class="pun">.</span><span class="pln">breakspeed</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
        self</span><span class="pun">:</span><span class="pln">Break</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span></pre>

<p>
	Finally, we add our Break function, where the object finally gets broken apart into pieces:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Break</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">self</span><span class="pun">:</span><span class="pln">GetHidden</span><span class="pun">() </span><span class="kwd">then</span><span class="pun"> </span><span class="kwd">return</span><span class="pun"> </span><span class="kwd">end</span><span class="pun"> </span><span class="com">--Make sure this only gets broken once</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> == </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pun"> </span><span class="kwd">return</span><span class="pun"> </span><span class="kwd">end</span><span class="pun"> </span><span class="com">--If there are no kids then don't do anything</span><span class="pln">

    </span><span class="com">--Hide the object</span><span class="pln">
    self</span><span class="pun">:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
	
    </span><span class="com">--Calculate simple mass for each piece</span><span class="pln">
    </span><span class="kwd">local</span><span class="pun"> </span><span class="pln">mass</span><span class="pun"> = </span><span class="pln">self</span><span class="pun">:</span><span class="pln">GetMass</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">mass</span><span class="pun"> == </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
        mass</span><span class="pun"> = </span><span class="lit">5</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
        mass</span><span class="pun"> = </span><span class="pln">mass</span><span class="pun"> / #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids
    </span><span class="kwd">end</span><span class="pln">
	
    </span><span class="com">--Create a table to store the fragments in</span><span class="pln">
    self</span><span class="pun">.</span><span class="pln">fragments</span><span class="pun"> = {}</span><span class="pln">
    </span><span class="kwd">while</span><span class="pun"> #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> &gt; </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">do</span><span class="pln">
        </span><span class="kwd">local</span><span class="pun"> </span><span class="pln">child</span><span class="pun"> = </span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
        child</span><span class="pun">:</span><span class="pln">SetParent</span><span class="pun">(</span><span class="kwd">nil</span><span class="pun">)</span><span class="pln">
        child</span><span class="pun">:</span><span class="pln">SetMass</span><span class="pun">(</span><span class="pln">mass</span><span class="pun">)</span><span class="pln">
        child</span><span class="pun">:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">)</span><span class="pln">		
        child</span><span class="pun">:</span><span class="pln">SetCollisionType</span><span class="pun">(</span><span class="pln">COLLISION_DEBRIS</span><span class="pun">)</span><span class="pln">
        table</span><span class="pun">.</span><span class="pln">insert</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">fragments</span><span class="pun">, </span><span class="pln">child</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span></pre>

<p>
	<span style="font-size:18px;">Running the Game</span>
</p>

<p>
	I was very excited when I ran the game for the first time, in a level with a crate hooked up to this script. What would happen if I shot the object? Would it break into pieces and look natural, like I hoped it would? Here is the result:
</p>

<div class="ipsEmbeddedVideo" contenteditable="false">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="150" title="Breakable Objects" width="200" data-embed-src="https://www.youtube-nocookie.com/embed/0UL1-Phfs3E?feature=oembed"></iframe>
	</div>
</div>

<p>
	Wow, on the first try it looks great! Adding sounds for collision and breaking would enhance the feel of it even more, and I plan to add this next. I also plan to try adding some velocity to the broken fragments, so the crate has more of an explosive appearance, instead of just falling apart in place.
</p>

<p>
	Here is the complete script, with the accompanying properties definition file. The script requires a game created with the current "beta-e" branch on Steam:
</p>

<p>
	<a class="ipsAttachLink" data-fileext="lua" data-fileid="22510" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=22510&amp;key=ed955c1d84fc4e5946017509c9393266" rel="">Breakable.lua</a><a class="ipsAttachLink" data-fileext="json" data-fileid="22511" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=22511&amp;key=0536fdedb3c63bfad5422baedf69484e" rel="">Breakable.json</a>
</p>

<p>
	And here are the model files I used. Crate01_Combined.mdl is the one that is ready-to-use with this script:
</p>

<p>
	<a class="ipsAttachLink" data-fileext="zip" data-fileid="22512" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=22512&amp;key=a92c40e2f940f83a6d06a8e09ea3c7b3" rel="">Crate01.zip</a>
</p>

<p>
	I even find the code itself for this very beautiful, for some reason. The weird comments with the @ signs are annotations that give Lua Language Server a hint about what type each parameter is, for nice auto-completion in our script editor. These comments are automatically generated when the script is first created.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="com">---@class Breakable : Entity</span><span class="pln">
Breakable</span><span class="pun"> = {}</span><span class="pln">
Breakable</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> = </span><span class="lit">10</span><span class="pun"> </span><span class="com">--"Health"</span><span class="pln">
Breakable</span><span class="pun">.</span><span class="pln">breakspeed</span><span class="pun"> = </span><span class="lit">10</span><span class="pun"> </span><span class="com">--"Break speed"</span><span class="pln">

</span><span class="com">---@param self Breakable</span><span class="pln">
</span><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Start</span><span class="pun">()</span><span class="pln">	
    </span><span class="kwd">local</span><span class="pun"> </span><span class="pln">n
    </span><span class="kwd">for</span><span class="pun"> </span><span class="pln">n</span><span class="pun"> = </span><span class="lit">1</span><span class="pun">, #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> </span><span class="kwd">do</span><span class="pun"> </span><span class="com">--No problems using a for loop here since we are not modifying the entity limbs ;)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]:</span><span class="pln">SetMass</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="com">--This should already be zero, but if not let's set it automatically</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]:</span><span class="pln">SetCollisionType</span><span class="pun">(</span><span class="pln">COLLISION_NONE</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
        </span><span class="com">--self.kids[n]:SetColor(0,4,0,1) --optional visual hint for testing</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span><span class="pln">

</span><span class="com">---@param self Breakable</span><span class="pln">
</span><span class="com">---@param entity Entity</span><span class="pln">
</span><span class="com">---@param position Vec3</span><span class="pln">
</span><span class="com">---@param normal Vec3</span><span class="pln">
</span><span class="com">---@param speed number</span><span class="pln">
</span><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Collide</span><span class="pun">(</span><span class="pln">entity</span><span class="pun">, </span><span class="pln">position</span><span class="pun">, </span><span class="pln">normal</span><span class="pun">, </span><span class="pln">speed</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">speed</span><span class="pun"> &gt; </span><span class="pln">self</span><span class="pun">.</span><span class="pln">breakspeed</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
        self</span><span class="pun">:</span><span class="pln">Break</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span><span class="pln">

</span><span class="com">---@param self Breakable</span><span class="pln">
</span><span class="com">---@param amount number</span><span class="pln">
</span><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Damage</span><span class="pun">(</span><span class="pln">amount</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> &gt; </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> = </span><span class="pln">self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> - </span><span class="pln">amount
        </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">self</span><span class="pun">.</span><span class="pln">health</span><span class="pun"> &lt;= </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
            self</span><span class="pun">:</span><span class="pln">Break</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">end</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span><span class="pln">

</span><span class="com">---@param self Breakable</span><span class="pln">
</span><span class="kwd">function</span><span class="pun"> </span><span class="pln">Breakable</span><span class="pun">:</span><span class="pln">Break</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">self</span><span class="pun">:</span><span class="pln">GetHidden</span><span class="pun">() </span><span class="kwd">then</span><span class="pun"> </span><span class="kwd">return</span><span class="pun"> </span><span class="kwd">end</span><span class="pun"> </span><span class="com">--Make sure this only gets broken once</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> == </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pun"> </span><span class="kwd">return</span><span class="pun"> </span><span class="kwd">end</span><span class="pun"> </span><span class="com">--If there are no kids then don't do anything</span><span class="pln">

    </span><span class="com">--Hide the object</span><span class="pln">
    self</span><span class="pun">:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
	
    </span><span class="com">--Calculate simple mass for each piece</span><span class="pln">
    </span><span class="kwd">local</span><span class="pun"> </span><span class="pln">mass</span><span class="pun"> = </span><span class="pln">self</span><span class="pun">:</span><span class="pln">GetMass</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pun"> </span><span class="pln">mass</span><span class="pun"> == </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">then</span><span class="pln">
        mass</span><span class="pun"> = </span><span class="lit">5</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
        mass</span><span class="pun"> = </span><span class="pln">mass</span><span class="pun"> / #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids
    </span><span class="kwd">end</span><span class="pln">
	
    </span><span class="com">--Create a table to store the fragments in</span><span class="pln">
    self</span><span class="pun">.</span><span class="pln">fragments</span><span class="pun"> = {}</span><span class="pln">
    </span><span class="kwd">while</span><span class="pun"> #</span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun"> &gt; </span><span class="lit">0</span><span class="pun"> </span><span class="kwd">do</span><span class="pln">
        </span><span class="kwd">local</span><span class="pun"> </span><span class="pln">child</span><span class="pun"> = </span><span class="pln">self</span><span class="pun">.</span><span class="pln">kids</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
        child</span><span class="pun">:</span><span class="pln">SetParent</span><span class="pun">(</span><span class="kwd">nil</span><span class="pun">)</span><span class="pln">
        child</span><span class="pun">:</span><span class="pln">SetMass</span><span class="pun">(</span><span class="pln">mass</span><span class="pun">)</span><span class="pln">
        child</span><span class="pun">:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">)</span><span class="pln">		
        child</span><span class="pun">:</span><span class="pln">SetCollisionType</span><span class="pun">(</span><span class="pln">COLLISION_DEBRIS</span><span class="pun">)</span><span class="pln">
        table</span><span class="pun">.</span><span class="pln">insert</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">fragments</span><span class="pun">, </span><span class="pln">child</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span></pre>

<p>
	If you are looking for custom artwork for your game, I can wholeheartedly recommend Rich DiGiovanni's services. I have worked with Rich on many projects, and I always get great results at a reasonable price: <a href="https://richdigiovanni.com/" rel="external nofollow">https://richdigiovanni.com/</a>
</p>
]]></description><guid isPermaLink="false">2884</guid><pubDate>Wed, 27 Aug 2025 04:30:46 +0000</pubDate></item><item><title>Interactive Multi-body Physics</title><link>https://www.leadwerks.com/community/blogs/entry/2880-interactive-multi-body-physics/</link><description><![CDATA[<p>
	I love the physics interactions in Half-Life 2, Penumbra, and similar games, and I want this to be a key part of gameplay in our <a href="https://www.leadwerks.com/scp" rel="external nofollow">upcoming SCP game</a>. Physics interactions should should be everywhere, all around the player, so that the world feels alive and interactive.
</p>

<p>
	Furniture is a great way to add physics interactions to your game, because each object can be set up one, and many instances can of the object can be placed in your levels. Cabinets and doors can swivel open and closed using a hinge joint. Drawers can slide in and out using a slider joint. This simple interactivity can turn a search for an important item into a mini-game, and encourages the player to explore the world in hopes of finding a valuable item.
</p>

<p>
	I decided to start with a filing cabinet that <a contenteditable="false" data-ipshover="" data-ipshover-target="https://www.leadwerks.com/community/profile/272-rich/?do=hovercard" data-mentionid="272" href="https://www.leadwerks.com/community/profile/272-rich/" rel="">@Rich</a> created. Each drawer is a separate limb in the model, so they can move independently.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22441" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.03d5fe604dc8c1b1b4df3f6c91cef280.png" rel=""><img alt="image.thumb.png.b0a53b617ac8c7efab73aad1abf1914b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22441" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.b0a53b617ac8c7efab73aad1abf1914b.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	<span style="font-size:18px;">Colliders</span>
</p>

<p>
	This item will require five different oddly-shape colliders. One for the cabinet, and another for each of the four drawers. I want to be able to place items in the drawers and close the drawers, while still having collision with the exterior of the cabinet, so the colliders need to be pretty detailed.
</p>

<p>
	The first thing I did was open the model in the model editor, delete everything except the bottom drawer, collapse it, then save that model as a separate drawer model.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22442" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.ae59f499e04e943788cbfc73efb26720.png" rel=""><img alt="image.thumb.png.83a923d3e2e71e6ac302a3f32867d1b1.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22442" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.83a923d3e2e71e6ac302a3f32867d1b1.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Because the drawers are dynamic objects, they could not use polygon mesh colliders. There isn't a single collider shape that is appropriate for drawers, and a convex hull would not give me the abililty to place items in the drawer. What I needed was a multi-part collider made up of convex hulls.
</p>

<p>
	I added the drawer model to a new scene, positioned it as (0,0,0), and started building a collision shape out of brushes. I saved this as a map file and will keep it in case I want to modify the collider a bit more in the future: My minimum thickness here is four centimeters, a number I will keep in mind in the future and revise if it turns out this is needed after play testing.
</p>

<p>
	<img alt="image.png.d47d53d7ac709b19a91c00d39bca8db6.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22443" data-ratio="79.50" style="height:auto;" width="639" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.d47d53d7ac709b19a91c00d39bca8db6.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	I selected all the brushes, right clicked in the scene editor, and selected the "Save as Model" popup menu item, and saved a MDL file. Now I had a model file with separate limbs for the different convex hulls I wanted to make the shape out of.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22444" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.b7536c5b98c17197d4909549dc023c46.png" rel=""><img alt="image.thumb.png.eb68563607498e0ac22ef21f3f0c626e.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22444" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.eb68563607498e0ac22ef21f3f0c626e.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	I did the same thing for the cabinet exterior. Entities that are connected to each other with a joint will not collide with each other, fortunately, so we don't have to worry about making the drawer and cabinet colliders match exactly. If the drawers intersect the file cabinet collider a little bit, it's not a problem. The individual drawers <em>will </em>collide with each other, since they are not connected to the same joint, so take care that they do not interpenetrate.
</p>

<p>
	I am not sure yet if I want the file cabinets to be immovable or dynamic. Since it's not difficult, I will make the collider out of separate convex pieces so I can make it dynamic in the future if I decide I want to.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22445" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.6b0777a6837e3a15cadb83fff158d558.png" rel=""><img alt="image.thumb.png.bfb5d2251aac42626e0f62c16dc81e13.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22445" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.bfb5d2251aac42626e0f62c16dc81e13.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Now we are ready to add the colliders to the models. I opened the drawer model in the model editor and selected the new <strong>File &gt; Load Dynamic Collider </strong>menu item. This will load a model and turn each limb into a separate convex hull, for multi-part colliders. It worked perfectly!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22446" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.81e7b4a510d821e563413ce482e41d58.png" rel=""><img alt="image.thumb.png.dbd3f47d065a4320d4b8a18d03991a89.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22446" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.dbd3f47d065a4320d4b8a18d03991a89.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	For this to work as expected, you usually need the root limb in your visual model to be positioned at (0,0,0), with rotation (0,0,0), and scale (1,1,1). You may get weirdly rotated, offset, or scaled results if this is not the case. Fortunately, you can reset the model's transformation matrix just by selecting the <strong>Tools &gt; Reset Transform</strong> menu item. You might need to do this to the visual model, the collider model, or both!
</p>

<p>
	Now that I had my individual models, with colliders, I added the file cabinet to the scene, added four drawers, positioned them each in the right place, and parented them to the cabinet. Positioning drawers was actually pretty easy and their placement does not have to be exact, so don't get obssessive about making them perfect.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22448" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.e3d21e934e8732c335764cbee9c0656e.png" rel=""><img alt="image.thumb.png.920d369d5954963012dfe7bc1e1c0bea.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22448" data-ratio="65.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.920d369d5954963012dfe7bc1e1c0bea.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	I saved the assembly as a prefab, and now I can place the cabinet anywhere I like. I could even save this assembly as a new model file, and it would retain the colliders for each limb, but I think I am going to leave it in its separate pieces, referencing the file cabinet and draw models. This way I can easily modify those models in the future, if I am not happy with how their colliders are shaped.
</p>

<p>
	Since I ended up just using one repeating drawer model, I recommend all furniture in the future be exported from the modeling program this way: the cabinet object with no drawers, doors, or whatever moving parts there are, and then one copy of the drawer or door. Since it's pretty easy to line the drawers up in the right place, you don't even need any nodes to hint where they are supposed to be placed.
</p>

<p>
	<span style="font-size:18px;">Joints</span>
</p>

<p>
	The next step was to add a slider joint for each drawer. I added a new class that just creates a slider joint. There are two pieces of information I am interested in, the joint pin and the limits. The pin defines the vector the joint slides along, and the limits determine where the joint starts and stops. I want the drawer to be locked to the cabinet so it can't be pulled out all the way, and the joint limits will help me achieve that behavior. I could add more properties in the future, but for now let's just keep it simple.
</p>

<p>
	<a class="ipsAttachLink" data-fileext="zip" data-fileid="22465" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=22465&amp;key=34228bee03545f543eacc3753db54e60" rel="">SliderJoint.zip</a>
</p>

<p>
	 
</p>

<p>
	In the Start method, a slider joint is created between the entity and its own parent. If there is no parent, this is actually okay. The joint will just be created as if a non-movable parent object is used.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">void</span><span class="pln"> </span><span class="typ">SliderJoint</span><span class="pun">::</span><span class="typ">Start</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> entity </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetEntity</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">entity</span><span class="pun">-&gt;</span><span class="typ">GetMass</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0.0f</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Slider joint requires entity to have non-zero mass"</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> gpin </span><span class="pun">=</span><span class="pln"> </span><span class="typ">TransformVector</span><span class="pun">(</span><span class="pln">pin</span><span class="pun">,</span><span class="pln"> entity</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">);</span><span class="pln">
    joint </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateSliderJoint</span><span class="pun">(</span><span class="pln">entity</span><span class="pun">-&gt;</span><span class="typ">GetPosition</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">),</span><span class="pln"> gpin</span><span class="pun">,</span><span class="pln"> entity</span><span class="pun">-&gt;</span><span class="typ">GetParent</span><span class="pun">(),</span><span class="pln"> entity</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">limits</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> limits</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln"> joint</span><span class="pun">-&gt;</span><span class="typ">SetLimits</span><span class="pun">(</span><span class="pln">limits</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> limits</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	I created a simple scene with a floor, a pivot for the player start position, and our filing cabinet. I selected one drawer, pulled it out a bit, set the pass to 2.0, and added the slider joint to it. If it works, I should be able to walk into the drawer and push it back into the cabinet.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22449" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.066a91e1de669041a196267c8fd29878.png" rel=""><img alt="image.thumb.png.941fb843c9ab5ab2b70cf970f7d05cdd.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22449" data-ratio="45.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.941fb843c9ab5ab2b70cf970f7d05cdd.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	When I run the game, I am able to push the drawer in a bit, but it seems like it is hitting something when it gets close to the filing cabinet. Since the drawer does not collide with the cabinet, it must be hitting the other drawers.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="22454" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/debugging.gif.340fa9c42725ba355af2bb5854019c09.gif" rel=""><img alt="debugging.thumb.gif.22b01353c235f16bc1387101f17d18e0.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="22454" data-ratio="78.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/debugging.thumb.gif.22b01353c235f16bc1387101f17d18e0.gif" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	I went back to the editor and moved the drawer above and below out of the way, to make sure that was actually the problem:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22451" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.94e820f125e3dd4ddc875872a7b9170f.png" rel=""><img alt="image.thumb.png.6a87f4aaac5e235b40cf528e38d892f3.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22451" data-ratio="62.53" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.6a87f4aaac5e235b40cf528e38d892f3.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	When I ran the game I could push the drawer in all the way, and then some. Because there is no collision between the drawer and its parent, the cabinet itself, there is nothing to stop the drawer unless I set the joint limits. I'm not worried about this, so it's fine for now. I'm more worried about these collidring drawers!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22452" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.33099ad7159cea6ffda4fccb3e6dd1bc.png" rel=""><img alt="image.thumb.png.344aa771a90574bbd48643fdfb2fcc45.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22452" data-ratio="55.07" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.344aa771a90574bbd48643fdfb2fcc45.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	If I pull the other drawers out all the way, the problem becomes can be easily seen. The front panel overlaps with the drawer above it. How should I resolve this? I already said I want 4 centimeters to be my minimum thickness, since very thin objects could potentially have problems with small objects falling through them.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22455" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.e367c145a5c39630a583c03e8b8814f0.png" rel=""><img alt="image.thumb.png.8c523e2b53c01469efdfd0201ff76a01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22455" data-ratio="65.87" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.8c523e2b53c01469efdfd0201ff76a01.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Fortunately, I still have my map file where I created the drawer collider shape. I decided to just lower the height of the front piece so that it matched the sides and the back. Then I saved the brush objects as a model, and loaded it back into the fiel cabinet drawer model as a dynamic collider. We might change this design in the future, but for now I just want to get it working!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22456" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.f9ef154fba440836642bb5167e498e66.png" rel=""><img alt="image.thumb.png.38f2765198919c498f5716e6af260c7c.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22456" data-ratio="68.27" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.38f2765198919c498f5716e6af260c7c.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Here is the drawer with the new collider loaded:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22457" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.4c40fc9be7a9b8fb9e16fedfc340172f.png" rel=""><img alt="image.thumb.png.c4df3ae463d76507e65f79a399235c49.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22457" data-ratio="56.53" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.c4df3ae463d76507e65f79a399235c49.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	When I load my scene back up, it looks like the drawers have plenty of clearance.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22458" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.b5b769a0f46659cbe2bbfa86e196592e.png" rel=""><img alt="image.thumb.png.cb208ca7b7dff17905d998b2666ea541.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22458" data-ratio="77.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.cb208ca7b7dff17905d998b2666ea541.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	The game now works as expected. I can push the drawer in all the way without hitting anything.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22459" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.17cfbcc6f847960617fb3d6385129ca6.png" rel=""><img alt="image.thumb.png.dce7aa7fddbbd23ee70d46d54a43ee3c.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22459" data-ratio="58.53" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.dce7aa7fddbbd23ee70d46d54a43ee3c.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	I added the slider joint to the other drawers and set their mass to 2.0, and all the drawers could now be pushed freely.
</p>

<p>
	<img alt="image.png.4986a4146336abebd6494cd140b1b857.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22460" data-ratio="93.20" style="height:auto;" width="647" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.4986a4146336abebd6494cd140b1b857.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	I discovered an interesting problem while doing this! It's easy for the drawers to form a stair case shape. When this happens, the player will step almost instantly up all the drawers, to the top of the cabinet! In the future, maybe we will need a per-entity setting that makes it so an entity is non-steppable. For right now, I am not too concerned about it because I want to manually push and pull these drawers, not lean into them.
</p>

<p>
	<span style="font-size:18px;">Tactile Physics</span>
</p>

<p>
	I love the player physics in <em>Amnesia: The Dark Descent</em>, and I wanted to bring this type of interaction to our game. It seemed like it would be achievable, since we are using the same excellent physics library, <a href="https://newtondynamics.com/forum/newton.php" rel="external nofollow">Newton Game Dynamics</a>, that is in use by Frictional Games.
</p>

<p>
	We already have logic in our player class for picking up objects, and to my delight this mostly worked with the drawers. When a new object is picked up, a kinematic joint is created. The kinematic joint will then exert force and torque on the object the player is grabbing.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">pickedentity</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
	</span><span class="typ">float</span><span class="pln"> mass </span><span class="pun">=</span><span class="pln"> pickedentity</span><span class="pun">-&gt;</span><span class="typ">GetMass</span><span class="pun">();</span><span class="pln">
	</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mass </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0.0f</span><span class="pln"> and mass </span><span class="pun">&lt;</span><span class="pln"> maxcarrymass </span><span class="pun">+</span><span class="pln"> maxcarrytolerance</span><span class="pun">)</span><span class="pln">
	</span><span class="pun">{</span><span class="pln">
		carryjoint </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateKinematicJoint</span><span class="pun">(</span><span class="pln">pickedentity</span><span class="pun">-&gt;</span><span class="typ">GetPosition</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">),</span><span class="pln"> pickedentity</span><span class="pun">);</span><span class="pln">
		carryjoint</span><span class="pun">-&gt;</span><span class="typ">SetMaxForce</span><span class="pun">(</span><span class="lit">1000</span><span class="pun">);</span><span class="pln">
		carryjoint</span><span class="pun">-&gt;</span><span class="typ">SetMaxTorque</span><span class="pun">(</span><span class="lit">1000</span><span class="pun">);</span><span class="pln">
		carrycollisiontype </span><span class="pun">=</span><span class="pln"> pickedentity</span><span class="pun">-&gt;</span><span class="typ">GetCollisionType</span><span class="pun">();</span><span class="pln">
		pickedentity</span><span class="pun">-&gt;</span><span class="typ">SetCollisionType</span><span class="pun">(</span><span class="pln">COLLISION_DEBRIS</span><span class="pun">);</span><span class="pln">
		carryposition </span><span class="pun">=</span><span class="pln"> </span><span class="typ">TransformPoint</span><span class="pun">(</span><span class="pln">pickedentity</span><span class="pun">-&gt;</span><span class="typ">GetPosition</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">),</span><span class="pln"> NULL</span><span class="pun">,</span><span class="pln"> camera</span><span class="pun">);</span><span class="pln">
		carryposition</span><span class="pun">.</span><span class="pln">z </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">0.1f</span><span class="pun">;</span><span class="pln">
		carryrotation </span><span class="pun">=</span><span class="pln"> </span><span class="typ">TransformRotation</span><span class="pun">(</span><span class="pln">pickedentity</span><span class="pun">-&gt;</span><span class="typ">GetQuaternion</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">),</span><span class="pln"> NULL</span><span class="pun">,</span><span class="pln"> camera</span><span class="pun">);</span><span class="pln">
		pickedobject</span><span class="pun">.</span><span class="pln">reset</span><span class="pun">();</span><span class="pln">
	</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	I made a small improvement to disable torque if the picked up object has any joints attached to it. This makes sense, because you don't want to rotate the drawer, just move it on one axis:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">not pickedentity</span><span class="pun">-&gt;</span><span class="typ">GetJoints</span><span class="pun">().</span><span class="pln">empty</span><span class="pun">())</span><span class="pln"> carryjoint</span><span class="pun">-&gt;</span><span class="typ">SetMaxTorque</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span></pre>

<p>
	When the game is played, the physics basically work the way I wanted. Of course there are no joint limits here, so there is nothing from stopping the drawers from going through the cabinet, or being thrown very far away. <img alt=":D" data-emoticon="" height="20" src="https://leadwerksstorage.s3.us-east-2.amazonaws.com/emoticons/default_biggrin.png" srcset="https://leadwerksstorage.s3.us-east-2.amazonaws.com/emoticons/biggrin@2x.png 2x" title=":D" width="20" />
</p>

<div class="ipsEmbeddedVideo" contenteditable="false">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" title="Filing Cabinet Physics Take 1" width="200" data-embed-src="https://www.youtube-nocookie.com/embed/aeeI-XsPxl4?feature=oembed"></iframe>
	</div>
</div>

<p>
	I set the joint limits property to [-40, 0], since the drawer starts closed by default.
</p>

<p>
	<img alt="image.png.bd49f60a6afb7f5a989446a3e327b91f.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22462" data-ratio="60.73" style="height:auto;" width="354" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.bd49f60a6afb7f5a989446a3e327b91f.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	When I ran the game again, here was the result. You can see the joint limits are working as intended.
</p>

<div class="ipsEmbeddedVideo" contenteditable="false">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" title="File Cabinet Physics Take 2" width="200" data-embed-src="https://www.youtube-nocookie.com/embed/JxreUgoVra8?feature=oembed"></iframe>
	</div>
</div>

<p>
	The behavior is not perfect. It's jumpy and glitchy. I might need to treat jointed objects a little differently than objects you pick up. But it's basically working, and I am confident with more testing and refinement it will turn into a polished and fun game mechanic.
</p>

<p>
	The last step was to save my assembly as a new prefab, now that I am pretty sure it's done.
</p>

<p>
	<img alt="image.png.a046fa6feb8bf50ef08b97276acb4a6f.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22463" data-ratio="76.54" style="height:auto;" width="358" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.a046fa6feb8bf50ef08b97276acb4a6f.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	Now I can place an infinite number of file cabinets, with an infinite number of interactive drawers, and force the player to search through them all! The true horror of the SCP beauracracy awaits you! Bwahahahahahaha!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="22464" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.png.a0aef7b339d5843ef5757fa8fe8014df.png" rel=""><img alt="image.thumb.png.178f3f36a5bac044f95ee89a8d8e5dd9.png" class="ipsImage ipsImage_thumbnailed" data-fileid="22464" data-ratio="47.07" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_08/image.thumb.png.178f3f36a5bac044f95ee89a8d8e5dd9.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>
]]></description><guid isPermaLink="false">2880</guid><pubDate>Fri, 15 Aug 2025 16:30:27 +0000</pubDate></item><item><title>Experiments with Intel Embree Raytracing</title><link>https://www.leadwerks.com/community/blogs/entry/2871-experiments-with-intel-embree-raytracing/</link><description><![CDATA[<p>
	I've added an implementation of <a href="https://www.embree.org" rel="external nofollow">Intel's Embree</a> raytracing library. The first step is to just expose a simple low-level library for use with C++. I have many ideas that can be built off this core functionality. Being able to trace millions of rays per second could be used to calculate ambient occlusion, some form of dynamic global illumination, or just batched asynchronous raycasts.
</p>

<p>
	As with all highly optimized code, the ray tracing commands are not quite as user-friendly as the rest of our API, but I will walk you through the steps to setting up a simple ray tracing test.
</p>

<p>
	<span style="font-size:18px;">Getting Started</span>
</p>

<p>
	The first step is to create a ray trace device and a ray tracing scene:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Create raytrace device    </span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> rt </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRayTracer</span><span class="pun">();</span><span class="pln">

</span><span class="com">// Create RT scene</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> scene </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTScene</span><span class="pun">(</span><span class="pln">rt</span><span class="pun">);</span></pre>

<p>
	A ray tracing scene is different from a Leadwerks Scene object. You can add mesh data directly to a scene:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">//Create the scene ground</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> ground </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBox</span><span class="pun">(</span><span class="pln">NULL</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> rtground </span><span class="pun">=</span><span class="pln"> scene</span><span class="pun">-&gt;</span><span class="typ">AddMesh</span><span class="pun">(</span><span class="pln">ground</span><span class="pun">-&gt;</span><span class="typ">GetMesh</span><span class="pun">(</span><span class="lit">0</span><span class="pun">));</span></pre>

<p>
	<span style="font-size:18px;">Instanced Geometry</span>
</p>

<p>
	Ray tracing scenes do not have any concept of entities like Leadwerks does. Any mesh you add to a scene just uses the mesh vertex positions without any transformation. However, you can make an instance of a scene, add it as a child of another scene, and apply a transform matrix to adjust its position and orientation.
</p>

<p>
	Let's make a scene just to act as a container for a mesh we plan on making multiple instances of:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Create RT scene that will be used as a container for the box mesh</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> meshcontainer </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTScene</span><span class="pun">(</span><span class="pln">rt</span><span class="pun">);</span><span class="pln">

</span><span class="com">//Create a box mesh and add it to the container scene</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> box </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBox</span><span class="pun">(</span><span class="pln">NULL</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> rtmesh </span><span class="pun">=</span><span class="pln"> meshcontainer</span><span class="pun">-&gt;</span><span class="typ">AddMesh</span><span class="pun">(</span><span class="pln">box</span><span class="pun">-&gt;</span><span class="typ">GetMesh</span><span class="pun">(</span><span class="lit">0</span><span class="pun">));</span></pre>

<p>
	And now let's create an instance of the mesh container scene in the first scene we created:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Create instances of the box scene and add them to the parent scene</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> A </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTInstance</span><span class="pun">(</span><span class="pln">scene</span><span class="pun">,</span><span class="pln"> meshcontainer</span><span class="pun">);</span></pre>

<p>
	We can now reposition instance A like so. We'll move it one meter to the left, and up 0.1 meters (half the ground height) plus 0.5 (half the box height):
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="pln">A</span><span class="pun">-&gt;</span><span class="typ">SetPosition</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span></pre>

<p>
	Now let's create a second instance and move it one meter to the right:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Create a second instance</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> B </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTInstance</span><span class="pun">(</span><span class="pln">scene</span><span class="pun">,</span><span class="pln"> meshcontainer</span><span class="pun">);</span><span class="pln">
B</span><span class="pun">-&gt;</span><span class="typ">SetPosition</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span></pre>

<p>
	<span style="font-size:18px;">Tracing Rays</span>
</p>

<p>
	Now we have a scene with ground and two boxes, and we are ready to trace rays. Our ray tracing code is hyperoptimized, which means we need to feed it data in the exact format it wants. Instead of providing two points for the raycast, we are going to provide a starting point, a normalized direction vector, and a length for the direction vector.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Define a ray</span><span class="pln">
</span><span class="typ">RTRay</span><span class="pln"> ray</span><span class="pun">;</span><span class="pln">
ray</span><span class="pun">.</span><span class="pln">origin </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vec3</span><span class="pun">(-</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
ray</span><span class="pun">.</span><span class="pln">dir </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vec3</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
ray</span><span class="pun">.</span><span class="pln">length </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20.0f</span><span class="pun">;</span><span class="pln">

</span><span class="com">// Perform single ray trace</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> rayinfo </span><span class="pun">=</span><span class="pln"> scene</span><span class="pun">-&gt;</span><span class="typ">TraceRay</span><span class="pun">(</span><span class="pln">ray</span><span class="pun">);</span></pre>

<p>
	The returned rayinfo structure will give us information about the ray. The structure uses IDs to tell us what was hit.
</p>

<ul>
	<li>
		If the mesh ID equals RT_INVALID_ID then nothing was hit, otherwise something was hit.
	</li>
	<li>
		If the instance ID equals RT_INVALID_ID, then a mesh added directly to the scene was hit, and we can find it by the mesh ID.
	</li>
	<li>
		If the instance ID it not RT_INVALID_ID, then an instance of another scene that was added to this scene was hit.
	</li>
	<li>
		Note that RT_INVALID_GEOMETRY_ID does not equal zero, so a mesh ID of 0 indicates a valid mesh.
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Print out the ray hit information</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">meshid </span><span class="pun">==</span><span class="pln"> RT_INVALID_ID</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Ray missed"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">else</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Hit position: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> std</span><span class="pun">::</span><span class="pln">wstring</span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">position</span><span class="pun">));</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid </span><span class="pun">!=</span><span class="pln"> RT_INVALID_ID</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid </span><span class="pun">==</span><span class="pln"> A</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"A hit ("</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">")"</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid </span><span class="pun">==</span><span class="pln"> B</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"B hit ("</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">")"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">meshid </span><span class="pun">==</span><span class="pln"> rtground</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Ground hit ("</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">rtground</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">")"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	This is more complicated than the simpler Leadwerks raycasting commands, but this approach will also scale better when performing hundreds of thousands of raycasts.
</p>

<p>
	Here is the complete code for the ray trace test program:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">#include</span><span class="pln"> </span><span class="str">"Leadwerks.h"</span><span class="pln">

</span><span class="kwd">using</span><span class="pln"> </span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">UltraEngine</span><span class="pun">;</span><span class="pln">

</span><span class="typ">int</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> argc</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pun">*</span><span class="pln"> argv</span><span class="pun">[])</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="com">// Create raytrace device    </span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> rt </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRayTracer</span><span class="pun">();</span><span class="pln">

    </span><span class="com">// Create RT scene</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> scene </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTScene</span><span class="pun">(</span><span class="pln">rt</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Create the scene ground</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> ground </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBox</span><span class="pun">(</span><span class="pln">NULL</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> rtground </span><span class="pun">=</span><span class="pln"> scene</span><span class="pun">-&gt;</span><span class="typ">AddMesh</span><span class="pun">(</span><span class="pln">ground</span><span class="pun">-&gt;</span><span class="typ">GetMesh</span><span class="pun">(</span><span class="lit">0</span><span class="pun">));</span><span class="pln">

    </span><span class="com">// Create RT scene that will be used as a container for the box mesh</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> meshcontainer </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTScene</span><span class="pun">(</span><span class="pln">rt</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Create a box mesh and add it to the container scene</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> box </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBox</span><span class="pun">(</span><span class="pln">NULL</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> rtmesh </span><span class="pun">=</span><span class="pln"> meshcontainer</span><span class="pun">-&gt;</span><span class="typ">AddMesh</span><span class="pun">(</span><span class="pln">box</span><span class="pun">-&gt;</span><span class="typ">GetMesh</span><span class="pun">(</span><span class="lit">0</span><span class="pun">));</span><span class="pln">
    
    </span><span class="com">// Create instances of the box scene and add them to the parent scene</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> A </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTInstance</span><span class="pun">(</span><span class="pln">scene</span><span class="pun">,</span><span class="pln"> meshcontainer</span><span class="pun">);</span><span class="pln">
    A</span><span class="pun">-&gt;</span><span class="typ">SetPosition</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Create a second instance</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> B </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTInstance</span><span class="pun">(</span><span class="pln">scene</span><span class="pun">,</span><span class="pln"> meshcontainer</span><span class="pun">);</span><span class="pln">
    B</span><span class="pun">-&gt;</span><span class="typ">SetPosition</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
    
    </span><span class="com">// Define a ray</span><span class="pln">
    </span><span class="typ">RTRay</span><span class="pln"> ray</span><span class="pun">;</span><span class="pln">
    ray</span><span class="pun">.</span><span class="pln">origin </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vec3</span><span class="pun">(-</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
    ray</span><span class="pun">.</span><span class="pln">dir </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vec3</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
    ray</span><span class="pun">.</span><span class="pln">length </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20.0f</span><span class="pun">;</span><span class="pln">
    
    </span><span class="com">// Perform single ray trace</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> rttrace </span><span class="pun">=</span><span class="pln"> scene</span><span class="pun">-&gt;</span><span class="typ">TraceRay</span><span class="pun">(</span><span class="pln">ray</span><span class="pun">);</span><span class="pln">
    
    </span><span class="com">// Print out the ray hit information</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">meshid </span><span class="pun">==</span><span class="pln"> RT_INVALID_ID</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Ray missed"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Hit position: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> std</span><span class="pun">::</span><span class="pln">wstring</span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">position</span><span class="pun">));</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid </span><span class="pun">!=</span><span class="pln"> RT_INVALID_ID</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid </span><span class="pun">==</span><span class="pln"> A</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"A hit ("</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">")"</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid </span><span class="pun">==</span><span class="pln"> B</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"B hit ("</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">instid</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">")"</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rttrace</span><span class="pun">.</span><span class="pln">meshid </span><span class="pun">==</span><span class="pln"> rtground</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Ground hit ("</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">rtground</span><span class="pun">-&gt;</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">")"</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	The program will output this text when run, indicating that instance A is the first hit object, along with the position the intersection occurs at.
</p>

<pre class="ipsCode">Hit position: -1.5, 0.6, 0
A hit (1)</pre>

<p>
	<span style="font-size:18px;">Ray Filtering</span>
</p>

<p>
	You can use the instance and mesh filter feature to make a ray that only intersects some geometry. Here we will set a bitwise mask so that instance A gets skipped, and instance B will be hit. Just add this code right before the scene-&gt;TraceRay() call.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Assign obect masks</span><span class="pln">
A</span><span class="pun">-&gt;</span><span class="typ">SetMask</span><span class="pun">(</span><span class="lit">1</span><span class="pun">);</span><span class="pln">
B</span><span class="pun">-&gt;</span><span class="typ">SetMask</span><span class="pun">(</span><span class="lit">2</span><span class="pun">);</span></pre>

<p>
	And then set an additional parameter in the TraceRay call to define the mask:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">// Perform single ray trace</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> rttrace </span><span class="pun">=</span><span class="pln"> scene</span><span class="pun">-&gt;</span><span class="typ">TraceRay</span><span class="pun">(</span><span class="pln">ray</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span></pre>

<p>
	Filtering will work on both a per-instance and per-mesh level. The default mask value for instances is 1 and the default mask value for raytrace meshes is -1u (all flags).
</p>

<p>
	The output of our modified program shows that instance A is being ignored, and now instance B is the one that gets hit:
</p>

<pre class="ipsCode">Hit position: 0.499999, 0.6, 0
B hit (2)</pre>

<p>
	<span style="font-size:18px;">Advanced Usage</span>
</p>

<p>
	The code below demonstrates a very simple real-time lightmapper using the ray tracing system.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">#include</span><span class="pln"> </span><span class="str">"Leadwerks.h"</span><span class="pln">

</span><span class="kwd">using</span><span class="pln"> </span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">UltraEngine</span><span class="pun">;</span><span class="pln">

</span><span class="typ">int</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> argc</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pun">*</span><span class="pln"> argv</span><span class="pun">[])</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> displays </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetDisplays</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> win </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateWindow</span><span class="pun">(</span><span class="str">""</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1280</span><span class="pun">,</span><span class="pln"> </span><span class="lit">720</span><span class="pun">,</span><span class="pln"> displays</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> fb </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateFramebuffer</span><span class="pun">(</span><span class="pln">win</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> world </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateWorld</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> cam </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateCamera</span><span class="pun">(</span><span class="pln">world</span><span class="pun">);</span><span class="pln">
    cam</span><span class="pun">-&gt;</span><span class="typ">SetClearColor</span><span class="pun">(</span><span class="lit">0.125f</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">auto</span><span class="pln"> ground </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBox</span><span class="pun">(</span><span class="pln">world</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> mtl </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateMaterial</span><span class="pun">();</span><span class="pln">
    ground</span><span class="pun">-&gt;</span><span class="typ">SetMaterial</span><span class="pun">(</span><span class="pln">mtl</span><span class="pun">);</span><span class="pln">
    
    </span><span class="typ">int</span><span class="pln"> sz </span><span class="pun">=</span><span class="pln"> </span><span class="lit">128</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> pixmap </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreatePixmap</span><span class="pun">(</span><span class="pln">sz</span><span class="pun">,</span><span class="pln"> sz</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> tex </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateTexture</span><span class="pun">(</span><span class="pln">TEXTURE_2D</span><span class="pun">,</span><span class="pln"> sz</span><span class="pun">,</span><span class="pln"> sz</span><span class="pun">);</span><span class="pln">
    mtl</span><span class="pun">-&gt;</span><span class="typ">SetTexture</span><span class="pun">(</span><span class="pln">tex</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">auto</span><span class="pln"> caster </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBox</span><span class="pun">(</span><span class="pln">world</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">
    caster</span><span class="pun">-&gt;</span><span class="typ">SetPosition</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
    caster</span><span class="pun">-&gt;</span><span class="typ">SetColor</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">

    cam</span><span class="pun">-&gt;</span><span class="typ">SetPosition</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">5</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">auto</span><span class="pln"> rt </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRayTracer</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> rts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTScene</span><span class="pun">(</span><span class="pln">rt</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> base </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTScene</span><span class="pun">(</span><span class="pln">rt</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> rtm </span><span class="pun">=</span><span class="pln"> base</span><span class="pun">-&gt;</span><span class="typ">AddMesh</span><span class="pun">(</span><span class="pln">caster</span><span class="pun">-&gt;</span><span class="typ">GetMesh</span><span class="pun">(</span><span class="lit">0</span><span class="pun">));</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> rti </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateRTInstance</span><span class="pun">(</span><span class="pln">rts</span><span class="pun">,</span><span class="pln"> base</span><span class="pun">);</span><span class="pln">

    </span><span class="typ">RTRay</span><span class="pln"> ray</span><span class="pun">;</span><span class="pln">
    ray</span><span class="pun">.</span><span class="pln">dir </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Vec3</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
    ray</span><span class="pun">.</span><span class="pln">length </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">not win</span><span class="pun">-&gt;</span><span class="typ">KeyHit</span><span class="pun">(</span><span class="pln">KEY_ESCAPE</span><span class="pun">)</span><span class="pln"> and not win</span><span class="pun">-&gt;</span><span class="typ">Closed</span><span class="pun">())</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        caster</span><span class="pun">-&gt;</span><span class="typ">Turn</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
        rti</span><span class="pun">-&gt;</span><span class="typ">SetMatrix</span><span class="pun">(</span><span class="pln">caster</span><span class="pun">-&gt;</span><span class="pln">matrix</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">unsigned</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> color</span><span class="pun">;</span><span class="pln">

        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> sz</span><span class="pun">;</span><span class="pln"> </span><span class="pun">++</span><span class="pln">x</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> y </span><span class="pun">&lt;</span><span class="pln"> sz</span><span class="pun">;</span><span class="pln"> </span><span class="pun">++</span><span class="pln">y</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">{</span><span class="pln">
                ray</span><span class="pun">.</span><span class="pln">origin</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">float</span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="typ">float</span><span class="pun">(</span><span class="pln">sz</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="typ">float</span><span class="pun">(</span><span class="pln">sz</span><span class="pun">))</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">10.0f</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">5.0f</span><span class="pun">;</span><span class="pln">
                ray</span><span class="pun">.</span><span class="pln">origin</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pun">;</span><span class="pln">
                ray</span><span class="pun">.</span><span class="pln">origin</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-((</span><span class="typ">float</span><span class="pun">(</span><span class="pln">y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="typ">float</span><span class="pun">(</span><span class="pln">sz</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">0.5f</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="typ">float</span><span class="pun">(</span><span class="pln">sz</span><span class="pun">))</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">10.0f</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">5.0f</span><span class="pun">);</span><span class="pln">

                </span><span class="kwd">auto</span><span class="pln"> rayinfo </span><span class="pun">=</span><span class="pln"> rts</span><span class="pun">-&gt;</span><span class="typ">TraceRay</span><span class="pun">(</span><span class="pln">ray</span><span class="pun">);</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rayinfo</span><span class="pun">.</span><span class="pln">meshid </span><span class="pun">!=</span><span class="pln"> RT_INVALID_ID</span><span class="pun">)</span><span class="pln">
                </span><span class="pun">{</span><span class="pln">
                    color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Rgba</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">);</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                </span><span class="kwd">else</span><span class="pln">
                </span><span class="pun">{</span><span class="pln">
                    color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Rgba</span><span class="pun">(</span><span class="lit">255</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">);</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                pixmap</span><span class="pun">-&gt;</span><span class="typ">WritePixel</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        tex</span><span class="pun">-&gt;</span><span class="typ">SetPixels</span><span class="pun">(</span><span class="pln">pixmap</span><span class="pun">);</span><span class="pln">

        world</span><span class="pun">-&gt;</span><span class="typ">Update</span><span class="pun">();</span><span class="pln">
        world</span><span class="pun">-&gt;</span><span class="typ">Render</span><span class="pun">(</span><span class="pln">fb</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	There's a lot that could be done to improve this basic implementation, but the idea works nicely.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="21906" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_04/Untitled.jpg.844dd682ed3cb0492644d2b2a2e78832.jpg" rel=""><img alt="Untitled.thumb.jpg.37fdd5bc1144ac165e59f897280ca711.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="21906" data-ratio="58.67" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_04/Untitled.thumb.jpg.37fdd5bc1144ac165e59f897280ca711.jpg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Substituting the spinning box a more complex model, we can see the routine does not seem to be bothered much by mesh complexity. This model is over 100,000 polygons, and a 512x512 lightmap is updating in 30 milliseconds with one CPU core and unbatched raycasts.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21908" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_04/image.png.bc115940f239569c0f8b34df67802b29.png" rel=""><img alt="image.thumb.png.dcb9baf40ad503ee3527a8095fc9fc20.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21908" data-ratio="55.07" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_04/image.thumb.png.dcb9baf40ad503ee3527a8095fc9fc20.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Here it is in motion:
</p>

<div class="ipsEmbeddedVideo" contenteditable="false">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" title="Intel Embree Raytracing Test" width="200" data-embed-src="https://www.youtube-nocookie.com/embed/MMSkEu4qATg?feature=oembed"></iframe>
	</div>
</div>

<p>
	Here is an updated test using shadow maps for direct lighting and CPU ray tracing for ambient occlusion:
</p>

<div class="ipsEmbeddedVideo" contenteditable="false">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" title="Raytraced lightmap combined with shadow map" width="200" data-embed-src="https://www.youtube-nocookie.com/embed/yAjWRRR51ro?feature=oembed"></iframe>
	</div>
</div>
]]></description><guid isPermaLink="false">2871</guid><pubDate>Sun, 13 Apr 2025 21:07:36 +0000</pubDate></item><item><title>Introducing Leadwerks IDE</title><link>https://www.leadwerks.com/community/blogs/entry/2869-introducing-leadwerks-ide/</link><description><![CDATA[<p>
	I don't write a lot of blogs anymore because most of our communication takes place through our weekly live chats <a href="https://www.leadwerks.com/discord" rel="external nofollow">on Discord</a>. However, when there is a topic that I want to explain in detail, blogs are a great format for this.
</p>

<p>
	The original idea in Leadwerks 5 was to integrate Microsoft's Visual Studio Code as the IDE for the engine, rather than using the somewhat clunky built-in IDE we had in Leadwerks 4.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21748" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.png.e9a3732d02a48b6b0e63cd0a65bc6763.png" rel=""><img alt="image.thumb.png.7fd7493a99e0711839c849ed2a9defdc.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21748" data-ratio="75.07" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.thumb.png.7fd7493a99e0711839c849ed2a9defdc.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a><br />
	<span style="font-size:12px;"><em>My eyes!</em></span>
</p>

<p>
	<a href="https://code.visualstudio.com" rel="external nofollow">Visual Studio Code</a> was the hot new text editor at the time, with cross-platform support and a dark theme that blended well with our new editor. A robust extensions system provided support for integrations with many languages including GLSL and Lua. There was even a <a href="https://marketplace.visualstudio.com/items?itemName=devCAT.lua-debug" rel="external nofollow">Lua debugger</a> with advanced features that seemed to turn Lua into a first-class programming language.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21763" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/Untitled.png.b026d47358759a382a5cd25ce27d3c8b.png" rel=""><img alt="Untitled.thumb.png.9b63109e8fd05c26b38cdcb0f21fbdbf.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21763" data-ratio="21.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/Untitled.thumb.png.9b63109e8fd05c26b38cdcb0f21fbdbf.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	However, outsourcing a significant part of our user experience to a third party application is not without disadvantages. One day I was looking at some old Leadwerks 4 tutorials on Lua basics, and the simplicity of the debug interface really jumped out at me. This got me thinking about the whole user experience for new Lua developers.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="21764" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.jpeg.d1d1833c6c0a14f14ab5ed5b6dedc98f.jpeg" rel=""><img alt="image.thumb.jpeg.5851f61cce49716e825b9f37a060667d.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="21764" data-ratio="80.13" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.thumb.jpeg.5851f61cce49716e825b9f37a060667d.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	<span style="font-size:18px;">Installation</span>
</p>

<p>
	The setup process for Visual Studio Code with Leadwerks 5 is not as streamlined as I would like.
</p>

<ol>
	<li>
		Find and install Visual Studio Code (not to be confused with Microsoft Visual Studio).
	</li>
	<li>
		Install the DevCat Lua Debugger extension from the Visual Studio Code marketplace (not any of the other hundreds of search results that appear when you search for this, and definitely not the <a href="https://marketplace.visualstudio.com/items?itemName=actboy168.lua-debug" rel="external nofollow">actboy168 Lua debugger</a> that will make your game crash).
	</li>
	<li>
		Navigate four levels deep in the program settings to find and enable the "Allow Breakpoints Everywhere" option. If you don't do this, or if the setting gets disabled somehow, the debugger won't work.
	</li>
</ol>

<p>
	The setup process is not too difficult, but I don't want a new user's first impression to be "Welcome to Leadwerks 5, the first thing you need to do is go install this other program and some weird things you've never heard of".
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21749" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.png.48f4f16959a1aadbf97da867d8767eea.png" rel=""><img alt="image.thumb.png.20754ed559f4f7f6bbbc4ca754764a7d.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21749" data-ratio="334.82" style="height:auto;" width="224" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.thumb.png.20754ed559f4f7f6bbbc4ca754764a7d.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a><br />
	<em><span style="font-size:12px;">Welcome to...Leadwerks?</span></em>
</p>

<p>
	The alternative to this is to just provide a build-in IDE (Integrated Development Environment) as part of the level editor. This completely eliminates the need to install anything extra. Visual Studio Code can still be used, and you can choose how Lua files should be opened when you double-click them in the project browser. Three options are provided in the program settings:
</p>

<ul>
	<li>
		Open in IDE
	</li>
	<li>
		Open in Visual Studio Code (also opens the current folder as a project)
	</li>
	<li>
		System Default
	</li>
</ul>

<p>
	It was not too difficult to get working, and after one week and 1500 lines of code I have a nearly-completed IDE working. The text editor is based on <a contenteditable="false" data-ipshover="" data-ipshover-target="https://www.leadwerks.com/community/profile/4-klepto2/?do=hovercard" data-mentionid="4" href="https://www.leadwerks.com/community/profile/4-klepto2/" rel="">@klepto2</a>'s excellent Scintilla widget, which he has generously made available for free <a href="https://github.com/klepto2/UltraEngineUtilities" rel="external nofollow">on Github</a> under the MIT license.
</p>

<p>
	<span style="font-size:18px;">Error Handling</span>
</p>

<p>
	When a coding error is encountered, the Visual Studio console dispays a rather verbose message.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21758" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.png.eece96726e9eef53c86c2d25f8ae06a7.png" rel=""><img alt="image.thumb.png.c40f8f3e3a907c106960277ee28b6687.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21758" data-ratio="59.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.thumb.png.c40f8f3e3a907c106960277ee28b6687.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	The built-in IDE displays a clearly highlighted error message that tells you what the problem is. I even coded it to convert the first letter in the error message to uppercase, just to make it look a little nicer.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21759" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.png.d8ee66761ac76e945fbba30c2f259f33.png" rel=""><img alt="image.thumb.png.7f7d4bece1efb286563b1158ff98e2dc.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21759" data-ratio="62.67" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.thumb.png.7f7d4bece1efb286563b1158ff98e2dc.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Both interfaces contain the same information, but with the built-in IDE you spend a lot less time scanning to find what you're looking for...which is a really good thing for an interface that gets heavily used!
</p>

<p>
	<span style="font-size:18px;">Debugging</span>
</p>

<p>
	Visual Studio Code has some advanced features like hovering over a word in the text editor to display the value. However, the whole interface is flooded with Visual clutter. Type names include their namespace and things like shared_ptr, which is an underlying detail that is irrelavant to Lua.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21760" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.png.c1a7a93a757d00ee0c35e49daf0c2acc.png" rel=""><img alt="image.thumb.png.284def55acd09450b49b15eab6a48dc4.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21760" data-ratio="59.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.thumb.png.284def55acd09450b49b15eab6a48dc4.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	The built-in IDE provides a more clear and concise view, with simplified type names and color-coded icons. It even displays an icon for each type of entity.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21761" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.png.56aab53ab158181abc9fc4c83d538913.png" rel=""><img alt="image.thumb.png.cbd9a6eee353b10217668023738f7233.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21761" data-ratio="62.67" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/image.thumb.png.cbd9a6eee353b10217668023738f7233.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	<span style="font-size:18px;">Window Management</span>
</p>

<p>
	Since Visual Studio Code is an external program, it does not stay on top of the editor window. To select the main editor window, VSCode, or game window, you need to click on their icons in the Windows taskbar or use Alt+Tab to cycle through windows.
</p>

<p>
	When the built-in IDE is used, by default, the game window appears on top of the editor when the game runs.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21753" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/b.png.d9dc26f1466f09120341fd7e3774aa2f.png" rel=""><img alt="b.thumb.png.1e46bc7f776b99a03e1fe2ba837028a3.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21753" data-ratio="56.27" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/b.thumb.png.1e46bc7f776b99a03e1fe2ba837028a3.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	With some clever Win32 programming, we can retrieve the game window from the running game's process handle. By responding to WINDOWSELECT and WINDOWDESELECT events, we can adjust the z-order of the game and editor windows to make the game and IDE act like they are both siblings parented to the main window. Whichever window is selected will appear on top, but both windows always stay on top of the main editor window.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21752" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/a.png.730acc7b2db6efb3cccda4921e343cdf.png" rel=""><img alt="a.thumb.png.8cc3d8246332395e94fefbabe2f0a58a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21752" data-ratio="56.27" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/a.thumb.png.8cc3d8246332395e94fefbabe2f0a58a.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	If the IDE window is maximized, we can detect this event and then change the game window parent to the IDE window itself. This allows you to use the debugger in fullscreen, while the game remains on top.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21756" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/c.png.17975b0d22f69148324eb1ef48ae94ec.png" rel=""><img alt="c.thumb.png.1947e6d38f1591ac367c970b319e322d.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21756" data-ratio="56.27" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/c.thumb.png.1947e6d38f1591ac367c970b319e322d.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	With this design you will spend less time hunting for icons in the Windows taskbar to switch windows back and forth, and more time working on your game.
</p>

<p>
	Finally, we are starting to see bloat creep into the Visual Studio Code user experience and it's no longer the light and fast text editor it began as.
</p>

<p>
	<img alt="bloat.png.3d8f22ce2082c6bace191155bccfc0c5.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21769" data-ratio="74.28" style="height:auto;" width="521" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/bloat.png.3d8f22ce2082c6bace191155bccfc0c5.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	<img alt="bloat3.png.dddd4a9859b709fe8fe5e6357e80c8ae.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21771" data-ratio="23.85" style="height:auto;" width="457" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/bloat3.png.dddd4a9859b709fe8fe5e6357e80c8ae.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	<img alt="bloat2.png.2ef40afb65f79f6e66f71439bab957a6.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21770" data-ratio="3.87" style="height:auto;" width="672" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/bloat2.png.2ef40afb65f79f6e66f71439bab957a6.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/bloat4.png.f5a65724a07fe8d3b1ef03ff31e13504.png" data-fileid="21772" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="21772" data-ratio="56.40" width="750" alt="bloat4.thumb.png.b7f4843e0fff7250b768caae3fbf2b18.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2025_03/bloat4.thumb.png.b7f4843e0fff7250b768caae3fbf2b18.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	Overall, the built-in IDE provides a smoother workflow with a much more focused presentation that highlights the information you are looking for with a lot less visual clutter. While the Visual Studio Code debugger will remain for those who wish to continue using it, I can already see that the built-in IDE will provide a massive boost in productivity to all our Lua developers, and a much improved user experience for new users.
</p>
]]></description><guid isPermaLink="false">2869</guid><pubDate>Fri, 28 Mar 2025 14:58:00 +0000</pubDate></item><item><title>Bodycam-style Movement Experiment</title><link>https://www.leadwerks.com/community/blogs/entry/2857-bodycam-style-movement-experiment/</link><description><![CDATA[<p>
	I am a fan of the recent bodycam-style movement videos that have come out. It's an idea I have had before, and then seeing it working was very cool. Today I spent a little time trying out my own implementation to see how well it would work.
</p>

<p>
	My idea was to use my VR headset as a mocap system to record myself walking and get the headset orientation for a few seconds, then play that back in a loop when the first-person player moves around. I started with a simple scene. The strip on the ground gave me a clear indication of direction so I knew which way to walk.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21362" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_12/image.png.3df94a7487c9f2548b97253ceb86c072.png" rel=""><img alt="image.thumb.png.213c1657de8c551c36fb0e0bd2e3098f.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21362" data-ratio="49.47" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_12/image.thumb.png.213c1657de8c551c36fb0e0bd2e3098f.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	The walkway is oriented along the X axis because the room I am using is wider than it is long!
</p>

<p>
	Next I made a simple program that would just record the headset orientation for a few frames. I used the trigger button to control when recording occurs. The recorded data gets saved to mocap.json when the program ends. It's very easy to accidentally overwrite this file, so once you get a good recording, it's a good idea to just copy and paste the file in case you overwrite it accidentally.
</p>

<p>
	Once I had the mocap data, I modified the FPSPlayer so it would get loaded and displayed.
</p>

<p>
	I added two global variables at the top of the file:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="pln">table mocapdata</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> mocapindex </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></pre>

<p>
	Then I added some code to load the mocap data in the Start method:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">void</span><span class="pln"> </span><span class="typ">FPSPlayer</span><span class="pun">::</span><span class="typ">Start</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
	mocapdata </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadTable</span><span class="pun">(</span><span class="str">"mocap.json"</span><span class="pun">);</span></pre>

<p>
	Next I added this function. It's a function, not a class method, but I was just trying to get things working as fast as possible so I could see the result:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">void</span><span class="pln"> </span><span class="typ">UpdateBobble</span><span class="pun">(</span><span class="pln">shared_ptr</span><span class="pun">&lt;</span><span class="typ">Entity</span><span class="pun">&gt;</span><span class="pln"> camera</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
	</span><span class="typ">int</span><span class="pln"> count </span><span class="pun">=</span><span class="pln"> mocapdata</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln">
	mocapindex </span><span class="pun">=</span><span class="pln"> mocapindex </span><span class="pun">%</span><span class="pln"> count</span><span class="pun">;</span><span class="pln">
	</span><span class="typ">Vec3</span><span class="pln"> offset</span><span class="pun">;</span><span class="pln">
	offset</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> mocapdata</span><span class="pun">[</span><span class="pln">mocapindex</span><span class="pun">][</span><span class="str">"position"</span><span class="pun">][</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
	offset</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> mocapdata</span><span class="pun">[</span><span class="pln">mocapindex</span><span class="pun">][</span><span class="str">"position"</span><span class="pun">][</span><span class="lit">1</span><span class="pun">];</span><span class="pln">
	offset</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1.65f</span><span class="pun">;</span><span class="pln">
	offset</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="com">// mocapdata[mocapindex]["position"][2];</span><span class="pln">
	camera</span><span class="pun">-&gt;</span><span class="typ">Move</span><span class="pun">(</span><span class="pln">offset</span><span class="pun">);</span><span class="pln">

	</span><span class="typ">float</span><span class="pln"> pitch </span><span class="pun">=</span><span class="pln"> camera</span><span class="pun">-&gt;</span><span class="pln">rotation</span><span class="pun">.</span><span class="pln">x</span><span class="pun">;</span><span class="pln">
	camera</span><span class="pun">-&gt;</span><span class="typ">SetRotation</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> camera</span><span class="pun">-&gt;</span><span class="pln">rotation</span><span class="pun">.</span><span class="pln">y</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">

	offset</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="com">// mocapdata[mocapindex]["rotation"][2];</span><span class="pln">
	offset</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> </span><span class="typ">float</span><span class="pun">(</span><span class="pln">mocapdata</span><span class="pun">[</span><span class="pln">mocapindex</span><span class="pun">][</span><span class="str">"rotation"</span><span class="pun">][</span><span class="lit">1</span><span class="pun">])</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">90.0f</span><span class="pun">;</span><span class="pln">
	offset</span><span class="pun">.</span><span class="pln">z </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="com">// mocapdata[mocapindex]["rotation"][0];</span><span class="pln">
	camera</span><span class="pun">-&gt;</span><span class="typ">Turn</span><span class="pun">(</span><span class="pln">offset</span><span class="pun">);</span><span class="pln">

	camera</span><span class="pun">-&gt;</span><span class="typ">Turn</span><span class="pun">(</span><span class="pln">pitch</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">


	</span><span class="pun">++</span><span class="pln">mocapindex</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	Next I created another scene with an FPSPlayer and a long textured hallway to walk down.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="21363" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_12/image.png.ef0368a2d05beda4fd65cf3163cc300e.png" rel=""><img alt="image.thumb.png.e4dc3f49882b0ce8c662d268a40d4fdd.png" class="ipsImage ipsImage_thumbnailed" data-fileid="21363" data-ratio="45.20" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_12/image.thumb.png.e4dc3f49882b0ce8c662d268a40d4fdd.png" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	I was worried that my head movements might be too subtle to show up in first-person player view, but it turns out I had nothing to worry about. Just some simple code produced the approximate effect I was hoping to see.
</p>

<div class="ipsEmbeddedVideo">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="150" src="https://www.youtube-nocookie.com/embed/Nt3zyKaVEeQ?feature=oembed" title="Body-cam movement proof of concept" width="200"></iframe>
	</div>
</div>

<p>
	<span style="background-color:#1e2122;color:#e8e6e3;font-size:15px;">The technique basically works, but could be improved with more recordings in a bigger space, blending the start and the end of the loop to prevent the jump, and smoothing the camera movement a bit. Of course, you would want to footstep sounds to be timed to the footsteps in the mocap data. Still, the initial results are pretty good and I am encouraged to experiment with this more.</span>
</p>

<p>
	<span style="background-color:#1e2122;color:#e8e6e3;font-size:15px;">Here are the files I used. In main.cpp I added a definition for RECORDMODE, which controls whether the application is being run to record movement or play it back.</span>
</p>
<p>
<a class="ipsAttachLink" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=21364&amp;key=346a98c884b1193f614f1f5394d37917" data-fileExt='cpp' data-fileid='21364' data-filekey='346a98c884b1193f614f1f5394d37917'>FPSPlayer.cpp</a> 
<a class="ipsAttachLink" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=21365&amp;key=7d6930e49b1f41699826fe2cd4109d0f" data-fileExt='cpp' data-fileid='21365' data-filekey='7d6930e49b1f41699826fe2cd4109d0f'>main.cpp</a> 
<a class="ipsAttachLink" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=21366&amp;key=361d52526ce7839989524a0829dc4e6b" data-fileExt='zip' data-fileid='21366' data-filekey='361d52526ce7839989524a0829dc4e6b'>Maps.zip</a> 
<a class="ipsAttachLink" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=21367&amp;key=37886dc7f37eb868b9732b426d831fb6" data-fileExt='json' data-fileid='21367' data-filekey='37886dc7f37eb868b9732b426d831fb6'>mocap.json</a></p>]]></description><guid isPermaLink="false">2857</guid><pubDate>Tue, 24 Dec 2024 19:26:00 +0000</pubDate></item><item><title>sRGB and Linear Color Spaces</title><link>https://www.leadwerks.com/community/blogs/entry/2844-srgb-and-linear-color-spaces/</link><description><![CDATA[<p>
	Recently I came across a problem where HDRI images I loaded and converted into cube maps looked simultaneously too bright and too dark.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="20443" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.jpeg.dcc43a3be6185be2264f442988b18856.jpeg" rel=""><img alt="image.thumb.jpeg.9172bbe46dcb4873bdda313bae79290a.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="20443" data-ratio="52.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.thumb.jpeg.9172bbe46dcb4873bdda313bae79290a.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	I found that a simple linear to sRGB conversion made the images look as I would expect.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="20444" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.jpeg.45d66d59c18cf6f3a6663cc736834313.jpeg" rel=""><img alt="image.thumb.jpeg.b4a1594c70d7a44c244484d274a7fce8.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="20444" data-ratio="52.27" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.thumb.jpeg.b4a1594c70d7a44c244484d274a7fce8.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	I have read about sRGB and linear color spaces before, but for some reason this time it just clicked and I understood. Maybe since I am not longer dealing with low-level Vulkan nonsense, I have extra mental capacity to spend on more high-level concepts like this. In any case, I find this stuff is usually not explained very well and I want to show you how it works, in simple terms everyone can understand.
</p>

<p>
	The basic idea of sRGB color space is that the way we perceive lightness in an image is wrong. Look at the gradient below. The change in the left-most 25% of the image looks much greater than the change in the right-most 25%, but it's not:
</p>

<p>
	<img alt="image.png.54b6a9f347c9aad6f4b128f51cbebc81.png" class="ipsImage ipsImage_thumbnailed" data-fileid="20445" data-ratio="6.25" style="height:auto;" width="256" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.png.54b6a9f347c9aad6f4b128f51cbebc81.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	A long time ago, photography folks all agreed that these types of gradients look wrong. You will often see this referred to as <a href="https://blog.solidsignal.com/tutorials/crushed-blacks-bad/" rel="external nofollow">crushed blacks</a>, the opposite of overexposed bright colors, and it's usually considered to be a bad thing:
</p>

<p>
	<img alt="image.png.6f8a14c2fb2ac40d59aea2533a991899.png" class="ipsImage ipsImage_thumbnailed" data-fileid="20448" data-ratio="58.17" style="height:auto;" width="600" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.png.6f8a14c2fb2ac40d59aea2533a991899.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	In 1996 <a href="https://en.wikipedia.org/wiki/SRGB" rel="external nofollow">HP and Microsoft</a> came up with an equation that makes a gradient that looks "right". This is called sRGB color space, and it just shifts the darker tones to the right with a simple equation. The resulting gradient below looks more "balanced", even though it's really <em>unbalanced:</em>
</p>

<p>
	<img alt="image.png.2ce5cbda86433bf20d778f28b2288a4c.png" class="ipsImage ipsImage_thumbnailed" data-fileid="20447" data-ratio="6.25" style="height:auto;" width="256" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.png.2ce5cbda86433bf20d778f28b2288a4c.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	The only problem is that since these colors are stretched, all our nice neat color math is no longer valid. Multiplying two colors together like we do for lighting won't produce the right result because the colors have all been shifted to the right by varying amounts. 
</p>

<p>
	The solution is to <em>unstretch</em> all colors by converting from sRGB to linear, perform your color math like lighting, and then shift the result back from linear to sRGB. Fortunately you don't have to modify your textures and colors, because sRGB is considered the "correct" value that we want to see, and linear color space is a temporary intermediary used for math. We can convert between these color spaces in a shader with the code below.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">const</span><span class="pln"> </span><span class="typ">float</span><span class="pln"> GAMMA </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2.2</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">float</span><span class="pln"> INV_GAMMA </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1.0</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> GAMMA</span><span class="pun">;</span><span class="pln">

vec3 linearTosRGB</span><span class="pun">(</span><span class="pln">vec3 color</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> pow</span><span class="pun">(</span><span class="pln">color</span><span class="pun">,</span><span class="pln"> vec3</span><span class="pun">(</span><span class="pln">INV_GAMMA</span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

vec3 sRGBToLinear</span><span class="pun">(</span><span class="pln">vec3 srgbIn</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> vec3</span><span class="pun">(</span><span class="pln">pow</span><span class="pun">(</span><span class="pln">srgbIn</span><span class="pun">.</span><span class="pln">xyz</span><span class="pun">,</span><span class="pln"> vec3</span><span class="pun">(</span><span class="pln">GAMMA</span><span class="pun">)));</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	Let's take a look at some math to see how light and color interact with this approach:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="typ">float</span><span class="pln"> albedo </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">;</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> light </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.75</span><span class="pun">;</span><span class="pln">
    
</span><span class="typ">float</span><span class="pln"> color </span><span class="pun">=</span><span class="pln"> albedo </span><span class="pun">*</span><span class="pln"> light</span><span class="pun">;</span><span class="pln">
    
</span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Simple lighting: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">color</span><span class="pun">));</span><span class="pln">

albedo </span><span class="pun">=</span><span class="pln"> sRGBToLinear</span><span class="pun">(</span><span class="pln">albedo</span><span class="pun">);</span><span class="pln">
light </span><span class="pun">=</span><span class="pln"> sRGBToLinear</span><span class="pun">(</span><span class="pln">light</span><span class="pun">);</span><span class="pln">

color </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LinearTosRGB</span><span class="pun">(</span><span class="pln">color</span><span class="pun">);</span><span class="pln">

</span><span class="typ">Print</span><span class="pun">(</span><span class="str">"sRGB lighting: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">color</span><span class="pun">));</span></pre>

<p>
	The output of this program shows that converting from sRGB to linear and back results in lighting that doesn't get smashed by the albedo color:
</p>

<pre class="ipsCode">Simple lighting: 0.375
sRGB lighting: 0.640292</pre>

<p>
	What this technique basically does is it reallocates precision away from the bright colors, which we do not have as accurate perception of, and gives it to the dark colors, which we can see between more easily. With standard linear color a simple scene looks both dull and heavily contrasted:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="20441" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.png.932f35e137e1fc4f15c2e8f3ce0b99b9.png" rel=""><img alt="image.thumb.png.72d12c015894a04292b9cbeb12d26875.png" class="ipsImage ipsImage_thumbnailed" data-fileid="20441" data-ratio="72.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/image.thumb.png.72d12c015894a04292b9cbeb12d26875.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	The same scene comes to life when colors are converted from sRGB to linear before lighting, and then transformed back for the final color. We can clearly see a blue tint from the PBR environment map mixed with the orange color, and the dark gray colors have much more definition now. I don't even understand how something can be blue and orange at the same time, but I can clearly see a blue reflection from the sky in the image below.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="20442" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/srgb.png.8fab2c8b17122d440975cda4da14c172.png" rel=""><img alt="srgb.thumb.png.6481c001c21a23d05100df47b99add48.png" class="ipsImage ipsImage_thumbnailed" data-fileid="20442" data-ratio="64.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2024_05/srgb.thumb.png.6481c001c21a23d05100df47b99add48.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	This operation doesn't change the colors, it just changes the way that colors combine. Overall this makes it easier for lights to illuminate dark areas without requiring extremely bright lights with strong contrast, and the PBR lighting is more strongly visible in colored objects. This update is available now on the beta branch, in the standalone and Steam versions of Ultra Engine.
</p>

<p>
	<span> </span>I used the Lua code below to create the gradient images. You can paste this into a Lua file and run it by selecting the <strong>Script &gt; Run Script </strong>item in the main menu, or you can just paste this into the console and press enter, and it will run:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">local</span><span class="pln"> pixmap </span><span class="pun">=</span><span class="pln"> CreatePixmap</span><span class="pun">(</span><span class="lit">256</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">)</span><span class="pln">

</span><span class="com">--Write linear gradient</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="kwd">do</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="kwd">do</span><span class="pln">
        pixmap</span><span class="pun">:</span><span class="pln">WritePixel</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> Rgba</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
</span><span class="kwd">end</span><span class="pln">
pixmap</span><span class="pun">:</span><span class="pln">Save</span><span class="pun">(</span><span class="pln">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)..</span><span class="str">"/lineargradient.png"</span><span class="pun">)</span><span class="pln">

</span><span class="com">--Convert to sRGB</span><span class="pln">
pixmap </span><span class="pun">=</span><span class="pln"> pixmap</span><span class="pun">:</span><span class="pln">LinearTosRgb</span><span class="pun">()</span><span class="pln">
pixmap</span><span class="pun">:</span><span class="pln">Save</span><span class="pun">(</span><span class="pln">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)..</span><span class="str">"/srgbgradient.png"</span><span class="pun">)</span></pre>

<p>
	I hope this explanation helps you to understand what sRGB and linear color spaces actually do and how this feature makes your games look better. It's actually a very straightforward concept that for some reason is poorly explained in most articles I have seen. Just remember, linear is the "squished" color for math, and sRGB is the "stretched" color that's easier to see (sRGB = <s>standard</s> <em>stretched</em> RGB).
</p>
]]></description><guid isPermaLink="false">2844</guid><pubDate>Tue, 28 May 2024 14:18:00 +0000</pubDate></item><item><title>Finishing Touches</title><link>https://www.leadwerks.com/community/blogs/entry/2826-finishing-touches/</link><description><![CDATA[<p>
	At long last, the engine that felt like it would never be done is here. This is all the result of <a href="https://www.leadwerks.com/community/blogs/entry/1763-preparing-leadwerks-for-the-future/" rel="">an idea</a> I had and wanted to put to the test. Of course I never anticipated it was possible to deliver a 10x improvement in rendering performance, but here we are with the <a href="https://github.com/UltraEngine/Benchmarks" rel="external nofollow">benchmarks to prove it</a>.
</p>

<p>
	<span style="font-size:18px;">Research &amp; Development</span>
</p>

<p>
	The last four years of research and development were an opportunity to rethink and redesign what a game engine should be. Along the way there were a few ideas I had that turned out to be not that great of an idea in practice:
</p>

<p>
	<b>Variance shadow maps:</b> Although I love the look of these under very controlled settings, the technique has extremely bad artifacts that can never be fixed. Depth shadow maps really are the best approach to texture-based shadows. They are also extremely slow to render shadow updates. I would like to see some better hardware filtering options in the future, though.
</p>

<p>
	<strong>Voxel-based indirect lighting:</strong> Compelling in theory but extremely messy in practice. I learned a lot and this prompted me to integrate compute shaders into the engine, but ultimately the results I got weren't compelling enough to turn into a finished product. Given the problems everyone else has with this approach, I think it makes sense to focus on hardware raytracing as the next step up in lighting.
</p>

<p>
	<strong>Novel entity component system: </strong>My idea was to make it so when you called a component method, all the components that had that method would get called. I had some complicated Lua code set up that handled this, and early builds of Ultra used a C++ preprocessor that analyzed header files and created some complicated wrappers to make C++ work in roughly the same way. Ultimately I decided the confusion wasn't worth the questionable benefit and I implemented a conventional multi-component system like everyone had asked for, without any preprocessor.
</p>

<p>
	<strong>Tabbed Asset Editor: </strong>Another thing that sounded good in theory but was not great in practice was to use a single asset editor window with tabs for the different opened files. All it took was a day of using this to realize how much better it was to open each item in its own window. Hunting and clicking file names in tabs is just not fun. I get claustrophobic just looking at this:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="19422" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/image.jpeg.2bec65ced7d16d6c38e3c6baea88492b.jpeg" rel=""><img alt="image.thumb.jpeg.d932da0b6b9a3c0b57c14a516c3bb2c6.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="19422" data-ratio="70.67" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/image.thumb.jpeg.d932da0b6b9a3c0b57c14a516c3bb2c6.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	<span style="font-size:18px;">The Results</span>
</p>

<p>
	Besides the performance, here are the things that I think turned out better than I imagined:
</p>

<p>
	<strong>glTF Integration: </strong>Khronos glTF has proven to be a <a href="https://www.ultraengine.com/learn/modeling" rel="external nofollow">wonderful workflow</a>, and eliminates any "black box" file formats for 3D content. Your game-ready files can always be pulled back into your modeling program, edited, and saved, with no need for secretive conversion pipelines. A <a href="https://www.sketchfab.com" rel="external nofollow">wide variety of game-ready content</a> is available for you to use with Ultra, so I am expecting to see no more programmer art!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="19419" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/large.37753377_10156301149921183_2048314579712212992_n.jpg.9b112ca254b8b2484a85731d4f67110c.jpg.34819a46f59731552d7b61c8779a07fd.jpg.3ece72c7614afea4109668805de9a134.jpg" rel=""><img alt="large.37753377_10156301149921183_2048314579712212992_n.jpg.9b112ca254b8b2484a85731d4f67110c.jpg.34819a46f59731552d7b61c8779a07fd.thumb.jpg.53d28eb5a1208a0716bf92d51502d992.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="19419" data-ratio="56.27" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/large.37753377_10156301149921183_2048314579712212992_n.jpg.9b112ca254b8b2484a85731d4f67110c.jpg.34819a46f59731552d7b61c8779a07fd.thumb.jpg.53d28eb5a1208a0716bf92d51502d992.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	<strong>Physically-based Rendering: </strong>This aligns closely with glTF integration because the file format includes a well-defined <a href="https://www.ultraengine.com/learn/pbrmaterials" rel="external nofollow">PBR materials system</a>. The secret to good PBR is to have good imagery for reflections. To achieve this, I put a lot of work into the volumetric environment probes system that first appeared in Leadwerks. Probe volumes can now be drawn like a brush object for easier creation and precise sizing in the editor. A configurable fade distance for each edge lets you control how probes blend together. Probes also incorporate the sky lighting so you can have seamless transitions between bright outdoor areas and dark enclosed spaces.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="19420" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/image.jpeg.629083dd53d4bc37e2851d38a9d5756a.jpeg" rel=""><img alt="image.thumb.jpeg.3f3eb06b2bae8092af8baa05f692aa01.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="19420" data-ratio="49.20" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/image.thumb.jpeg.3f3eb06b2bae8092af8baa05f692aa01.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	<strong>Lua Integration:</strong> The VSCode Lua debugger turns Lua into a <a href="https://www.ultraengine.com/learn/Programming?lang=lua" rel="external nofollow">first-class game programming language</a> and is a dream to work with. The editor scripting integration is in my opinion the best scripting integration of any 3D program I've ever seen. You can access the entire engine API from within editor scripts to <a href="https://www.leadwerks.com/community/topic/62313-clear-cache-option-in-the-editor/" rel="">add new functionality</a> or modify the program. Additional documentation for the editor's innards will arrive in the coming days.
</p>

<p>
	<strong>Ultra GUI: </strong>This took a lot of time but having control over every pixel and event made it worthwhile. Taking control of the user experience by writing <a href="https://store.steampowered.com/app/1512210/Ultra_App_Kit/" rel="external nofollow">my own GUI</a> was one of the best decisions I made. The interface doubles up as the editor's own UI drawn using GDI+ and the in-game UI rendered with Vulkan.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="19417" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/ss_f0a2b0e8b1f06ac5cc8997a00b6948162360ec60.1920x1080.jpg.732e15e7b0852dfb93b5b987bd0f40d0.jpg" rel=""><img alt="ss_f0a2b0e8b1f06ac5cc8997a00b6948162360ec60.1920x1080.thumb.jpg.ce6b85cb154644fb47b7a8cde104a98e.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="19417" data-ratio="56.27" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/ss_f0a2b0e8b1f06ac5cc8997a00b6948162360ec60.1920x1080.thumb.jpg.ce6b85cb154644fb47b7a8cde104a98e.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	<strong>C++ API:</strong> The most significant change is the user of shared pointers, which forever eliminate the problems with uninitialized and invalid pointers games were prone to as they grew more complex. Additionally, the API is <a href="https://www.ultraengine.com/learn/Programming?lang=cpp" rel="external nofollow">generally better thought out</a> and consistent than what we had in Leadwerks.
</p>

<p>
	<strong>Terrain:</strong> You wanted more terrain material layers, so you got them. Up to 256, in fact, with fast performance. You can create multiple terrains, position, rotate, and scale them, and even create non-square terrains to fill in any area.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/image.jpeg.130cc7eba1a4375b5b3a6770b4ed09ad.jpeg" data-fileid="19423" data-fileext="jpeg" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="19423" data-ratio="27.07" width="750" alt="image.thumb.jpeg.77815c2d6147071301954a52a785c063.jpeg" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_10/image.thumb.jpeg.77815c2d6147071301954a52a785c063.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	 
</p>

<p>
	<strong>Player Physics:</strong> Yes, you can crouch now. <img alt=";)" data-emoticon="" height="20" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" srcset="https://leadwerksstorage.s3.us-east-2.amazonaws.com/emoticons/wink@2x.png 2x" title=";)" width="20" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/emoticons/default_wink.png">
</p>

<p>
	<strong>Pathfinding: </strong>It's dynamic and you can create multiple navmeshes, for different areas or differently sized characters. Pathfinding and physics are now decoupled so you can have armies of characters that only use the pathfinding system for movement.
</p>

<p>
	<strong>3D Brush Editing:</strong> I was not planning on this feature until the last minute, but you can can create, move, scale, rotate, and shear brushes in any viewport, including the 3D view. the long-asked-for vertex tool is included as well as a face tool for adjusting texture mapping and geometry.
</p>

<p>
	Last but not least, the engine's <strong>multithreaded </strong>design is crazy advanced! Your game code runs on its own thread, giving you a full 16 milliseconds to operate without slowing down the renderer. Physics, pathfinding, animation, rendering, and culling all operate on separate threads. It's an amazing feat in engineering that was only possible because I had seen all the bottlenecks people could create in Leadwerks, so I could plan a way around them when designing Ultra.
</p>

<p>
	<span style="font-size:18px;">Early Access</span>
</p>

<p>
	The 1.0 release will be marked "Early Access" because there are still a few extra features to add, and we will have a period where changes to the workflow can still be considered based on user feedback. Once decals, particle emitters, VR support, flowgraphs, and brush smooth groups are added, that will be considered version 1.1 (no "early access"). So there will still be a few months of time where I can revise any parts of the workflow you think can be improved. There are also still some <a href="https://www.leadwerks.com/community/topic/62257-cascaded-shadow-map-artifact/" rel="">compatibility issues</a> with AMD cards that are being resolved, so please be aware of that.
</p>

<p>
	A big thanks goes out to all the testers who helped me smooth things out before the release. The good times are finally here!
</p>
]]></description><guid isPermaLink="false">2826</guid><pubDate>Tue, 24 Oct 2023 22:13:00 +0000</pubDate></item><item><title>Colliders in Ultra Engine</title><link>https://www.leadwerks.com/community/blogs/entry/2820-colliders-in-ultra-engine/</link><description><![CDATA[<p>
	<strong>Attention: The addition of our custom model file format has changed the way this system works. Colliders are now saved into the Ultra MDL file.</strong>
</p>

<p>
	I haven't been blogging much because I am making so much progress that there would be too much to cover. In this article I will talk about one little system I spent the last week on and show the improvements I have made to the workflow over Leadwerks.
</p>

<p>
	In the asset editor, when the root of a model is selected, a "Collider" field appears in the physics properties. You can use this to quickly calculate and apply a collider to the model.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18781" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.fd9408ae3b91eddb08d32d80342fb2be.jpeg" rel=""><img alt="image.thumb.jpeg.dfb588a2391ba3c40a345955b401bd9a.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18781" data-ratio="66.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.thumb.jpeg.dfb588a2391ba3c40a345955b401bd9a.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	The convex hull collider includes an option for tolerance. You can adjust this to get a more or less precise convex hull around the model:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="18782" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.gif.731642d696383728debc8224fc26793e.gif" rel=""><img alt="image.thumb.gif.3930737397bba368278ed60725903eee.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="18782" data-ratio="66.33" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.thumb.gif.3930737397bba368278ed60725903eee.gif" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	There is also an "optimize" option for the mesh collider. When this is activated it will attempt to merge adjacent coplanar polygons into a single face, and can result in fewer seams on colliding faces.
</p>

<p>
	Two new shapes are included, the capsule and the chamfer cylinder, so now all the shapes supported by <a href="http://newtondynamics.com" rel="external nofollow">Newton Dynamics</a> are available. (The chamfer cylinder is good for vehicle tires.)
</p>

<p>
	<img alt="image.jpeg.33bb742b7fabc2a9899335922e9f994e.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18783" data-ratio="75.00" style="height:auto;" width="320" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.33bb742b7fabc2a9899335922e9f994e.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /> <img alt="image.jpeg.e569adfd0a4c6cbd50fd41dcc91d944a.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18784" data-ratio="75.00" style="height:auto;" width="320" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.e569adfd0a4c6cbd50fd41dcc91d944a.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" />
</p>

<p>
	Unlike Leadwerks, you no longer need to specify the axis cylindrical shapes are oriented around. The editor will calculate which bounding box axes are most like each other, and use the third axis to orient the shape. This works for long skinny objects:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18785" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.a0df2c398ef66d6a432c80b859a2327a.jpeg" rel=""><img alt="image.thumb.jpeg.64fda76f0ca50ee9f97194f774837f5b.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18785" data-ratio="67.20" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.thumb.jpeg.64fda76f0ca50ee9f97194f774837f5b.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	And it also works for short fat objects:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18786" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.19729697856bc861cd41f93a84d76bc3.jpeg" rel=""><img alt="image.thumb.jpeg.e48c770c650c9ae7fa405153e8caa423.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18786" data-ratio="66.93" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.thumb.jpeg.e48c770c650c9ae7fa405153e8caa423.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	It will even detect which direction a cone should be facing, by calculating the total distance from the shape the model vertices are with each direction, and selecting the orientation with the smallest error value. (A single cone is probably not the best choice of shape for this model, but the point is that it correctly guesses which direction the cone should point based on the model vertices.)
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18788" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.06171fb5a95b0c519407f6fd8dc5a7f2.jpeg" rel=""><img alt="image.thumb.jpeg.6f1ddb4a704e35dd45358a3307374203.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18788" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.thumb.jpeg.6f1ddb4a704e35dd45358a3307374203.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	For more advanced geometry, the convex decomposition tool is available in the Utilities panel. This exposes all the parameters available in V-HACD 4.0 and processes the task on a separate thread so you can continue to use the editor as the task runs in the background. It usually only takes a few seconds to compete, but it is nice to not have your workflow interrupted:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18787" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.9d1fab0ab055ad8607f58fe6faadc07b.jpeg" rel=""><img alt="image.thumb.jpeg.70c868009518c34f54436c9ed3e7a4e9.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18787" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.thumb.jpeg.70c868009518c34f54436c9ed3e7a4e9.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	You can export colliders as model files, so it's also possible to just use this to generate convex hulls and export them to .obj. (I could get a better fitting shape if I spent more time adjusting the settings, but I just quickly made this shape with very low settings in order to get this screenshot.)
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18789" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.jpeg.3a19ff0d194cf25df613e4587575d684.jpeg" rel=""><img alt="image.thumb.jpeg.897b562ae7186c01adf023147c4ae95c.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18789" data-ratio="62.93" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_06/image.thumb.jpeg.897b562ae7186c01adf023147c4ae95c.jpeg" src="https://www.leadwerks.com/community/applications/core/interface/js/spacer.png" /></a>
</p>

<p>
	The new .collider file format, like everything else in Ultra, is JSON-based. Simple shapes can be stored in pure ASCII text, so they can be edited by hand if needed:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="pun">{</span><span class="pln">
	</span><span class="str">"collider"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
		</span><span class="str">"parts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
			</span><span class="pun">{</span><span class="pln">
				</span><span class="str">"offset"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
					</span><span class="lit">0.0024815797805786133</span><span class="pun">,</span><span class="pln">
					</span><span class="pun">-</span><span class="lit">0.18715500831604004</span><span class="pun">,</span><span class="pln">
					</span><span class="pun">-</span><span class="lit">1.055002212524414e-05</span><span class="pln">
				</span><span class="pun">],</span><span class="pln">
				</span><span class="str">"rotation"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
					</span><span class="lit">0.0</span><span class="pun">,</span><span class="pln">
					</span><span class="lit">0.0</span><span class="pun">,</span><span class="pln">
					</span><span class="lit">0.0</span><span class="pln">
				</span><span class="pun">],</span><span class="pln">
				</span><span class="str">"shape"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"BOX"</span><span class="pun">,</span><span class="pln">
				</span><span class="str">"size"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
					</span><span class="lit">1.8899539709091187</span><span class="pun">,</span><span class="pln">
					</span><span class="lit">2.0</span><span class="pun">,</span><span class="pln">
					</span><span class="lit">1.8019688129425049</span><span class="pln">
				</span><span class="pun">]</span><span class="pln">
			</span><span class="pun">}</span><span class="pln">
		</span><span class="pun">]</span><span class="pln">
	</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	More complex shapes like convex hulls and meshes use binary data appended to the end of the file to store their contents, so that they load quickly in the engine:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="pun">{</span><span class="pln">
	</span><span class="str">"collider"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
		</span><span class="str">"parts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
			</span><span class="pun">{</span><span class="pln">
				</span><span class="str">"data"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
				</span><span class="str">"shape"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"CONVEXHULL"</span><span class="pun">,</span><span class="pln">
				</span><span class="str">"vertices"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">61</span><span class="pln">
			</span><span class="pun">}</span><span class="pln">
		</span><span class="pun">]</span><span class="pln">
	</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}�</span><span class="pln">n</span><span class="pun">�</span><span class="lit">2</span><span class="pun">��&lt;�</span><span class="lit">5</span><span class="pun">�</span><span class="pln">j</span><span class="pun">�</span><span class="pln">k</span><span class="pun">��#�</span><span class="pln">I3</span><span class="pun">��</span><span class="pln">ch</span><span class="pun">�������=</span><span class="pln">b</span><span class="pun">[��</span><span class="pln">I</span><span class="pun">&lt;��~</span><span class="pln">d</span><span class="pun">�</span><span class="pln">I</span><span class="pun">�</span><span class="pln">D</span><span class="pun">�`</span><span class="pln"> </span><span class="pun">�[�</span><span class="pln">\{</span><span class="lit">4</span><span class="pun">���</span><span class="pln">A</span><span class="pun">���?</span><span class="pln">XO3</span><span class="pun">���&gt;��վ�</span><span class="pln">v</span><span class="pun">!���</span><span class="pln">f</span><span class="pun">���</span><span class="pln">	</span><span class="pun">?]����</span><span class="pln">I</span><span class="pun">�!�����</span><span class="pln">V</span><span class="pun">�</span><span class="pln"> </span><span class="pun">&gt;�?���</span><span class="pln">mdG</span><span class="pun">�</span><span class="pln">t9</span><span class="pun">�</span><span class="pln">a</span><span class="pun">��@���</span><span class="pln">X</span><span class="pun">?</span><span class="pln">W</span><span class="pun">��</span><span class="lit">0o</span><span class="pun">��</span><span class="pln">e</span><span class="pun">�-�</span><span class="pln">D</span><span class="pun">��</span><span class="pln">h</span><span class="pun">�</span><span class="pln">P</span><span class="pun">?&gt;����&gt;��׾���@��=</span><span class="pln">T</span><span class="pun">�</span><span class="pln">U</span><span class="pun">?��ž�{����</span><span class="pln">w</span><span class="pun">��</span><span class="pln"> </span><span class="pun">���</span><span class="pln">G</span><span class="pun">��</span><span class="lit">3</span><span class="pun">��ޑ?&gt;</span><span class="pln">V</span><span class="pun">?</span><span class="pln">c</span><span class="pun">�����;���</span><span class="lit">6</span><span class="pun">�</span><span class="pln">s</span><span class="pun">�/?&amp;�=����?</span><span class="pln">q</span><span class="pun">�</span><span class="pln">e</span><span class="pun">?</span><span class="pln">j</span><span class="pun">��</span><span class="pln">N</span><span class="pun">���[#</span><span class="pln">b</span><span class="pun">��</span><span class="pln">b</span><span class="pun">ݽ(��&gt;��Ǿ�ڽ�</span><span class="pln">E</span><span class="pun">��</span><span class="pln">MV</span><span class="pun">�ַؽ��</span><span class="lit">8</span><span class="pun">&lt;�</span><span class="pln">G</span><span class="pun">������</span><span class="pln">D</span><span class="pun">?</span><span class="pln">BYT</span><span class="pun">?</span><span class="pln">I</span><span class="pun">���@</span><span class="str">"N?6?�"</span><span class="pun">;-�</span><span class="pln">$</span><span class="pun">?��콜�</span><span class="str">";��t&gt;8�"</span><span class="pun">�</span><span class="pln">t</span><span class="pun">�</span><span class="str">";�z��3�0���"</span><span class="pun">;����ݐ�)�%=}</span><span class="pln">P</span><span class="pun">?��</span><span class="lit">0</span><span class="pun">?���=/�</span><span class="pln">E</span><span class="pun">?</span><span class="pln">BYT</span><span class="pun">?</span><span class="str">'&gt;�=�</span><span class="pln">E</span><span class="pun">��</span><span class="pln">MV</span><span class="pun">����={;</span><span class="pln">
</span><span class="pun">?</span><span class="str">"����&gt;�־��</span><span class="pln">c</span><span class="pun">�</span><span class="pln">
</span><span class="pun">�&gt;��?</span><span class="pln">q</span><span class="pun">�</span><span class="pln">e</span><span class="pun">?�</span><span class="pln">$</span><span class="pun">&gt;�ӽ��</span><span class="pln">P</span><span class="pun">��@&gt;��,?�</span><span class="pln">j</span><span class="pun">|=�</span><span class="lit">2</span><span class="pun">�&gt;[</span><span class="pln">o</span><span class="pun">/���</span><span class="pln">P</span><span class="pun">�</span><span class="pln">C</span><span class="pun">��&gt;��?&gt;</span><span class="pln">V</span><span class="pun">?</span><span class="pln">Z</span><span class="pun">�&gt;�������Қ�&gt;��</span><span class="pln">h</span><span class="pun">���@�&gt;��</span><span class="pln">E</span><span class="pun">�</span><span class="pln">CO2</span><span class="pun">��.�&gt;</span><span class="pln">T</span><span class="pun">��[</span><span class="pln">A</span><span class="pun">�</span><span class="pln">y</span><span class="pun">]�&gt;</span><span class="pln">L</span><span class="pun">��=</span><span class="pln">T</span><span class="pun">�</span><span class="pln">U</span><span class="pun">?</span><span class="pln">w4</span><span class="pun">�&gt;���&gt;�쾛?#</span><span class="pln">o</span><span class="pun">��</span><span class="pln">e</span><span class="pun">�-�</span><span class="pln">r</span><span class="pun">?�����</span><span class="pln">U</span><span class="pun">?�</span><span class="str">"?</span><span class="pun">.�</span><span class="pln">v</span><span class="pun">&gt;��!?�</span><span class="pln">R</span><span class="pun">?�</span><span class="pln">zv</span><span class="pun">����&gt;�@?(�</span><span class="lit">0</span><span class="pun">�</span><span class="pln">S
</span><span class="pun">�&gt;.(?�</span><span class="pln">n_</span><span class="pun">�</span><span class="pln">C</span><span class="pun">�?�|-?</span><span class="pln">d</span><span class="pun">�[��}?��</span><span class="lit">4</span><span class="pun">?��&gt;��վ��</span><span class="lit">6</span><span class="pun">?</span><span class="lit">2M4</span><span class="pun">��*?�&gt;?</span><span class="pln">P</span><span class="pun">��=</span><span class="pln">_</span><span class="pun">�</span><span class="pln">s</span><span class="pun">��</span><span class="pln">F</span><span class="pun">?</span><span class="pln">V </span><span class="pun">���ྲ</span><span class="typ">Am</span><span class="pun">?�#�</span><span class="pln">I3</span><span class="pun">�</span><span class="typ">Lr</span><span class="pun">?�����</span></pre>

<p>
	Leadwerks .phy collider files can also be loaded.
</p>

<p>
	Notice in the shots above, in the drop-down menu there is also a "Browse" option, so you can create a collider file with a low-resolution model and then load the resulting collider to use with a high-resolution model.
</p>

<p>
	I think this design strikes the right balance to let you do simple things quickly, but still allow for more advanced options when you need them. In the future I would like to add an additional in-depth collider editing utility that would allow you to select each sub-object, select model polygons and generate shapes just from those polys, and other advanced features.
</p>

<p>
	The <a href="https://github.com/UltraEngine/Documentation/blob/master/CPP/Entity_SetPickMode.md" rel="external nofollow">pick mode feature</a> has been revised. The original design was based on Blitz3D, which did not support collision primitives at all. Instead of specifying a sphere or other shape for picking, you can now just tell the entity to use the collider for picking. (PICK_SPHERE is removed.)
</p>

<p>
	On an unrelated note, I had to adjust the camera range based on the distance from the model being viewed, in order to make very small objects visible. This allows extreme close-up shots like in the blog header image that you probably aren't used to seeing except in VR. It's kind of interesting.
</p>
]]></description><guid isPermaLink="false">2820</guid><pubDate>Fri, 09 Jun 2023 10:42:00 +0000</pubDate></item><item><title>AI-powered Texture Generation in Ultra Engine</title><link>https://www.leadwerks.com/community/blogs/entry/2819-ai-powered-texture-generation-in-ultra-engine/</link><description><![CDATA[<p>
	Not long ago, I wrote about my experiments with <a href="https://www.leadwerks.com/community/blogs/entry/2793-ai-generated-game-textures-and-concept-art/" rel="">AI-generated textures for games</a>. I think the general consensus at the time was that the technology was interesting but not very useful in its form at the time. Recently, I had reason to look into the OpenAI development SDK, because I wanted to see if it was possible to automatically convert our C++ documentation into documentation for Lua. While looking at that, I started poking around with the image generation API, which is now using DALL-E 2. Step by step, I was able to implement AI texture generation in the new editor and game engine, using only a Lua script file and an external DLL. This is available in version 1.0.2 right now:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18660" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.jpeg.4617776b8c1a5997b6069e708deb3b49.jpeg" rel=""><img alt="image.thumb.jpeg.b74ab52c91215690b915b18e83bb4cec.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18660" data-ratio="53.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.thumb.jpeg.b74ab52c91215690b915b18e83bb4cec.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	Let's take a deep dive into how this works...
</p>

<p>
	<span style="font-size:18px;">Extension Script</span>
</p>

<p>
	The extension is run by placing a file called "OpenAI.lua" in the "Scripts/Start/Extensions" folder. Everything in the Start folder gets run automatically when the editor starts up, in no particular order. At the top of the script we create a Lua table and load a DLL module that contains a few functions we need:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">local</span><span class="pln"> extension </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
extension</span><span class="pun">.</span><span class="pln">openai </span><span class="pun">=</span><span class="pln"> require </span><span class="str">"openai"</span></pre>

<p>
	Next we declare a function that is used to process events. We can skip its inner workings for now:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">function</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">hook</span><span class="pun">(</span><span class="pln">event</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">end</span></pre>

<p>
	We need a way for the user to activate our extension, so we will add a menu item to the "Scripting" submenu. The ListenEvent call will cause our hook function to get called whenever the user selects the menu item for this extension.  Note that we are passing the extension table itself in the event listener's extra parameter. There is no need for us to use a global variable for the extension table, and it's better that we don't.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="kwd">local</span><span class="pln"> menu </span><span class="pun">=</span><span class="pln"> program</span><span class="pun">.</span><span class="pln">menu</span><span class="pun">:</span><span class="pln">FindChild</span><span class="pun">(</span><span class="str">"Scripting"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> menu </span><span class="pun">~=</span><span class="pln"> </span><span class="kwd">nil</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
    </span><span class="kwd">local</span><span class="pln"> submenu </span><span class="pun">=</span><span class="pln"> menu</span><span class="pun">:</span><span class="pln">FindChild</span><span class="pun">(</span><span class="str">"OpenAI"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> submenu </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">nil</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
        submenu </span><span class="pun">=</span><span class="pln"> CreateMenu</span><span class="pun">(</span><span class="str">""</span><span class="pun">,</span><span class="pln"> menu</span><span class="pun">)</span><span class="com">-- divider</span><span class="pln">
        submenu </span><span class="pun">=</span><span class="pln"> CreateMenu</span><span class="pun">(</span><span class="str">"OpenAI"</span><span class="pun">,</span><span class="pln"> menu</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">end</span><span class="pln">
    extension</span><span class="pun">.</span><span class="pln">menuitem </span><span class="pun">=</span><span class="pln"> CreateMenu</span><span class="pun">(</span><span class="str">"Text to Image"</span><span class="pun">,</span><span class="pln"> submenu</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">end</span><span class="pln">

ListenEvent</span><span class="pun">(</span><span class="pln">EVENT_WIDGETACTION</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">menuitem</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">hook</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">)</span></pre>

<p>
	This gives us a menu item we can use to bring up our extension's window.
</p>

<p>
	<img alt="image.png.0923f51d0765874b6eaef84dbb16b9c6.png" class="ipsImage ipsImage_thumbnailed" data-fileid="18669" data-ratio="66.51" style="width:500px;height:auto;" width="651" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.png.0923f51d0765874b6eaef84dbb16b9c6.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	The next section creates a window, creates a user interface, and adds some widgets to it. I won't paste the whole thing here, but you can look at the script to see the rest:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">extension</span><span class="pun">.</span><span class="pln">window </span><span class="pun">=</span><span class="pln"> CreateWindow</span><span class="pun">(</span><span class="str">"Text to Image"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> winx</span><span class="pun">,</span><span class="pln"> winy</span><span class="pun">,</span><span class="pln"> program</span><span class="pun">.</span><span class="pln">window</span><span class="pun">,</span><span class="pln"> WINDOW_HIDDEN </span><span class="pun">|</span><span class="pln"> WINDOW_CENTER </span><span class="pun">|</span><span class="pln"> WINDOW_TITLEBAR</span><span class="pun">)</span></pre>

<p>
	Note the window is using the WINDOW_HIDDEN style flag so it is not visible when the program starts. We're also going to add event listeners to detect when the window is closed, and when a button is pressed:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">ListenEvent</span><span class="pun">(</span><span class="pln">EVENT_WINDOWCLOSE</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">window</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">hook</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">)</span><span class="pln">
ListenEvent</span><span class="pun">(</span><span class="pln">EVENT_WIDGETACTION</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">button</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">hook</span><span class="pun">,</span><span class="pln"> extension</span><span class="pun">)</span></pre>

<p>
	The resulting tool window will look something like this:
</p>

<p>
	<img alt="image.png.72ca5378f207fa2ef86508a2414080fe.png" class="ipsImage ipsImage_thumbnailed" data-fileid="18661" data-ratio="60.26" style="width:460px;height:auto;" width="609" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.png.72ca5378f207fa2ef86508a2414080fe.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	Now let's take a look at that hook function. We made three calls to ListenEvent, so that means we have three things the function needs to evaluate. Selecting the menu item for this extension will cause our hidden window to become visible and be activated:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">    </span><span class="kwd">elseif</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">id </span><span class="pun">==</span><span class="pln"> EVENT_WIDGETACTION </span><span class="kwd">then</span><span class="pln">
        
        </span><span class="kwd">if</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">source </span><span class="pun">==</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">menuitem </span><span class="kwd">then</span><span class="pln">

            extension</span><span class="pun">.</span><span class="pln">window</span><span class="pun">:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">)</span><span class="pln">
            extension</span><span class="pun">.</span><span class="pln">window</span><span class="pun">:</span><span class="pln">Activate</span><span class="pun">()</span></pre>

<p>
	When the user closes the close button on the tool window, the window gets hidden and the main program window is activated:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">    </span><span class="kwd">if</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">id </span><span class="pun">==</span><span class="pln"> EVENT_WINDOWCLOSE </span><span class="kwd">then</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">source </span><span class="pun">==</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">window </span><span class="kwd">then</span><span class="pln">
            extension</span><span class="pun">.</span><span class="pln">window</span><span class="pun">:</span><span class="pln">SetHidden</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
            program</span><span class="pun">.</span><span class="pln">window</span><span class="pun">:</span><span class="pln">Activate</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">end</span></pre>

<p>
	Finally, we get to the real point of this extension, and write the code that should be executed when the Generate button is pressed. First we get the API key from the text field, passing it to the Lua module DLL by calling openal.setapikey.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">        </span><span class="kwd">elseif</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">source </span><span class="pun">==</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">button </span><span class="kwd">then</span><span class="pln">

            </span><span class="kwd">local</span><span class="pln"> apikey </span><span class="pun">=</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">apikeyfield</span><span class="pun">:</span><span class="pln">GetText</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> apikey </span><span class="pun">==</span><span class="pln"> </span><span class="str">""</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                Notify</span><span class="pun">(</span><span class="str">"API key is missing"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Error"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
                </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pln">
            </span><span class="kwd">end</span><span class="pln">

            extension</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">.</span><span class="pln">setapikey</span><span class="pun">(</span><span class="pln">apikey</span><span class="pun">)</span></pre>

<p>
	Next we get the user's description of the image they want, and figure out what size it should be generated at. Smaller images generate faster and cost a little bit less, if you are using a paid OpenAL plan, so they can be good for testing ideas. The maximum size for images is currently 1021x1024.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">            </span><span class="kwd">local</span><span class="pln"> prompt </span><span class="pun">=</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">promptfield</span><span class="pun">:</span><span class="pln">GetText</span><span class="pun">()</span><span class="pln">

            </span><span class="kwd">local</span><span class="pln"> sz </span><span class="pun">=</span><span class="pln"> </span><span class="lit">512</span><span class="pln">
            </span><span class="kwd">local</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">sizefield</span><span class="pun">:</span><span class="pln">GetSelectedItem</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                sz </span><span class="pun">=</span><span class="pln"> </span><span class="lit">256</span><span class="pln">
            </span><span class="kwd">elseif</span><span class="pln"> i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                sz </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1024</span><span class="pln">
            </span><span class="kwd">end</span></pre>

<p>
	The next step is to copy the user's settings into the program settings so they will get saved when the program closes. Since the main program is using a <a href="https://github.com/UltraEngine/tableplusplus" rel="external nofollow">C++ table</a> for the settings, both Lua and the main program can easily share the same information:
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">            </span><span class="com">--Save settings</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> type</span><span class="pun">(</span><span class="pln">program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">)</span><span class="pln"> </span><span class="pun">~=</span><span class="pln"> </span><span class="str">"userdata"</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
            </span><span class="kwd">end</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> type</span><span class="pun">(</span><span class="pln">program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">)</span><span class="pln"> </span><span class="pun">~=</span><span class="pln"> </span><span class="str">"userdata"</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">.</span><span class="pln">openai </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
            </span><span class="kwd">end</span><span class="pln">
            program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">.</span><span class="pln">apikey </span><span class="pun">=</span><span class="pln"> apikey
            program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">.</span><span class="pln">prompt </span><span class="pun">=</span><span class="pln"> prompt
            program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">.</span><span class="pln">size </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
            program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">.</span><span class="pln">size</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> sz
            program</span><span class="pun">.</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">extensions</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">.</span><span class="pln">size</span><span class="pun">[</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> sz   </span></pre>

<p>
	Extensions should save their settings in a sub-table in the "extensions" table, so keep data separate from the main program and other extensions. When these settings are saved in the settings.json file, they will look like this. Although generated images must be square, I opted to save both width and height in the settings, for possible future compatibility.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="pln">	</span><span class="str">"extensions"</span><span class="pun">:</span><span class="pln">
	</span><span class="pun">{</span><span class="pln">
		</span><span class="str">"openai"</span><span class="pun">:</span><span class="pln">
		</span><span class="pun">{</span><span class="pln">
			</span><span class="str">"apikey"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"</span><span class="pun">,</span><span class="pln">
			</span><span class="str">"prompt"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"tiling seamless texture warehouse painted concrete wall abandoned dirty"</span><span class="pun">,</span><span class="pln">
			</span><span class="str">"size"</span><span class="pun">:</span><span class="pln">
			</span><span class="pun">[</span><span class="pln">
				</span><span class="lit">1024</span><span class="pun">,</span><span class="pln">
				</span><span class="lit">1024</span><span class="pln">
			</span><span class="pun">]</span><span class="pln">
		</span><span class="pun">}</span><span class="pln">
	</span><span class="pun">},</span></pre>

<p>
	Finally, we call the module function to generate the image, which may take a couple of minutes. If its successful we load the resulting image as a pixmap, create a texture from it, and then open that texture in a new asset editor window. This is done to eliminate the asset path, so the asset editor doesn't know where the file was loaded from. We also make a call to AssetEditor:Modify, which will cause the window to display a prompt if it is closed without saving. This prevents the user's project folder from filling up with a lot of garbage images they don't want to keep.
</p>

<pre class="ipsCode prettyprint lang-lua prettyprinted"><span class="pln">            </span><span class="kwd">if</span><span class="pln"> extension</span><span class="pun">.</span><span class="pln">openai</span><span class="pun">.</span><span class="pln">newimage</span><span class="pun">(</span><span class="pln">sz</span><span class="pun">,</span><span class="pln"> sz</span><span class="pun">,</span><span class="pln"> prompt</span><span class="pun">,</span><span class="pln"> savepath</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                </span><span class="kwd">local</span><span class="pln"> pixmap </span><span class="pun">=</span><span class="pln"> LoadPixmap</span><span class="pun">(</span><span class="pln">savepath</span><span class="pun">)</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> pixmap </span><span class="pun">~=</span><span class="pln"> </span><span class="kwd">nil</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                    </span><span class="kwd">local</span><span class="pln"> tex </span><span class="pun">=</span><span class="pln"> CreateTexture</span><span class="pun">(</span><span class="pln">TEXTURE_2D</span><span class="pun">,</span><span class="pln"> pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> pixmap</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">y</span><span class="pun">,</span><span class="pln"> pixmap</span><span class="pun">.</span><span class="pln">format</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">pixmap</span><span class="pun">})</span><span class="pln">
                    tex</span><span class="pun">.</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> </span><span class="str">"New texture"</span><span class="pln">
                    </span><span class="kwd">local</span><span class="pln"> asseteditor </span><span class="pun">=</span><span class="pln"> program</span><span class="pun">:</span><span class="pln">OpenAsset</span><span class="pun">(</span><span class="pln">tex</span><span class="pun">)</span><span class="pln">
                    </span><span class="kwd">if</span><span class="pln"> asseteditor </span><span class="pun">~=</span><span class="pln"> </span><span class="kwd">nil</span><span class="pln"> </span><span class="kwd">then</span><span class="pln">
                        asseteditor</span><span class="pun">:</span><span class="pln">Modify</span><span class="pun">()</span><span class="pln">
                    </span><span class="kwd">else</span><span class="pln">
                        Print</span><span class="pun">(</span><span class="str">"Error: Failed to open texture in asset editor."</span><span class="pun">);</span><span class="pln">
                    </span><span class="kwd">end</span><span class="pln">
                </span><span class="kwd">end</span><span class="pln">
            </span><span class="kwd">else</span><span class="pln">
                Print</span><span class="pun">(</span><span class="str">"Error: Image generation failed."</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">end</span></pre>

<p>
	The resulting extension provides an interface we can use to generate a variety of interesting textures. I think you will agree, these are quite a lot better than what we had just a few months ago.
</p>

<p>
	<img alt="image.jpeg.a84ad19638234783763ad45c6491115b.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18663" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.jpeg.a84ad19638234783763ad45c6491115b.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	<img alt="image.jpeg.29c1666cdd4b0ca68c7febf309b3bbe1.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18664" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.jpeg.29c1666cdd4b0ca68c7febf309b3bbe1.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	<img alt="image.jpeg.27791588b4793e7a27cdf5fb52e742bd.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18665" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.jpeg.27791588b4793e7a27cdf5fb52e742bd.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	<img alt="image.jpeg.98f627b2635def51543789dbe324355f.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18667" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.jpeg.98f627b2635def51543789dbe324355f.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	<img alt="image.jpeg.b3b75d61d610d082fff611b04f087ade.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18668" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.jpeg.b3b75d61d610d082fff611b04f087ade.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	Of course the Lua debugger in Visual Studio Code came in very handy while developing this.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18670" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.jpeg.0387f13c7b61d71f30654f6efba1b843.jpeg" rel=""><img alt="image.thumb.jpeg.8b042d5d16419378d492aacc8a50399f.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18670" data-ratio="53.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_04/image.thumb.jpeg.8b042d5d16419378d492aacc8a50399f.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	That's pretty much all there is to the Lua side of things. Now let's take a closer look at the module code.
</p>

<p>
	<span style="font-size:18px;">The Module</span>
</p>

<p>
	Lua modules provide a mechanism whereby Lua can execute C++ code packed into a dynamic linked library. The DLL needs to contain one retired function, which is luaopen_ plus the name of the module, without any extension. The module file is "openai.dll" so we will declare a function called luaopen_openai:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">extern</span><span class="pln"> </span><span class="str">"C"</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    __declspec</span><span class="pun">(</span><span class="pln">dllexport</span><span class="pun">)</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> luaopen_openai</span><span class="pun">(</span><span class="pln">lua_State</span><span class="pun">*</span><span class="pln"> L</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        lua_newtable</span><span class="pun">(</span><span class="pln">L</span><span class="pun">);</span><span class="pln">
        </span><span class="typ">int</span><span class="pln"> sz </span><span class="pun">=</span><span class="pln"> lua_gettop</span><span class="pun">(</span><span class="pln">L</span><span class="pun">);</span><span class="pln">
        lua_pushcfunction</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> openai_newimage</span><span class="pun">);</span><span class="pln">   lua_setfield</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">"newimage"</span><span class="pun">);</span><span class="pln">
        lua_pushcfunction</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> openai_setapikey</span><span class="pun">);</span><span class="pln">   lua_setfield</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">"setapikey"</span><span class="pun">);</span><span class="pln">
        lua_pushcfunction</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> openai_getlog</span><span class="pun">);</span><span class="pln">   lua_setfield</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">"getlog"</span><span class="pun">);</span><span class="pln">
        lua_settop</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> sz</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	This function creates a new table and adds some function pointers to it, and returns the table. (This is the table we will store in extension.openai). The functions are setapikey(), getlog() and newimage().
</p>

<p>
	The first function is very simple, and just provides a way for the script to send the user's API key to the module:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="typ">int</span><span class="pln"> openai_setapikey</span><span class="pun">(</span><span class="pln">lua_State</span><span class="pun">*</span><span class="pln"> L</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    APIKEY</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lua_isstring</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">))</span><span class="pln"> APIKEY </span><span class="pun">=</span><span class="pln"> lua_tostring</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	The getlog function just returns any printed text, for extra debugging:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="typ">int</span><span class="pln"> openai_getlog</span><span class="pun">(</span><span class="pln">lua_State</span><span class="pun">*</span><span class="pln"> L</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    lua_pushstring</span><span class="pun">(</span><span class="pln">L</span><span class="pun">,</span><span class="pln"> logtext</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">());</span><span class="pln">
    logtext</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	The newimage function is where the action is at, but there's actually two overloads of it. The first one is the "real" function, and the second one is a wrapper that extracts the right function arguments from Lua, and then calls the real function. I'd say the hardest part of all this is interfacing with the Lua stack, but if you just go carefully you can follow the right pattern.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">bool</span><span class="pln"> openai_newimage</span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> std</span><span class="pun">::</span><span class="pln">string</span><span class="pun">&amp;</span><span class="pln"> prompt</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> std</span><span class="pun">::</span><span class="pln">string</span><span class="pun">&amp;</span><span class="pln"> path</span><span class="pun">)</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> openai_newimage</span><span class="pun">(</span><span class="pln">lua_State</span><span class="pun">*</span><span class="pln"> L</span><span class="pun">)</span></pre>

<p>
	This is done so the module can be easily compiled and tested as an executable.
</p>

<p>
	The real newimage function is where all the action is. It sets up a curl instance and communicates with a web server. There's quite a lot of error checking in the response, so don't let that confused you. If the call is successful, then a second curl object is created in order to download the resulting image. This must be done before the curl connection is closed, as the server will not allow access after that happens:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="kwd">bool</span><span class="pln"> openai_newimage</span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> std</span><span class="pun">::</span><span class="pln">string</span><span class="pun">&amp;</span><span class="pln"> prompt</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> std</span><span class="pun">::</span><span class="pln">string</span><span class="pun">&amp;</span><span class="pln"> path</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">bool</span><span class="pln"> success </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">width </span><span class="pun">!=</span><span class="pln"> height or </span><span class="pun">(</span><span class="pln">width </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">256</span><span class="pln"> and width </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">512</span><span class="pln"> and width </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">1024</span><span class="pun">))</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Image dimensions must be 256x256, 512x512, or 1024x1024."</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    std</span><span class="pun">::</span><span class="pln">string imageurl</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">APIKEY</span><span class="pun">.</span><span class="pln">empty</span><span class="pun">())</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

    std</span><span class="pun">::</span><span class="pln">string url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://api.openai.com/v1/images/generations"</span><span class="pun">;</span><span class="pln">
    std</span><span class="pun">::</span><span class="pln">string readBuffer</span><span class="pun">;</span><span class="pln">
    std</span><span class="pun">::</span><span class="pln">string bearerTokenHeader </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Authorization: Bearer "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> APIKEY</span><span class="pun">;</span><span class="pln">
    std</span><span class="pun">::</span><span class="pln">string contentType </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Content-Type: application/json"</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">auto</span><span class="pln"> curl </span><span class="pun">=</span><span class="pln"> curl_easy_init</span><span class="pun">();</span><span class="pln">

    </span><span class="kwd">struct</span><span class="pln"> curl_slist</span><span class="pun">*</span><span class="pln"> headers </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span><span class="pln">
    headers </span><span class="pun">=</span><span class="pln"> curl_slist_append</span><span class="pun">(</span><span class="pln">headers</span><span class="pun">,</span><span class="pln"> bearerTokenHeader</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">());</span><span class="pln">
    headers </span><span class="pun">=</span><span class="pln"> curl_slist_append</span><span class="pun">(</span><span class="pln">headers</span><span class="pun">,</span><span class="pln"> contentType</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">());</span><span class="pln">

    nlohmann</span><span class="pun">::</span><span class="pln">json j3</span><span class="pun">;</span><span class="pln">
    j3</span><span class="pun">[</span><span class="str">"prompt"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> prompt</span><span class="pun">;</span><span class="pln">
    j3</span><span class="pun">[</span><span class="str">"n"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">switch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">width</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">case</span><span class="pln"> </span><span class="lit">256</span><span class="pun">:</span><span class="pln">
        j3</span><span class="pun">[</span><span class="str">"size"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"256x256"</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">case</span><span class="pln"> </span><span class="lit">512</span><span class="pun">:</span><span class="pln">
        j3</span><span class="pun">[</span><span class="str">"size"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"512x512"</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">case</span><span class="pln"> </span><span class="lit">1024</span><span class="pun">:</span><span class="pln">
        j3</span><span class="pun">[</span><span class="str">"size"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"1024x1024"</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    std</span><span class="pun">::</span><span class="pln">string postfields </span><span class="pun">=</span><span class="pln"> j3</span><span class="pun">.</span><span class="pln">dump</span><span class="pun">();</span><span class="pln">

    curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl</span><span class="pun">,</span><span class="pln"> CURLOPT_HTTPHEADER</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">);</span><span class="pln">
    curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl</span><span class="pun">,</span><span class="pln"> CURLOPT_URL</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">());</span><span class="pln">
    curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl</span><span class="pun">,</span><span class="pln"> CURLOPT_POSTFIELDS</span><span class="pun">,</span><span class="pln"> postfields</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">());</span><span class="pln">
    curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl</span><span class="pun">,</span><span class="pln"> CURLOPT_WRITEFUNCTION</span><span class="pun">,</span><span class="pln"> </span><span class="typ">WriteCallback</span><span class="pun">);</span><span class="pln">
    curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl</span><span class="pun">,</span><span class="pln"> CURLOPT_WRITEDATA</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">readBuffer</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">auto</span><span class="pln"> errcode </span><span class="pun">=</span><span class="pln"> curl_easy_perform</span><span class="pun">(</span><span class="pln">curl</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">errcode </span><span class="pun">==</span><span class="pln"> CURLE_OK</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="com">//OutputDebugStringA(readBuffer.c_str());</span><span class="pln">
        trim</span><span class="pun">(</span><span class="pln">readBuffer</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">readBuffer</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> and readBuffer</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">'{'</span><span class="pln"> and readBuffer</span><span class="pun">[</span><span class="pln">readBuffer</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">'}'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            j3 </span><span class="pun">=</span><span class="pln"> nlohmann</span><span class="pun">::</span><span class="pln">json</span><span class="pun">::</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">readBuffer</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">j3</span><span class="pun">.</span><span class="pln">is_object</span><span class="pun">())</span><span class="pln">
            </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">j3</span><span class="pun">[</span><span class="str">"error"</span><span class="pun">].</span><span class="pln">is_object</span><span class="pun">())</span><span class="pln">
                </span><span class="pun">{</span><span class="pln">
                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">j3</span><span class="pun">[</span><span class="str">"error"</span><span class="pun">][</span><span class="str">"message"</span><span class="pun">].</span><span class="pln">is_string</span><span class="pun">())</span><span class="pln">
                    </span><span class="pun">{</span><span class="pln">
                        std</span><span class="pun">::</span><span class="pln">string msg </span><span class="pun">=</span><span class="pln"> j3</span><span class="pun">[</span><span class="str">"error"</span><span class="pun">][</span><span class="str">"message"</span><span class="pun">];</span><span class="pln">
                        msg </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> msg</span><span class="pun">;</span><span class="pln">
                        </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">());</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                    </span><span class="kwd">else</span><span class="pln">
                    </span><span class="pun">{</span><span class="pln">
                        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Unknown error."</span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                </span><span class="kwd">else</span><span class="pln">
                </span><span class="pun">{</span><span class="pln">
                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">j3</span><span class="pun">[</span><span class="str">"data"</span><span class="pun">].</span><span class="pln">is_array</span><span class="pun">()</span><span class="pln"> or j3</span><span class="pun">[</span><span class="str">"data"</span><span class="pun">].</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
                    </span><span class="pun">{</span><span class="pln">
                        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">j3</span><span class="pun">[</span><span class="str">"data"</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">"url"</span><span class="pun">].</span><span class="pln">is_string</span><span class="pun">())</span><span class="pln">
                        </span><span class="pun">{</span><span class="pln">
                            std</span><span class="pun">::</span><span class="pln">string s </span><span class="pun">=</span><span class="pln"> j3</span><span class="pun">[</span><span class="str">"data"</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">"url"</span><span class="pun">];</span><span class="pln">
                            imageurl </span><span class="pun">=</span><span class="pln"> s</span><span class="pun">;</span><span class="pln"> </span><span class="com">// I don't know why the extra string is needed here...</span><span class="pln">

                            readBuffer</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">

                            </span><span class="com">// Download the image file</span><span class="pln">
                            </span><span class="kwd">auto</span><span class="pln"> curl2 </span><span class="pun">=</span><span class="pln"> curl_easy_init</span><span class="pun">();</span><span class="pln">
                            curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl2</span><span class="pun">,</span><span class="pln"> CURLOPT_URL</span><span class="pun">,</span><span class="pln"> imageurl</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">());</span><span class="pln">
                            curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl2</span><span class="pun">,</span><span class="pln"> CURLOPT_WRITEFUNCTION</span><span class="pun">,</span><span class="pln"> </span><span class="typ">WriteCallback</span><span class="pun">);</span><span class="pln">
                            curl_easy_setopt</span><span class="pun">(</span><span class="pln">curl2</span><span class="pun">,</span><span class="pln"> CURLOPT_WRITEDATA</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">readBuffer</span><span class="pun">);</span><span class="pln">

                            </span><span class="kwd">auto</span><span class="pln"> errcode </span><span class="pun">=</span><span class="pln"> curl_easy_perform</span><span class="pun">(</span><span class="pln">curl2</span><span class="pun">);</span><span class="pln">
                            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">errcode </span><span class="pun">==</span><span class="pln"> CURLE_OK</span><span class="pun">)</span><span class="pln">
                            </span><span class="pun">{</span><span class="pln">
                                </span><span class="typ">FILE</span><span class="pun">*</span><span class="pln"> file </span><span class="pun">=</span><span class="pln"> fopen</span><span class="pun">(</span><span class="pln">path</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">(),</span><span class="pln"> </span><span class="str">"wb"</span><span class="pun">);</span><span class="pln">
                                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">file </span><span class="pun">==</span><span class="pln"> NULL</span><span class="pun">)</span><span class="pln">
                                </span><span class="pun">{</span><span class="pln">
                                    </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Failed to write file."</span><span class="pun">);</span><span class="pln">
                                </span><span class="pun">}</span><span class="pln">
                                </span><span class="kwd">else</span><span class="pln">
                                </span><span class="pun">{</span><span class="pln">
                                    </span><span class="kwd">auto</span><span class="pln"> w </span><span class="pun">=</span><span class="pln"> fwrite</span><span class="pun">(</span><span class="pln">readBuffer</span><span class="pun">.</span><span class="pln">c_str</span><span class="pun">(),</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> readBuffer</span><span class="pun">.</span><span class="pln">size</span><span class="pun">(),</span><span class="pln"> file</span><span class="pun">);</span><span class="pln">
                                    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">w </span><span class="pun">==</span><span class="pln"> readBuffer</span><span class="pun">.</span><span class="pln">size</span><span class="pun">())</span><span class="pln">
                                    </span><span class="pun">{</span><span class="pln">
                                        success </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
                                    </span><span class="pun">}</span><span class="pln">
                                    </span><span class="kwd">else</span><span class="pln">
                                    </span><span class="pun">{</span><span class="pln">
                                        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Failed to write file data."</span><span class="pun">);</span><span class="pln">
                                    </span><span class="pun">}</span><span class="pln">
                                    fclose</span><span class="pun">(</span><span class="pln">file</span><span class="pun">);</span><span class="pln">
                                </span><span class="pun">}</span><span class="pln">
                            </span><span class="pun">}</span><span class="pln">
                            </span><span class="kwd">else</span><span class="pln">
                            </span><span class="pun">{</span><span class="pln">
                                </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Failed to download image."</span><span class="pun">);</span><span class="pln">
                            </span><span class="pun">}</span><span class="pln">

                            curl_easy_cleanup</span><span class="pun">(</span><span class="pln">curl2</span><span class="pun">);</span><span class="pln">
                        </span><span class="pun">}</span><span class="pln">
                        </span><span class="kwd">else</span><span class="pln">
                        </span><span class="pun">{</span><span class="pln">
                            </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Image URL missing."</span><span class="pun">);</span><span class="pln">
                        </span><span class="pun">}</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                    </span><span class="kwd">else</span><span class="pln">
                    </span><span class="pun">{</span><span class="pln">
                        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Data is not an array, or data is empty."</span><span class="pun">);</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">else</span><span class="pln">
            </span><span class="pun">{</span><span class="pln">
                </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Response is not a valid JSON object."</span><span class="pun">);</span><span class="pln">
                </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">readBuffer</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Response is not a valid JSON object."</span><span class="pun">);</span><span class="pln">
            </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">readBuffer</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Error: Request failed."</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    curl_easy_cleanup</span><span class="pun">(</span><span class="pln">curl</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> success</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	My first attempt at this used a third-party C++ library for OpenAI, but I actually found it was easier to just make the low-level CURL calls myself.
</p>

<p>
	Now here's the kicker: Any web API in the world will work with almost the exact same code. You now know how to build extensions that communicate with any website with a web interface, including SketchFab, CGTrader, itch.io, and easily interact with them in the Ultra Engine editor. The full source code for this and other Ultra Engine modules is available <span>here:</span>
</p>

<p>
	<span><a href="https://github.com/UltraEngine/Lua" rel="external nofollow">https://github.com/UltraEngine/Lua</a></span>
</p>

<p>
	This integration of text-to-image tends to do well with base textures that have a uniform distribution of detail. Rock, concrete, ground, plaster, and other materials look great with the DALL-E 2 generator. It doesn't do as well with geometry details and complex structures, since the AI has no real understanding of the purpose of things. The point of this integration was not to make the end-all-be-all AI texture generation. The technology is changing rapidly and will undoubtedly continue to advance. Rather, the point of this exercise was to demonstrate a complex feature added in a Lua extension, without being built into the editor's code. By releasing the source for this I hope to help people start developing their own extensions to add new peripheral features they want to see in the editor and game engine.
</p>

<div class="ipsEmbeddedVideo">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" src="https://www.ultraengine.com/community/applications/core/interface/index.html" title="Ai-powered Texture Generation in Ultra Engine editor" width="200" data-embed-src="https://www.youtube-nocookie.com/embed/12_D8SjZXKo?feature=oembed"></iframe>
	</div>
</div>

<p>
	 
</p>
]]></description><guid isPermaLink="false">2819</guid><pubDate>Mon, 24 Apr 2023 10:15:00 +0000</pubDate></item><item><title>Ultra Engine glTF extensions</title><link>https://www.leadwerks.com/community/blogs/entry/2818-ultra-engine-gltf-extensions/</link><description><![CDATA[<p>
	As I have explained before, I plan for Ultra Engine to use glTF for our main 3D model file format, so that your final game models can be easily loaded back into a modeling program for editing whenever you need. glTF supports a lot of useful features and is widely supported, but there are a few missing pieces of information I need to add into it. Fortunately, this JSON-based file format has a mechanism for extensions that add new features and data to the format. In this article I will describe the custom extensions I am adding for Ultra Engine.
</p>

<p>
	<span style="font-size:18px;">ULTRA_triangle_quads</span>
</p>

<p>
	All glTF models are triangle meshes, but we want to support quad meshes primarily because its better for tessellation. This extension gets added to the primitives block. If the "quads" value is set to true, this indicates that the triangle indices are stored in a manner such that the first four indices of every six indices form a quad:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="str">"extensions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
	</span><span class="str">"ULTRA_triangle_quads"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
		</span><span class="str">"quads"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
	</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	There is no other glTF extension for quads, and so there is no way to export a glTF quad mesh from any modeling program. To get quad meshes into Ultra Engine you can load an OBJ file and then resave it as glTF. Here is a glTF file using quads that was created this way. You can see the tessellation creates an even distribution of polygons:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18546" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.beb8899b09885468f0dfd97ec519cebc.jpeg" rel=""><img alt="image.thumb.jpeg.acf42a08c98f5f6756cbdbcdb9ae241d.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18546" data-ratio="53.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.acf42a08c98f5f6756cbdbcdb9ae241d.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	For comparison, here is the same mesh saved as triangles and tessellated. The long thin triangles result in a very uneven distribution of polygons. Not good!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18547" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.c8efa827abb73925ad31d30c7ffe3676.jpeg" rel=""><img alt="image.thumb.jpeg.1d7faa75c8904d7dc3d0fe07b5d7c980.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18547" data-ratio="53.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.1d7faa75c8904d7dc3d0fe07b5d7c980.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	The mesh still stores triangle data so the file can be loaded back into a 3D modeling program without any issues.
</p>

<p>
	Here is another comparison that shows how triangle (on the left) and quads (on the right) tessellate:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18553" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.840a3f8e8d7ed91d7c944afd39c63437.jpeg" rel=""><img alt="image.thumb.jpeg.329f645da6ffbff01d7f46d12725c64f.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18553" data-ratio="53.33" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.329f645da6ffbff01d7f46d12725c64f.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	<span style="font-size:18px;">ULTRA_material_displacement</span>
</p>

<p>
	This extension adds displacement maps to glTF materials, in a manner that is consistent with how other textures are stored:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="str">"ULTRA_material_displacement"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
	</span><span class="str">"displacementTexture"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
		</span><span class="str">"index"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln">
		</span><span class="str">"offset"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">0.035</span><span class="pun">,</span><span class="pln">
		</span><span class="str">"strength"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0.05</span><span class="pln">
	</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	The extension indicates a texture index, a maximum displacement value in meters, and a uniform offset, also in meters. This can be used to store material displacement data for tessellation or parallax mapping. Here is a model loaded straight from a glTF file with displacement info and tessellation:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18548" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.df878e7edf859abb36361846e5059646.jpeg" rel=""><img alt="image.thumb.jpeg.73a2611126ec5c4a5a02f2be80534466.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18548" data-ratio="53.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.73a2611126ec5c4a5a02f2be80534466.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	 
</p>

<p>
	If the file is loaded in other programs, the displacement info will just be skipped.
</p>

<p>
	<span style="font-size:18px;">ULTRA_vertex_displacement</span>
</p>

<p>
	Our game engine uses a <a href="https://www.leadwerks.com/community/blogs/entry/2440-advanced-tessellation-in-vulkan/" rel="">per-vertex displacement factor</a> to control how displacement maps affect geometry. This extension adds an extra attribute into the primitives structure to store these values:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="str">"primitives"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
	</span><span class="pun">{</span><span class="pln">
		</span><span class="str">"attributes"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
			</span><span class="str">"NORMAL"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
			</span><span class="str">"POSITION"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
			</span><span class="str">"TEXCOORD_0"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
		</span><span class="pun">},</span><span class="pln">
		</span><span class="str">"indices"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln">
		</span><span class="str">"material"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
		</span><span class="str">"mode"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln">
		</span><span class="str">"extensions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
			</span><span class="str">"ULTRA_vertex_displacement"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
				</span><span class="str">"DISPLACEMENT"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">7</span><span class="pln">
			</span><span class="pun">}</span><span class="pln">
		</span><span class="pun">}</span><span class="pln">
	</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	This can be used to prevent cracks from appearing  at texcoord seams.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18551" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.5539f9a37d3f229bd56427cae9750569.jpeg" rel=""><img alt="image.thumb.jpeg.347d6964d77e24f8c293281cfcfc3d5b.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18551" data-ratio="65.87" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.347d6964d77e24f8c293281cfcfc3d5b.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	Here you can see the displacement value being loaded back from a glTF file it has been saved into. I'm using the vertex color to visually verify that it's working right:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.456d1dfb0cf9bd25b0d3acd8750309b2.jpeg" data-fileid="18554" data-fileext="jpeg" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="18554" data-ratio="72.40" width="750" alt="image.thumb.jpeg.1cf298d343ee8ec919c91bd14c80cfd4.jpeg" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.1cf298d343ee8ec919c91bd14c80cfd4.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	<span style="font-size:18px;">ULTRA_extended_material</span>
</p>

<p>
	This extension adds other custom parameters that Ultra Engine uses. glTF handles almost everything we want to do, and there are just a few settings to add. Since the Ultra Engine material format is JSON-based, it's easy to just insert the extra parameters into the glTF like so:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="str">"ULTRA_extended_material"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
	</span><span class="str">"shaderFamily"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"PBR"</span><span class="pun">,</span><span class="pln">
	</span><span class="str">"shadow"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
	</span><span class="str">"tessellation"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	In reality I do not feel that this extension is very well-defined and do not expect it to see any adoption outside of Ultra Engine. I made the displacement parameters a separate extension because they are well-defined, and there might be an opportunity to work with other application developers using that extension.
</p>

<p>
	Here we can see the per-material shadow property is disabled when it is loaded from the glTF:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18549" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.d6225f35578834c1c85ccc9a9dc1f121.jpeg" rel=""><img alt="image.thumb.jpeg.7817537f85a7240cb4c4df0acda86aa9.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18549" data-ratio="53.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.7817537f85a7240cb4c4df0acda86aa9.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	For comparison, here is what the default setting looks like:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18550" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.03860b8027ee431a5f4899270f3974c5.jpeg" rel=""><img alt="image.thumb.jpeg.4f28fa2932570cc0eca7c39a95a28132.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18550" data-ratio="53.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.4f28fa2932570cc0eca7c39a95a28132.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	These extensions are simply meant to add special information that Ultra Engine uses into the glTF format. I do not currently have plans to try to get other developers to adopt my standards. I just want to add the extra information that our game engine needs, while also ensuring compatibility with the rest of the glTF ecosystem. If you are writing a glTF importer or exporter and would like to work together to improve the compatibility of our applications, let me know!
</p>

<p>
	I used the <a href="https://polyhaven.com/a/rock_moss_set_02" rel="external nofollow">Rock Moss Set 02</a> model pack from Polyhaven in this article.
</p>
]]></description><guid isPermaLink="false">2818</guid><pubDate>Sat, 18 Mar 2023 11:40:00 +0000</pubDate></item><item><title>Building Universal Game Assets</title><link>https://www.leadwerks.com/community/blogs/entry/2817-building-universal-game-assets/</link><description><![CDATA[<p>
	I have two goals for the art pipeline in the new game engine.
</p>

<ul>
	<li>
		Eliminate one-way 3D model conversions.
	</li>
	<li>
		Use the same game asset files on all platforms.
	</li>
</ul>

<p>
	In Leadwerks, 3D models get converted into the proprietary MDL format. Although the format is pretty straightforward and simple, there is no widespread support for loading it back into 3D modeling programs. This means you need to keep a copy of your 3D model in FBX and MDL format. You may possibly want to keep an additional file for the modeling program it was made in, such as 3D Studio Max MAX files. I wanted to simplify this by relying on the widely-support glTF file format, which can be used for final game-ready models, but can still easily loaded back into Blender or another program.
</p>

<p>
	<span style="font-size:18px;">Texture Compression</span>
</p>

<p>
	Texture compression is where this all gets a lot more tricky. Texture compression is an important technique that can reduce video memory usage to about 25% what it would be otherwise. When texture compression is used games run faster, load faster, and look better because they can use higher resolution images. There are two problems with texture compression.
</p>

<ul>
	<li>
		Supported texture compression methods vary wildly across PC and mobile platforms.
	</li>
	<li>
		Many 3D modeling programs do not support modern texture compression formats.
	</li>
</ul>

<p>
	On the PC, BC7 is the best compression format to use for most images. BC5 is a two-channel format appropriate for normal maps, with Z reconstruction in the shader. BC6H can be used to compress HDR RGB images (mostly skyboxes). BC4 is a single-channel compressed format that is rarely used but could be useful for some special cases. The older DXTC formats should no longer be used, as they have worse artifacts with the same data size.
</p>

<p>
	Here are a few programs that do not support the newer BC formats, even though they are the standard for modern games:
</p>

<ul>
	<li>
		Windows Explorer
	</li>
	<li>
		Blender
	</li>
	<li>
		Microsoft 3D Object Viewer
	</li>
</ul>

<p>
	Now, the glTF model format does not actually support DDS format textures at all. The <a href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/MSFT_texture_dds" rel="external nofollow">MSFT_texture_dds</a> extension adds support for this by adding an extra image source into the file. glTF loaders that support this extension can load the DDS files, while programs that do not recognize this extension will ignore it and load the original PNG or JPEG files:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="str">"textures"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"source"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"extensions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">"MSFT_texture_dds"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="str">"source"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">],</span><span class="pln">
</span><span class="str">"images"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"uri"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"defaultTexture.png"</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"uri"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"DDSTexture.dds"</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	I don't know any software that supports this extension except Ultra Engine. Even Microsoft's own 3D Object Viewer app does not support DDS files.
</p>

<p>
	The Ultra Engine editor has a feature that allows you to convert a model's textures to DDS. This will resave all textures in DDS format, and change the model materials to point to the DDS files instead of the original format the model uses (probably PNG):
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18539" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.d1c60eb0fb880d767348611fbec2dec2.jpeg" rel=""><img alt="image.thumb.jpeg.f60928e33f8d334b7dd39b74d097b8a9.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18539" data-ratio="60.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.f60928e33f8d334b7dd39b74d097b8a9.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	When a glTF file is saved after this step, the resulting glTF will keep copies of both the original PNG and the saved DDS files. The resulting glTF file can be loaded in Ultra Engine ready to use in your game, with DDS files applied, but it can still be loaded in programs that do not support DDS files, and the original PNG files will be used instead:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18540" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.33d7a5a406c794097b0e8061dd64c1fd.jpeg" rel=""><img alt="image.thumb.jpeg.105fd51ba9d8b850aff12bb779f340d6.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18540" data-ratio="62.93" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.105fd51ba9d8b850aff12bb779f340d6.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	The same model can be loaded back into Blender in case any changes need to be made. If you resave it, the references to the DDS images will be lost, so just repeat the DDS conversion step in the Ultra Engine to finalize the updated version of your model.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18542" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.a1a26eb2c8b8d389bd85968e760801f6.jpeg" rel=""><img alt="image.thumb.jpeg.4ebeb20f33685b590be9f428e5c09f54.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18542" data-ratio="53.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.4ebeb20f33685b590be9f428e5c09f54.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	To cut down on the size of your game files, you can just skip PNG files in the final packaging step so only DDS files are included.
</p>

<p>
	<span style="font-size:18px;">Basis Universal</span>
</p>

<p>
	We still have to deal with the issue of hardware support for different codecs. This table from Unity's documentation shows the problem clearly:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="18544" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.png.dc51691d4eaa4f597c439afdc768b889.png" rel=""><img alt="image.thumb.png.5ea0fdaead80dd7555c5c133f535d6c0.png" class="ipsImage ipsImage_thumbnailed" data-fileid="18544" data-ratio="47.20" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.png.5ea0fdaead80dd7555c5c133f535d6c0.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	We do want to support some mobile-based VR platforms, so this is something that needs to be considered now. <a href="https://github.com/BinomialLLC/basis_universal" rel="external nofollow">Basis Universal</a> is a library from the author of Crunch that solves this problem by introducing a platform-agnostic intermediate compressed format that can be quickly transcoded into various texture compression formats, without going through a slow decompression and recompression step. We can generate Basis files in the Ultra Engine editor just like we did for DDS: There is a <a href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_basisu" rel="external nofollow">glTF extension that supports basis textures</a>, so glTF models can be saved that reference the Basis files.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18543" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.f7bd1213bb76dd650e992c7802f84f13.jpeg" rel=""><img alt="image.thumb.jpeg.657380f2c94c4ffb8f59684598d1160d.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18543" data-ratio="63.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.657380f2c94c4ffb8f59684598d1160d.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	The basis textures are stored in the glTF file the same way the DDS extension works:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="str">"textures"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"source"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"extensions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">"KHR_texture_basisu"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="str">"source"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">],</span><span class="pln">
</span><span class="str">"images"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"mimeType"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"image/png"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"bufferView"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"mimeType"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"image/ktx2"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"bufferView"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	The resulting glTF files can be loaded as game-ready models <em>with </em>compressed textures on PC <em>or </em>mobile platforms, <em>and </em>can still be loaded back into Blender or another 3D modeling program. We don't have to store different copies of our textures in different compression formats or go through a slow conversion step when publishing the game to other platforms. If your game is only intended to run on PC platforms, then DDS is the simplest path to take, but if you plan to support mobile-based VR devices in the future then Basis solves that problem nicely.
</p>

<p>
	Here is our revised table:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="18545" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.png.e2fd0d790969a2e4aa00f2542ea6158c.png" rel=""><img alt="image.thumb.png.50e1a04367917e08b7da410bf6d3a609.png" class="ipsImage ipsImage_thumbnailed" data-fileid="18545" data-ratio="47.20" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.png.50e1a04367917e08b7da410bf6d3a609.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	I was also working with the <a href="https://www.khronos.org/ktx/" rel="external nofollow">KTX2 format</a> for a while, but I ended up only using it for its support for Basis compression. DDS and Basis cover all your needs and are more widely supported.
</p>

<p>
	Basis also supports a very lossy but efficient ETC1S method which is similar to Crunch, as well as uncompressed texture data, but in my opinion the one purpose for the Basis format is its UASTC format.
</p>

<p>
	This article features the <a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Lantern" rel="external nofollow">lantern sample glTF model</a> from Microsoft.
</p>
]]></description><guid isPermaLink="false">2817</guid><pubDate>Fri, 17 Mar 2023 12:54:00 +0000</pubDate></item><item><title>Package plugins and Quake files now supported</title><link>https://www.leadwerks.com/community/blogs/entry/2814-package-plugins-and-quake-files-now-supported/</link><description><![CDATA[<p>
	Package plugins are now supported in Ultra Engine 1.0.2. This allows the engine to handle other package formats besides just ZIP. In order to test this out, I created the <a href="https://github.com/UltraEngine/PluginSDK/tree/master/Plugins/Quake%20Loader" rel="external nofollow">Quake Loader plugin</a>, which currently supports the following formats used in the original Quake game:
</p>

<ul>
	<li>
		PAK
	</li>
	<li>
		WAD
	</li>
	<li>
		BSP (textures)
	</li>
	<li>
		SPR
	</li>
	<li>
		LMP
	</li>
</ul>

<p>
	Why Quake? Well, the original Quake game is what got me into game development through modding, but Quake is also great for testing because it's so weird. This game was written before hardware accelerated graphics existed, so it has a lot of conventions that don't match modern 3D standards:
</p>

<ul>
	<li>
		All animation is vertex morphing (no skeletal animation)
	</li>
	<li>
		Texture coordinates are in pixels / integers instead of floating point values
	</li>
	<li>
		Three different types of "packages" (PAK, WAD, BSP), the latter two of which can be stored inside the first (packages-in-packages)
	</li>
	<li>
		Image files are stored seven different ways:
		<ul>
			<li>
				LMP image files
			</li>
			<li>
				SPR sprite files
			</li>
			<li>
				Color pallette ('@')
			</li>
			<li>
				Mipmapped texture in WAD ('D')
			</li>
			<li>
				Console picture ('E')
			</li>
			<li>
				Status bar pictures ('B')
			</li>
			<li>
				Mipmapped textures stored in BSP
			</li>
		</ul>
	</li>
</ul>

<p>
	Each of the image storage methods store data in slightly different ways. Sometimes the width and height are packed into the image data, sometimes they are found elsewhere in a separate structure, sometimes they are hard-coded, and sometimes you have to guess the image dimensions based on the data size. It's a mess, and it seems like this was made up on-the-fly by several people, but this was cutting-edge technology at the time and I don't think usability outside the specified game they were working on was a big concern.
</p>

<p>
	There are some interesting challenges there, but this forms the basis of several dated but influential games. As time went by, the design became more and more sane. For example, Quake 3 just stores textures as TGA images in a ZIP file with the extension changed to PK3. So I figure if I can make the plugin system flexible enough to work with Quake then it will be able to handle anything.
</p>

<p>
	Originally I tried implementing this using <a href="https://nemstools.github.io/pages/Miscellaneous-HLLib.html" rel="external nofollow">HLLib from Nem's Tools</a>. This library supports Quake formats and more, but I had a lot of problems with it, and got better results when I just wrote my own PAK loading code. WAD files were an interesting challenge. There is no real Quake texture format, so loading the raw texture data from the package made no sense. The most interesting breakthrough in this process was how I handled WAD textures. I finally figured out I can make the WAD loader return a block of data that forms a DDS file when a texture is loaded, even through the texture name has no extension. This tricks the engine into thinking the WAD package contains a DDS file which can easily be loaded, when the reality is quite different. This introduces an important concept, that package plugins don't necessarily have to return the exact data they contain, but instead can return files in a ready-to-use format, and the game engine will never know the difference.
</p>

<p>
	The resulting plugin allows you to easily extract and load textures from the Quake game files. This code will extract textures from a single WAD file.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">#include</span><span class="pln"> </span><span class="str">"UltraEngine.h"</span><span class="pln">

</span><span class="kwd">using</span><span class="pln"> </span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">UltraEngine</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> argc</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pun">*</span><span class="pln"> argv</span><span class="pun">[])</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="com">// Load Quake file loader plugin</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> qplg </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPlugin</span><span class="pun">(</span><span class="str">"Plugins/QuakeLoader"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Load FreeImage texture plugin</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> fiplg </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPlugin</span><span class="pun">(</span><span class="str">"Plugins/FITextureLoader"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// WAD to download</span><span class="pln">
    </span><span class="typ">WString</span><span class="pln"> wadfile </span><span class="pun">=</span><span class="pln"> </span><span class="str">"bigcastle"</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// Download a WAD package</span><span class="pln">
    </span><span class="typ">DownloadFile</span><span class="pun">(</span><span class="str">"https://www.quaketastic.com/files/texture_wads/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> wadfile </span><span class="pun">+</span><span class="pln"> </span><span class="str">".wad"</span><span class="pun">,</span><span class="pln"> wadfile </span><span class="pun">+</span><span class="pln"> </span><span class="str">".wad"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Load the package</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> wad </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPackage</span><span class="pun">(</span><span class="pln">wadfile </span><span class="pun">+</span><span class="pln"> </span><span class="str">".wad"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Create a subdirectory to save images in</span><span class="pln">
    </span><span class="typ">CreateDir</span><span class="pun">(</span><span class="pln">wadfile</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Read all files in the package</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> dir </span><span class="pun">=</span><span class="pln"> wad</span><span class="pun">-&gt;</span><span class="typ">LoadDir</span><span class="pun">(</span><span class="str">""</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">auto</span><span class="pln"> file </span><span class="pun">:</span><span class="pln"> dir</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// Load the image</span><span class="pln">
        </span><span class="kwd">auto</span><span class="pln"> pm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPixmap</span><span class="pun">(</span><span class="pln">file</span><span class="pun">);</span><span class="pln">

        </span><span class="com">// Save as a PNG file</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">pm</span><span class="pun">)</span><span class="pln"> pm</span><span class="pun">-&gt;</span><span class="typ">Save</span><span class="pun">(</span><span class="pln">wadfile </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file </span><span class="pun">+</span><span class="pln"> </span><span class="str">".png"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="typ">OpenDir</span><span class="pun">(</span><span class="pln">wadfile</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	All the files found in the WAD package are saved as PNG images. BSP files will work exactly the same way.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18484" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.706611c24138047eb3922b40d6f125f5.jpeg" rel=""><img alt="image.thumb.jpeg.15c29a116ee568dbaf4bd439fc586213.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18484" data-ratio="77.47" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.15c29a116ee568dbaf4bd439fc586213.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	This code will load a Quake package, extract all the textures from all the maps in the game, and save them to a folder on the desktop. This is done by detecting packages-in-packages (WAD and BSP), which return the file type '3', indicating that they can be treated both as a file and as a folder. Since none of these package formats use any compression, pixmaps can be easily loaded straight out of the file without extracting the entire BSP. Since Quake PAK files don't use compression, the whole system just turns into a very complicated blob of data with pointers that store data all over the place:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">#include</span><span class="pln"> </span><span class="str">"UltraEngine.h"</span><span class="pln">

</span><span class="kwd">using</span><span class="pln"> </span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">UltraEngine</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> argc</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pun">*</span><span class="pln"> argv</span><span class="pun">[])</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="com">// Load Quake file loader plugin</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> qplg </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPlugin</span><span class="pun">(</span><span class="str">"Plugins/QuakeLoader"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Load FreeImage texture plugin</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> fiplg </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPlugin</span><span class="pun">(</span><span class="str">"Plugins/FITextureLoader"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Path to Quake game</span><span class="pln">
    </span><span class="typ">WString</span><span class="pln"> qpath </span><span class="pun">=</span><span class="pln"> </span><span class="str">"C:/Program Files (x86)/Steam/steamapps/common/Quake"</span><span class="pun">;</span><span class="pln">
    </span><span class="com">//WString qpath = "D:/SteamLibrary/steamapps/common/Quake";</span><span class="pln">
  
    </span><span class="com">// Load game package</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> pak </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPackage</span><span class="pun">(</span><span class="pln">qpath </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/id1/PAK1.PAK"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Change the current directory to load files with relative paths</span><span class="pln">
    </span><span class="typ">ChangeDir</span><span class="pun">(</span><span class="pln">qpath </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/id1"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Create a folder to save images in    </span><span class="pln">
    </span><span class="typ">WString</span><span class="pln"> savedir </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/Quake"</span><span class="pun">;</span><span class="pln">
    </span><span class="typ">CreateDir</span><span class="pun">(</span><span class="pln">savedir</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Load the main package directory</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> dir </span><span class="pun">=</span><span class="pln"> pak</span><span class="pun">-&gt;</span><span class="typ">LoadDir</span><span class="pun">(</span><span class="str">"maps"</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">auto</span><span class="pln"> file </span><span class="pun">:</span><span class="pln"> dir</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// Print the file name</span><span class="pln">
        </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">file</span><span class="pun">);</span><span class="pln">

        </span><span class="com">// Get the file type</span><span class="pln">
        </span><span class="typ">int</span><span class="pln"> type </span><span class="pun">=</span><span class="pln"> </span><span class="typ">FileType</span><span class="pun">(</span><span class="str">"maps/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file</span><span class="pun">);</span><span class="pln">

        </span><span class="com">// If a package-in-a-package is found load its contents (BSP and WAD)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="lit">3</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">auto</span><span class="pln"> subdir </span><span class="pun">=</span><span class="pln"> pak</span><span class="pun">-&gt;</span><span class="typ">LoadDir</span><span class="pun">(</span><span class="str">"maps/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file</span><span class="pun">);</span><span class="pln">
            </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> n </span><span class="pun">&lt;</span><span class="pln"> subdir</span><span class="pun">.</span><span class="pln">size</span><span class="pun">();</span><span class="pln"> </span><span class="pun">++</span><span class="pln">n</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">{</span><span class="pln">
                </span><span class="com">// Load the texture (plugin will return DDS files)</span><span class="pln">
                </span><span class="kwd">auto</span><span class="pln"> pm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPixmap</span><span class="pun">(</span><span class="str">"maps/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> subdir</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]);</span><span class="pln">

                </span><span class="com">// Save to PNG</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">pm</span><span class="pun">)</span><span class="pln"> pm</span><span class="pun">-&gt;</span><span class="typ">Save</span><span class="pun">(</span><span class="pln">savedir </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> subdir</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">".png"</span><span class="pun">);</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="typ">OpenDir</span><span class="pun">(</span><span class="pln">savedir</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	Here is the result:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18483" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.61d281fb681d9c90115f25ced3bb8498.jpeg" rel=""><img alt="image.thumb.jpeg.0b413ab419afe5946ace68c8ef4391d7.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18483" data-ratio="77.47" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.0b413ab419afe5946ace68c8ef4391d7.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	Since all of these images can be loaded as pixmaps, does that mean they can also be loaded as a texture? I had to know the answer, so I tried this code:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">#include</span><span class="pln"> </span><span class="str">"UltraEngine.h"</span><span class="pln">

</span><span class="kwd">using</span><span class="pln"> </span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">UltraEngine</span><span class="pun">;</span><span class="pln">

</span><span class="typ">int</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> argc</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pun">*</span><span class="pln"> argv</span><span class="pun">[])</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="com">//Get the displays</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> displays </span><span class="pun">=</span><span class="pln"> </span><span class="typ">GetDisplays</span><span class="pun">();</span><span class="pln">

    </span><span class="com">//Create a window</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> window </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateWindow</span><span class="pun">(</span><span class="str">"Ultra Engine"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1280</span><span class="pun">,</span><span class="pln"> </span><span class="lit">720</span><span class="pun">,</span><span class="pln"> displays</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> WINDOW_CENTER </span><span class="pun">|</span><span class="pln"> WINDOW_TITLEBAR</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Create a world</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> world </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateWorld</span><span class="pun">();</span><span class="pln">

    </span><span class="com">//Create a framebuffer</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> framebuffer </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateFramebuffer</span><span class="pun">(</span><span class="pln">window</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Create a camera</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> camera </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateCamera</span><span class="pun">(</span><span class="pln">world</span><span class="pun">);</span><span class="pln">
    camera</span><span class="pun">-&gt;</span><span class="typ">SetClearColor</span><span class="pun">(</span><span class="lit">0.125</span><span class="pun">);</span><span class="pln">
    camera</span><span class="pun">-&gt;</span><span class="typ">SetPosition</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">2</span><span class="pun">);</span><span class="pln">
    camera</span><span class="pun">-&gt;</span><span class="typ">SetFov</span><span class="pun">(</span><span class="lit">70</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Create a light</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> light </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBoxLight</span><span class="pun">(</span><span class="pln">world</span><span class="pun">);</span><span class="pln">
    light</span><span class="pun">-&gt;</span><span class="typ">SetRotation</span><span class="pun">(</span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">35</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
    light</span><span class="pun">-&gt;</span><span class="typ">SetRange</span><span class="pun">(-</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
    light</span><span class="pun">-&gt;</span><span class="typ">SetColor</span><span class="pun">(</span><span class="lit">2</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Create a model</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateBox</span><span class="pun">(</span><span class="pln">world</span><span class="pun">);</span><span class="pln">
    
    </span><span class="com">// Load Quake file loader plugin</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> qplg </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPlugin</span><span class="pun">(</span><span class="str">"Plugins/QuakeLoader"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// WAD to download</span><span class="pln">
    </span><span class="typ">WString</span><span class="pln"> wadfile </span><span class="pun">=</span><span class="pln"> </span><span class="str">"bigcastle"</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// Download a WAD package</span><span class="pln">
    </span><span class="typ">DownloadFile</span><span class="pun">(</span><span class="str">"https://www.quaketastic.com/files/texture_wads/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> wadfile </span><span class="pun">+</span><span class="pln"> </span><span class="str">".wad"</span><span class="pun">,</span><span class="pln"> wadfile </span><span class="pun">+</span><span class="pln"> </span><span class="str">".wad"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Load the package</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> wad </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPackage</span><span class="pun">(</span><span class="pln">wadfile </span><span class="pun">+</span><span class="pln"> </span><span class="str">".wad"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Load a texture from the WAD package</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> tex </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadTexture</span><span class="pun">(</span><span class="str">"sclock"</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Create a material</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> mtl </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateMaterial</span><span class="pun">();</span><span class="pln">
    mtl</span><span class="pun">-&gt;</span><span class="typ">SetTexture</span><span class="pun">(</span><span class="pln">tex</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Apply the material to the box</span><span class="pln">
    model</span><span class="pun">-&gt;</span><span class="typ">SetMaterial</span><span class="pun">(</span><span class="pln">mtl</span><span class="pun">);</span><span class="pln">

    </span><span class="com">//Main loop</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">window</span><span class="pun">-&gt;</span><span class="typ">Closed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> and window</span><span class="pun">-&gt;</span><span class="typ">KeyDown</span><span class="pun">(</span><span class="pln">KEY_ESCAPE</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        model</span><span class="pun">-&gt;</span><span class="typ">Turn</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
        world</span><span class="pun">-&gt;</span><span class="typ">Update</span><span class="pun">();</span><span class="pln">
        world</span><span class="pun">-&gt;</span><span class="typ">Render</span><span class="pun">(</span><span class="pln">framebuffer</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	Here is the result, a texture loaded straight out of a Quake WAD file!
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18486" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.jpeg.a83455b98d27a01c1d320bafac8e3c74.jpeg" rel=""><img alt="image.thumb.jpeg.a3ff0e25662f1bf5f9f612c9322f5b58.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18486" data-ratio="53.07" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_03/image.thumb.jpeg.a3ff0e25662f1bf5f9f612c9322f5b58.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	To reiterate, testing with Quake files helped me to come up with two important design features:
</p>

<ul>
	<li>
		Packages-in-packages, indicated with the file type '3'.
	</li>
	<li>
		Packages do not necessarily have to return the same exact data they contain, and can instead prepare the data in a ready-to-use format, when that is desirable. The decision to base file type detection on the contents of the file instead of the file name extension worked well here, and allowed me to load the extension-less WAD texture names as DDS files.
	</li>
</ul>

<p>
	Some of these images came from "bigcastle.wad". I don't know who the original author is.
</p>

<p>
	If you are interested, you can read more about the weird world of Quake file formats <a href="https://six-of-one.github.io/quake-specifications/" rel="external nofollow">here</a>, although I must warn you that specification is not complete! <img alt=":lol:" data-emoticon="" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" title=":lol:" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/emoticons/default_laugh.png">
</p>
]]></description><guid isPermaLink="false">2814</guid><pubDate>Mon, 06 Mar 2023 06:48:00 +0000</pubDate></item><item><title>Editor Development Continues</title><link>https://www.leadwerks.com/community/blogs/entry/2807-editor-development-continues/</link><description><![CDATA[<p>
	I wanted to take some time to investigate geospatial features and see if it was possible to use GIS systems with Ultra Engine. My investigations were a success and resulted in some beautiful lunar landscapes in a relatively short period of time in the new game engine.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18446" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.jpeg.8e3d9fcac1e37d102500889867aae9eb.jpeg" rel=""><img alt="image.thumb.jpeg.d7a8671ba135c2e045aa721ed90e264b.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18446" data-ratio="46.53" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.thumb.jpeg.d7a8671ba135c2e045aa721ed90e264b.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	I have plans for this, but nothing I can say anything specific about right now, so now I am working on the editor again.
</p>

<p>
	Leadwerks had a very well-defined workflow, which made it simple to use but also somewhat limited. Ultra goes in the opposite direction, with extremely flexible support for plugins and extensions. It's a challenge to keep things together because if you make things to flexible it just turns into indecisiveness, and nothing will ever get finished that way. I think I am finding a good balance or things that should be hard-coded and things that should be configurable.
</p>

<p>
	Instead of separate windows for viewing models, textures, and materials, we just have one asset editor window, with tabs for separate items. The texture interface is the simplest and just shows some information about the asset on the left-side panel.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18447" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.jpeg.e170e8dbf7ed29ae721d516525366383.jpeg" rel=""><img alt="image.thumb.jpeg.6133ccf7dc9dceb6b8b2b01205222de2.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18447" data-ratio="68.80" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.thumb.jpeg.6133ccf7dc9dceb6b8b2b01205222de2.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	When you select the <strong>Save as</strong> menu item, the save file dialog is automatically populated with information loaded from all the plugins the engine has loaded. You can add support for new image formats at any time just by dropping a plugin in the right folder. In fact, the only hard-coded format in the list is DDS.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18448" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.jpeg.853693ea05179d9037588d8d92910c82.jpeg" rel=""><img alt="image.thumb.jpeg.baa3e8579dff1883d6b11f5b72b2c1cc.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18448" data-ratio="68.80" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.thumb.jpeg.baa3e8579dff1883d6b11f5b72b2c1cc.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	You can save to an image format, or to the more advanced DDS and KTX2 formats, which include mipmaps and other features. Even more impressive is the ability to load Leadwerks texture files (TEX) and save them directly into DDS format in their original pixel format, with no loss of image quality. Here we have a Leadwerks texture that uses DXT1 compression, converted to DDS without recompressing the pixels:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpeg" data-fileid="18449" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.jpeg.cb29afaae2b1ab9f27a3bae92af63b65.jpeg" rel=""><img alt="image.thumb.jpeg.cbba053b61382cab8b1ab0756aaaf3ba.jpeg" class="ipsImage ipsImage_thumbnailed" data-fileid="18449" data-ratio="65.20" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2023_02/image.thumb.jpeg.cbba053b61382cab8b1ab0756aaaf3ba.jpeg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	There are several tricks to make the new editor start up as fast as possible. One of these is that the various windows the editor uses don't actually get created until the first time they are used. This saves a little bit of time to give the application a snappier feel at startup.
</p>

<p>
	I hope the end result will provide a very intuitive workflow like the Leadwerks editor has, with enough power and flexibility to make the editor into a living growing platform with many people adding new capabilities for you to use. I think this is actually my favorite stuff to work on because this is where all the technology is exposed to you, the end user, and I get to carefully hand-craft an interface that makes you feel happy when you use it.
</p>
]]></description><guid isPermaLink="false">2807</guid><pubDate>Sun, 26 Feb 2023 17:22:07 +0000</pubDate></item><item><title>AI-Generated Game Textures and Concept Art</title><link>https://www.leadwerks.com/community/blogs/entry/2793-ai-generated-game-textures-and-concept-art/</link><description><![CDATA[<p>
	<a href="https://www.midjourney.com/home/" rel="external nofollow">Midjourney</a> is an AI art generator you can interact with on Discord to make content for your game engine. To use it, first<a href="https://discord.gg/midjourney" rel="external nofollow"> join the Discord channel</a> and enter one of the "newbie" rooms. To generate a new image, just type "/imagine" followed by the keywords you want to use. The more descriptive you are, the better. After a few moments four different images will be shown. You can upsample or create new variations of any of the images the algorithm creates.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="https://leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/imagine_command_popup_click.gif.2c5d4081695a74cd431095b9c9b5c0b4.gif" rel="external nofollow"><img alt="imagine_command_popup_click.thumb.gif.3185fa8d74dc59007f76813e6319845e.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="17682" data-ratio="70.87" style="width:600px;height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/imagine_command_popup_click.thumb.gif.3185fa8d74dc59007f76813e6319845e.gif" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	And then the magic begins:
</p>

<p>
	<img alt="167756032-0059cb74-d437-4747-8778-902c03403be6.gif.bcb3dc6ef99057b813c417a3f1632650.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="17683" data-ratio="100.00" style="height:auto;" width="400" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/167756032-0059cb74-d437-4747-8778-902c03403be6.gif.bcb3dc6ef99057b813c417a3f1632650.gif" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	Here are some of the images I "created" in a few minutes using the tool:
</p>

<p>
	<img alt="JoshKlint_ukraine_post-apocalyptic_urban_environment_wasteland__119c3db6-173c-40b8-8675-f7e7a4f101d6.png.5f147f95c98d568122cbe811582bf1fc.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17681" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/JoshKlint_ukraine_post-apocalyptic_urban_environment_wasteland__119c3db6-173c-40b8-8675-f7e7a4f101d6.png.5f147f95c98d568122cbe811582bf1fc.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"><img alt="JoshKlint_strogg_enemy_210988df-22e0-43f3-8856-bbb3f2868f8c.png.f94ecc9284eb4e0646b8ed754ed1ad06.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17680" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/JoshKlint_strogg_enemy_210988df-22e0-43f3-8856-bbb3f2868f8c.png.f94ecc9284eb4e0646b8ed754ed1ad06.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"><img alt="JoshKlint_quake_map_id_software_game_9ea0bca3-83ac-4694-8da0-97cb18ef11bb.png.43a1f351f62589594e158c746f5e1efb.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17678" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/JoshKlint_quake_map_id_software_game_9ea0bca3-83ac-4694-8da0-97cb18ef11bb.png.43a1f351f62589594e158c746f5e1efb.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"><img alt="JoshKlint_quake_map_id_software_game_3b7b564d-8737-4367-9175-cba17ca4c358.png.8e21166df257e1261eb230aae6bf8f13.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17677" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/JoshKlint_quake_map_id_software_game_3b7b564d-8737-4367-9175-cba17ca4c358.png.8e21166df257e1261eb230aae6bf8f13.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"><img alt="JoshKlint_doom_2016_game_level_544a65f1-5a2f-43f7-9fd1-4ce96561b5dd.png.059dc8d28f2f85efc669a3a57798865a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17676" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/JoshKlint_doom_2016_game_level_544a65f1-5a2f-43f7-9fd1-4ce96561b5dd.png.059dc8d28f2f85efc669a3a57798865a.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"><img alt="JoshKlint_cyberpunk_cthulu_204afbaf-acb1-461b-8a44-e0400716fa5f.png.0aac204066b166274d6c2b096b61dca2.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17675" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/JoshKlint_cyberpunk_cthulu_204afbaf-acb1-461b-8a44-e0400716fa5f.png.0aac204066b166274d6c2b096b61dca2.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"><img alt="JoshKlint_cyberpunk_city_photorealistic_4aa102b8-b8bc-401d-ba8f-9f8ffd6a55a8.png.d57eb2c2f7b3230e0f835ca8fec2406b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17674" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/JoshKlint_cyberpunk_city_photorealistic_4aa102b8-b8bc-401d-ba8f-9f8ffd6a55a8.png.d57eb2c2f7b3230e0f835ca8fec2406b.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"><img alt="8b3d5967-0fc7-4ebe-842d-b2155fdddc56_progress_image_96.jpg.ed18179feffd2b7b171aed8de66ce957.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17673" data-ratio="100.00" style="height:auto;" width="512" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_09/8b3d5967-0fc7-4ebe-842d-b2155fdddc56_progress_image_96.jpg.ed18179feffd2b7b171aed8de66ce957.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	I'm really surprised by the results. I didn't think it was possible for AI to demonstrate this level of spatial reasoning. You can clearly see that it has some kind of understanding of 3D perspective and lighting. Small errors like the misspelling of "Quake" as "Quke" only make it creepier, because it means the AI has a deep level of understanding and isn't just copying and pasting parts of images.
</p>

<p>
	What do you think about AI-generated artwork? Do you have any of your own images you would like to show off? Let me know in the comments below.
</p>
]]></description><guid isPermaLink="false">2793</guid><pubDate>Fri, 30 Dec 2022 19:19:36 +0000</pubDate></item><item><title>Tightening up the graphics and tripling down on glTF</title><link>https://www.leadwerks.com/community/blogs/entry/2782-tightening-up-the-graphics-and-tripling-down-on-gltf/</link><description><![CDATA[<p>
	As I have stated before, my goa for this game enginel is not to build a marketplace of 3D models, but instead to just make sure our model loading code reliably loads all 3D models that are compliant with the glTF specification. I started testing more 3D models from Sketchfab, and found that many of them are using specular/gloss materials. At first I thought I could just fudge the result, but I wasn't getting very good results, and the Windows 10 3D Object Viewer was showing them perfectly. This made me very upset because I feel that the software I make for you should be the absolute best thing possible. So I went ahead and implemented the actual <a href="https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness/README.md" rel="external nofollow">KHR_materials_pbrSpecularGlossiness</a> extension. Here are the results:
</p>

<p>
	<img alt="Untitled.jpg.8452d9b35f86cc9a041c3e3c4ff95b42.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17444" data-ratio="99.11" style="height:auto;" width="674" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Untitled.jpg.8452d9b35f86cc9a041c3e3c4ff95b42.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	It looks quite good without breaking the specular/gloss approach. I think this would be an ideal way to upgrade the Leadwerks renderer without making changes that are too big.
</p>

<p>
	To implement this, I used the <a href="https://github.khronos.org/glTF-Sample-Viewer-Release/" rel="external nofollow">glTF sample viewer</a> source code, which is a bit of a reference renderer for glTF materials. It has changed quite a lot since I first used it as a guide to implementing PBR materials. I decided I might as well integrated the latest code into our renderer. The shader code is very well organized, so it was pretty simple to integrate into my latest code. The glTF materials system is great because it provides a standardized materials system with a lot of advanced effects, along with a reference renderer that shows exactly how the materials are supposed to appear. This means that any engine or tool that is conforms to the glTF standard will always display the same materials the same way, while still allowing additional more specialized features like shadows and post-processing effects to be added.
</p>

<p>
	In addition to revising our metal-roughness code, I went ahead and added support for some more features. The <a href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_clearcoat" rel="external nofollow">KHR_materials_clearcoat</a> extension basically makes cars look like cars:
</p>

<div class="ipsEmbeddedVideo">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" title="PBR rendering with glTF" width="200" data-embed-src="https://www.youtube.com/embed/zJ25fVl1Wo4?feature=oembed"></iframe>
	</div>
</div>

<p>
	It adds an extra layer of reflections that gives a dark surface a big boost, and can even use a separate normal map for the clearcoat layer:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="gif" data-fileid="17432" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/image_1656582469.gif.7cd7422272b31be5e89fda385cf2121f.gif" rel=""><img alt="image_1656582469.thumb.gif.d4d318d2888c1efb72a8efa9e0d68029.gif" class="ipsImage ipsImage_thumbnailed" data-fileid="17432" data-ratio="70.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/image_1656582469.thumb.gif.d4d318d2888c1efb72a8efa9e0d68029.gif" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	The <a href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen" rel="external nofollow">KHR_materials_sheen</a> extension makes cloth look like cloth. You can see for yourself in the image below. The appearance kind of makes my skin crawl because I can't stand the feeling of velvet / velour. Apparently this is some kind of common tactile hypersensitivity and I actually get a feeling like electricity shooting through my spine just looking at this image, so I think the effect is working well:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17433" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image3.jpg.61e49e55d402b42002b663080340aca0.jpg" rel=""><img alt="Image3.thumb.jpg.0af406093a005891434f518c2bb0193e.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17433" data-ratio="78.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image3.thumb.jpg.0af406093a005891434f518c2bb0193e.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	Another creepy example. Just the thought of running my hand over that fuzzy orange fabric is making me shiver:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17441" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Untitled.jpg.04996bc50d56e135726f6907f80771e0.jpg" rel=""><img alt="Untitled.thumb.jpg.b2ad5fc15a99a8d80a839ad71e1b4442.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17441" data-ratio="75.07" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Untitled.thumb.jpg.b2ad5fc15a99a8d80a839ad71e1b4442.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	The <a href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_transmission" rel="external nofollow">KHR_materials_transmission</a> extensions provides a physically accurate model for transparent surfaces, with support for refraction. Here's a shot of refraction bending the background image:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17442" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Untitled.jpg.628a108414ed82793d6c63784248473a.jpg" rel=""><img alt="Untitled.thumb.jpg.ffbbe5b450c75e9cfb7a5c0899f31ccd.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17442" data-ratio="66.93" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Untitled.thumb.jpg.ffbbe5b450c75e9cfb7a5c0899f31ccd.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	This also supports roughness, for making the refracted background appear blurry like frosted glass:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17443" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Untitled.jpg.adae5024b689378870298b7284c07f10.jpg" rel=""><img alt="Untitled.thumb.jpg.5affc5316e1042e42106a7289d205717.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17443" data-ratio="62.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Untitled.thumb.jpg.5affc5316e1042e42106a7289d205717.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	I feel the background is too pixellated and should be improved. This problem <a href="https://github.com/KhronosGroup/glTF-Sample-Viewer/issues/409" rel="external nofollow">occurs in the reference renderer</a>, and I have several ideas on how to solve it.
</p>

<p>
	The transparency system is very advanced, and introduces a lot of interesting new commands to the engine:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="typ">Material</span><span class="pun">::</span><span class="typ">SetIndexOfRfraction</span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">float</span><span class="pln"> ior</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Material</span><span class="pun">::</span><span class="typ">SetThickness</span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">float</span><span class="pln"> thickness</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Material</span><span class="pun">::</span><span class="typ">SetTransmission</span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">float</span><span class="pln"> transmission</span><span class="pun">)</span></pre>

<p>
	These features will give you reliable support for loading glTF models from any source, as well as the best image quality possible for your games. I think realistic materials in VR will also make a big difference in bridging the uncanny valley. And all of this is done with the fastest possible performance for VR.
</p>

<p>
	And yes, of course I used a gamepad to do all of this.
</p>

<div class="ipsEmbeddedVideo">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="150" title="Game testers" width="200" data-embed-src="https://www.youtube.com/embed/BRWvfMLl4ho?feature=oembed"></iframe>
	</div>
</div>

<p>
	 
</p>

<p>
	 
</p>
]]></description><guid isPermaLink="false">2782</guid><pubDate>Fri, 30 Dec 2022 19:18:36 +0000</pubDate></item><item><title>Ultra Engine Client App</title><link>https://www.leadwerks.com/community/blogs/entry/2788-ultra-engine-client-app/</link><description><![CDATA[<p>
	I've been working hard getting all the rendering features to work together in one unified system. Ultra Engine, more than any renderer I have worked on, takes a lot of different features and integrates them into one physically-based graphics system. A lot of this is due to the excellent PBR materials system that Khronos glTF provides, and then there are my own features that are worked into this, like combined screen-space and voxel ray traced reflections.
</p>

<div class="ipsEmbeddedVideo">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" title="Gi specular reflection with latency" width="200" data-embed-src="https://www.youtube.com/embed/MXpI4ZPx2zE?feature=oembed"></iframe>
	</div>
</div>

<p>
	Anyways, it's a lot of difficult work, and I decided to take a "break" and focus something else for a few days.
</p>

<p>
	Before Leadwerks was on Steam, it had a web installer that would fetch a list of files from our server and any files that were missing, or were newer than the locally stored ones. There was no system for detecting updates, you just pressed the update button and the updater ran. The backend for this system was designed by <a contenteditable="false" data-ipshover="" data-ipshover-target="https://www.leadwerks.com/community/profile/4-klepto2/?do=hovercard" data-mentionid="4" href="https://www.leadwerks.com/community/profile/4-klepto2/" rel="">@klepto2</a> and it functioned well for what it needed to do.
</p>

<p>
	<img alt="install.png.ccf6c80217f7903c3b7a2c5aa2db11cf.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17486" data-ratio="79.38" style="height:auto;" width="616" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/install.png.ccf6c80217f7903c3b7a2c5aa2db11cf.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	With Ultra App Kit, I created a simple tool to generate projects. This introduced the account authentication / signin system, which was sort of superfluous for this application, but it was a good way to test it out:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17487" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/ss_1d7bdedccece25796c428121ef4d3d549da152ac.jpg.4fc1c23faf11638555cdca6b6f2b960f.jpg" rel=""><img alt="ss_1d7bdedccece25796c428121ef4d3d549da152ac.thumb.jpg.31db11ed0fa194983b9ebace00fde0d1.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17487" data-ratio="60.00" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/ss_1d7bdedccece25796c428121ef4d3d549da152ac.thumb.jpg.31db11ed0fa194983b9ebace00fde0d1.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	With Ultra Engine, I wanted some characteristics of both these applications. I wrote a new backend in about a day that handles updates. A PHP script authenticates the user and verifies product ownership, fetches a list of files, and retrieves files. Since C++ library files tend to be huge, I found it was necessary to add a compression system, so the script returns a zip compressed file. Of course, you don't want the server to be constantly creating zip files, so it caches the file and updates the zip only when I upload a new copy of the file. There's also a cache for the info retrieval, which is returned in JSON format, so it's easy to read in C++.
</p>

<p>
	For the front end, I took inspiration from the Github settings page, which I thought looked nice:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="17484" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/github.png.487bb7022c7e686105d01ae0c165c711.png" rel=""><img alt="github.thumb.png.005b32b6527a54c6524357f76b4487e8.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17484" data-ratio="61.60" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/github.thumb.png.005b32b6527a54c6524357f76b4487e8.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	And here's what I came up with. Projects will show when they are outdated and need to be updated (if a file in the template the project came from was changed). Each of the sections contains info and links to various topics. There's a lot there, but none of it feels extraneous to me. This is all made with the built-in GUI system. No HTML is used at all:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="17485" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Image1.png.c9a01b20067f537629a0db9a32499ef3.png" rel=""><img alt="Image1.thumb.png.db23b1f4711f589ae806f27d90ab0697.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17485" data-ratio="62.80" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_07/Image1.thumb.png.db23b1f4711f589ae806f27d90ab0697.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	The <a href="https://invisioncommunity.com/developers/rest-api" rel="external nofollow">Invision Power REST API</a> is extremely interesting. It allows authentication of accounts and purchases, but it can be made to do a lot of other things.
</p>

<ul>
	<li>
		Post a forum topic: <a href="https://invisioncommunity.com/developers/rest-api?endpoint=forums/topics/POSTindex" rel="external nofollow">https://invisioncommunity.com/developers/rest-api?endpoint=forums/topics/POSTindex</a>
	</li>
	<li>
		Upload an image to the gallery: <a href="https://invisioncommunity.com/developers/rest-api?endpoint=gallery/images/GETindex" rel="external nofollow">https://invisioncommunity.com/developers/rest-api?endpoint=gallery/images/GETindex</a>
	</li>
	<li>
		Download a file: <a href="https://invisioncommunity.com/developers/rest-api?endpoint=downloads/files/GETitem" rel="external nofollow">https://invisioncommunity.com/developers/rest-api?endpoint=downloads/files/GETitem</a>
	</li>
	<li>
		Unlock achievements: <a href="https://invisioncommunity.com/developers/rest-api?endpoint=core/members/POSTitem_achievements_awardbadge" rel="external nofollow">https://invisioncommunity.com/developers/rest-api?endpoint=core/members/POSTitem_achievements_awardbadge</a>
	</li>
</ul>

<p>
	None of that is very important right now, but it does provide some interesting ideas for future development of the game engine.
</p>
]]></description><guid isPermaLink="false">2788</guid><pubDate>Fri, 30 Dec 2022 13:18:41 +0000</pubDate></item><item><title>Exporting a glTF model from 3ds Max</title><link>https://www.leadwerks.com/community/blogs/entry/2791-exporting-a-gltf-model-from-3ds-max/</link><description><![CDATA[<p>
	Autodesk 3ds Max now supports export of glTF models, as well as a new glTF material type. The process of setting up and exporting glTF models is pretty straightforward, but there are a couple of little details I wanted to point out to help prevent you from getting stuck. For this article, I will be working with the <a href="https://polyhaven.com/a/rock_moss_set_01" rel="external nofollow">moss rocks 1 model pack</a> from Polyhaven.
</p>

<p>
	Getting geometry into 3ds Max is simple enough. I imported the model as an FBX file.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17604" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/Untitled.jpg.47669faa350b8e9eb4f88b819210c8d8.jpg" rel=""><img alt="Untitled.thumb.jpg.b60b85c057710551be047f4bbf8b5788.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17604" data-ratio="53.87" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/Untitled.thumb.jpg.b60b85c057710551be047f4bbf8b5788.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	To set up the material, I opened the compact material editor and set the first slot to be a glTF material.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="https://leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/1284079737_materialeditor.png.faa50b667293f67d4245166c62235fa4.png" rel="external nofollow"><img alt="1791106475_materialeditor.thumb.png.35f5719206ef9e12f2d764e50276b39a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17605" data-ratio="159.91" style="width:400px;height:auto;" width="469" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/1791106475_materialeditor.thumb.png.35f5719206ef9e12f2d764e50276b39a.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	Press the button for the base color map, and very importantly choose the<strong> General &gt; Bitmap</strong> map type. <strong><u>Do not</u> </strong>choose OSL &gt; Bitmap Lookup or your textures won't export at all.
</p>

<p>
	<img alt="2124304387_materialeditor.png.b19201e2f8aedc210eb5cb2a97df606a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17606" data-ratio="121.54" style="width:450px;height:auto;" width="557" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/2124304387_materialeditor.png.b19201e2f8aedc210eb5cb2a97df606a.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	Select your base color texture, then do the same thing with the normal and roughness maps, if you have them. 3ds Max treats metal / roughness as two separate textures, although you might be able to use the same texture both if it grabs the data from the green (roughness) and blue (metal) channels. This is something I don't know yet.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17607" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/1599536517_materialeditor.jpg.db87f99d8b6ee641cddd4a712399ff20.jpg" rel=""><img alt="1867673025_materialeditor.thumb.jpg.bfdac7b3089aa0ad50b6d33f8fc6fba7.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17607" data-ratio="54.93" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/1867673025_materialeditor.thumb.jpg.bfdac7b3089aa0ad50b6d33f8fc6fba7.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	Select the File &gt; Export menu item to bring up the glTF export dialog. Uncheck the "Export glTF binary" option because we don't want to pack our model and textures into a single file: I don't know what the baked / original material option does because I don't see any difference when I use it.
</p>

<p>
	<img alt="export.png.801b5c9abbf0daed5eac65a4552e3760.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17608" data-ratio="60.58" style="width:500px;height:auto;" width="652" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/export.png.801b5c9abbf0daed5eac65a4552e3760.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	At this point you should have a glTF file that is visible in any glTF model viewer.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17609" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/export.jpg.05488280419cf3cb87000b7ed5273841.jpg" rel=""><img alt="export.thumb.jpg.d39d6f0d99c7b2a2100437411422f5f4.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17609" data-ratio="62.93" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/export.thumb.jpg.d39d6f0d99c7b2a2100437411422f5f4.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	Now something slightly weird max does is it generates some new textures for some of the maps. This is probably because it is combining different channels to produce final images. In this case, none of our textures need to be combined, so it is just a small annoyance. A .log file will be saved as well, but these can be safely deleted.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17610" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/export.jpg.eceda46751422630651c1df051886fc4.jpg" rel=""><img alt="export.thumb.jpg.f0b6aea9744463a8e9f931a57889b49d.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17610" data-ratio="56.40" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/export.thumb.jpg.f0b6aea9744463a8e9f931a57889b49d.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p>
	You can leave the images as-is, or you can open up the glTF file in a text editor and manually change the image file names back to the original files:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted"><span class="pln">  </span><span class="str">"images"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      </span><span class="str">"uri"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"rock_moss_set_01_nor_gl_4k.jpg"</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      </span><span class="str">"uri"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"M_01___Defaultbasecolortexture.jpeg"</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      </span><span class="str">"uri"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"M_01___Defaultmetallicroughnesstex.jpeg"</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">],</span></pre>

<p>
	Finally, we're going to add LODs using the Mesh Editor &gt; ProOptimizer modifier. I like these settings, but the most important thing is to make sure "Keep textures" is checked. You can press the F3 key at any time to toggle wireframe view and get a better view of what the optimizer does to your mesh.
</p>

<p>
	<img alt="lod.png.0a137762081528949dc22170dac2effb.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17611" data-ratio="268.11" style="width:200px;height:auto;" width="254" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_08/lod.png.0a137762081528949dc22170dac2effb.png" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png">
</p>

<p>
	Export the file with the same name as the full-resolution model, and add "_lod1" to the end of the file name (before the extension). Then repeat this process saving lod2 and lod3 using 25% and 12.5% for the vertex reduction value in the ProOptimizer modifier.
</p>

<p>
	Here is my example model you can inspect:
</p>

<p>
	<a class="ipsAttachLink" contenteditable="false" data-fileext="zip" data-fileid="17612" href="https://www.leadwerks.com/community/applications/core/interface/file/attachment.php?id=17612" rel="">mossrock1a.zip</a>
</p>

<p>
	Now it is very easy to get 3D models from 3ds max to your game engine.
</p>
]]></description><guid isPermaLink="false">2791</guid><pubDate>Fri, 30 Dec 2022 13:18:03 +0000</pubDate></item><item><title>Ultra Engine Compatibility with Leadwerks Game Engine</title><link>https://www.leadwerks.com/community/blogs/entry/2797-ultra-engine-compatibility-with-leadwerks-game-engine/</link><description><![CDATA[<p>
	As the first release of Ultra Engine approaches, it seems clear that the best way to maximize its usefulness is to make it as compatible as possible with the Leadwerks game engine. To that end, I have implemented the following features.
</p>

<p>
	<strong>Native Loading of Leadwerks File Formats</strong>
</p>

<p>
	Ultra Engine loads and saves DDS, glTF, and OBJ files. Other formats are supported by plugins, both first and potentially third-party, for PNG, JPG, BMP, TGA, TIFF, GIF, HDR, KTX2, and other files. Additionally, all Leadwerks file formats are natively supported without requiring any plugins, so you can load TEX, MDL, PHY, MAT files, as well as full Leadwerks maps. You also have the option to save Leadwerks game engine formats in more up-to-date file formats such as glTF or DDS files with BC5/BC7 compression.
</p>

<p style="text-align:center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17881" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_10/Image1.jpg.fd4d3237b3c6c9bcaffb5fc0c1a5dcd8.jpg" rel=""><img alt="Image1.thumb.jpg.746bef4c6cb739d8ed94dcf212221d4b.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17881" data-ratio="53.73" style="height:auto;" width="750" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_10/Image1.thumb.jpg.746bef4c6cb739d8ed94dcf212221d4b.jpg" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png"></a>
</p>

<p style="text-align:center;">
	<em><span style="font-size:14px;">Testing a scene from the excellent <a href="https://store.steampowered.com/app/1803590/Cyclone/" rel="external nofollow">Cyclone</a> game (available on Steam!)</span></em>
</p>

<p>
	<strong>Classic Specular Shader Family</strong>
</p>

<p>
	PBR materials are wonderful but act quite differently from conventional specular/gloss game materials. I've added a "Classic" shader family which provides an additive specular effect, which may not be realistic but it makes materials expect the way you would expect them to, coming from Leadwerks game engine. When Leadwerks MAT files are loaded, this shader family is automatically applied to them, to get a closer match to their appearance in Leadwerks.
</p>

<p>
	<strong>Leadwerks Translation Layer</strong>
</p>

<p>
	I'm experimenting with a <span>Leadwerks header for Ultra Engine</span>. You can drop this into your Ultra Engine project folder and then start running code with the Leadwerks API. Internally, the Leadwerks game engine commands will be translated into the Ultra Engine API to execute your existing code in the new engine. I don't think this will be 100% perfect due to some differences in the way the two engines work, but I do think it will give you an easy way to get started and provide a gentler transition to Ultra.
</p>

<p>
	This code will actually work with both engines:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted"><span class="com">#include</span><span class="pln"> </span><span class="str">"Leadwerks.h"</span><span class="pln">

</span><span class="kwd">using</span><span class="pln"> </span><span class="kwd">namespace</span><span class="pln"> </span><span class="typ">Leadwerks</span><span class="pun">;</span><span class="pln">

</span><span class="typ">int</span><span class="pln"> main</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> argc</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pun">*</span><span class="pln"> argv</span><span class="pun">[])</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Window</span><span class="pun">*</span><span class="pln"> window </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Window</span><span class="pun">::</span><span class="typ">Create</span><span class="pun">();</span><span class="pln">
  
    </span><span class="typ">Context</span><span class="pun">*</span><span class="pln"> context </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Context</span><span class="pun">::</span><span class="typ">Create</span><span class="pun">(</span><span class="pln">window</span><span class="pun">);</span><span class="pln">
  
    </span><span class="typ">World</span><span class="pun">*</span><span class="pln"> world </span><span class="pun">=</span><span class="pln"> </span><span class="typ">World</span><span class="pun">::</span><span class="typ">Create</span><span class="pun">();</span><span class="pln">
    
    </span><span class="typ">Camera</span><span class="pun">*</span><span class="pln"> camera </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Camera</span><span class="pun">::</span><span class="typ">Create</span><span class="pun">();</span><span class="pln">
    camera</span><span class="pun">-&gt;</span><span class="typ">Move</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">3</span><span class="pun">);</span><span class="pln">
    
    </span><span class="typ">Light</span><span class="pun">*</span><span class="pln"> light </span><span class="pun">=</span><span class="pln"> </span><span class="typ">DirectionalLight</span><span class="pun">::</span><span class="typ">Create</span><span class="pun">();</span><span class="pln">
    light</span><span class="pun">-&gt;</span><span class="typ">SetRotation</span><span class="pun">(</span><span class="lit">35</span><span class="pun">,</span><span class="pln"> </span><span class="lit">35</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">

    </span><span class="typ">Model</span><span class="pun">*</span><span class="pln"> model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Model</span><span class="pun">::</span><span class="typ">Box</span><span class="pun">();</span><span class="pln">
    model</span><span class="pun">-&gt;</span><span class="typ">SetColor</span><span class="pun">(</span><span class="lit">0.0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1.0</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">window</span><span class="pun">-&gt;</span><span class="typ">Closed</span><span class="pun">()</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> window</span><span class="pun">-&gt;</span><span class="typ">KeyDown</span><span class="pun">(</span><span class="typ">Key</span><span class="pun">::</span><span class="typ">Escape</span><span class="pun">))</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">

        model</span><span class="pun">-&gt;</span><span class="typ">Turn</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Time</span><span class="pun">::</span><span class="typ">GetSpeed</span><span class="pun">(),</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">

        </span><span class="typ">Time</span><span class="pun">::</span><span class="typ">Update</span><span class="pun">();</span><span class="pln">
        world</span><span class="pun">-&gt;</span><span class="typ">Update</span><span class="pun">();</span><span class="pln">
        world</span><span class="pun">-&gt;</span><span class="typ">Render</span><span class="pun">();</span><span class="pln">
        context</span><span class="pun">-&gt;</span><span class="typ">Sync</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">

    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	I hope these enhancements give you a more enjoyable experience using Ultra together with the Leadwerks Editor, or as a standalone programming SDK.
</p>
]]></description><guid isPermaLink="false">2797</guid><pubDate>Fri, 30 Dec 2022 13:13:17 +0000</pubDate></item><item><title>Building a single-file 4K HDR skybox with BC6 compression</title><link>https://www.leadwerks.com/community/blogs/entry/2780-building-a-single-file-4k-hdr-skybox-with-bc6-compression/</link><description><![CDATA[<p>
	HDR skyboxes are important in PBR rendering because sky reflections get dampened by surface color. It's not really for the sky itself, but rather we don't want bright reflections to get clamped and washed out, so we need colors that go beyond the visible range of 0-1.
</p>

<p>
	<a href="https://polyhaven.com/hdris" rel="external nofollow">Polyhaven</a> has a large collection of free photo-based HDR environments, but they are all stored in EXR format as sphere maps. What we want are cubemaps stored in a single DDS file, preferably using texture compression.
</p>

<p>
	We're going to use <a href="https://matheowis.github.io/HDRI-to-CubeMap/" rel="external nofollow">this online tool </a>to convert the sphere maps to cubemaps. There are other ways to do this, and some may be better, but this is what I was able to find right away. By default, the page shows a background we can use for testing:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17417" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.881636c2f424a3290299b417c588ec75.jpg" rel=""><img alt="Untitled.thumb.jpg.7b5a8f57c560bdd7f35fd2acdf27c3c3.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17417" data-ratio="75.47" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.7b5a8f57c560bdd7f35fd2acdf27c3c3.jpg"></a>
</p>

<p>
	Let's process and save the image. Since we are just testing, it's fine to use a low resolution for now:
</p>

<p>
	<img alt="Untitled.jpg.051a9e19c0af10e4b89d1adf0a3f5e20.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17423" data-ratio="23.96" style="width:500px;height:auto;" width="747" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.051a9e19c0af10e4b89d1adf0a3f5e20.jpg">
</p>

<p>
	The layout is very important. We will select the last option, which exports each face as a separate image:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17421" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.09bfb8b882a39d7eec51f97994140c86.jpg" rel=""><img alt="Untitled.thumb.jpg.958e147dbe7fe0a153012973a75c9acf.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17421" data-ratio="93.33" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.958e147dbe7fe0a153012973a75c9acf.jpg"></a>
</p>

<p>
	Processing and saving the image will result in a zip file downloaded to your computer, with six files:
</p>

<ul>
	<li>
		px.hdr
	</li>
	<li>
		nx.hdr
	</li>
	<li>
		py.hdr
	</li>
	<li>
		ny.hdr
	</li>
	<li>
		pz.hdr
	</li>
	<li>
		nz.hdr
	</li>
</ul>

<p>
	These images correspond to the positive and negative directions on the X, Y, and Z axes.
</p>

<p>
	We will start by just converting a single image, px.hdr. We will convert this pixmap to the format TEXTURE_BC6H, which corresponds to VK_FORMAT_BC6H_UFLOAT_BLOCK in Vulkan, or DXGI_FORMAT_BC6H_UF16 in the <a href="https://docs.microsoft.com/en-us/windows/win32/direct3d11/bc6h-format" rel="external nofollow">DDS file format</a>. We need to load the FreeImage plugin for HDR file import, and the ISPC plugin for fast BC6H compression. The R16G16B16A16_SFLOAT is an intermediate format we need to convert the 32-bit floating point HDR file to, because the ISPC compressor expects this as the input format for BC6H compression:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="kwd">auto</span><span class="pln"> plg </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPlugin</span><span class="pun">(</span><span class="str">"Plugins/FITextureLoader"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> plg2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPlugin</span><span class="pun">(</span><span class="str">"Plugins/ISPCTexComp"</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">auto</span><span class="pln"> pixmap </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPixmap</span><span class="pun">(</span><span class="typ">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/px.hdr"</span><span class="pun">);</span><span class="pln">
pixmap </span><span class="pun">=</span><span class="pln"> pixmap</span><span class="pun">-&gt;</span><span class="typ">Convert</span><span class="pun">(</span><span class="typ">TextureFormat</span><span class="pun">(</span><span class="pln">VK_FORMAT_R16G16B16A16_SFLOAT</span><span class="pun">));</span><span class="pln">
pixmap </span><span class="pun">=</span><span class="pln"> pixmap</span><span class="pun">-&gt;</span><span class="typ">Convert</span><span class="pun">(</span><span class="pln">TEXTURE_BC6H</span><span class="pun">);</span><span class="pln">
pixmap</span><span class="pun">-&gt;</span><span class="typ">Save</span><span class="pun">(</span><span class="typ">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/px.dds"</span><span class="pun">);</span></pre>

<p>
	When we open the resulting DDS file in Visual Studio it appear quite dark, but I think this is due to how the HDR image is stored. I'm guessing colors are being scaled to a range between zero and one in the HDR file:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="17416" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.png.588728561d9260ae2a883631fb6a184f.png" rel=""><img alt="Untitled.thumb.png.9ed36ac9af9b869f8c7149976bcfd386.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17416" data-ratio="62.40" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.png.9ed36ac9af9b869f8c7149976bcfd386.png"></a>
</p>

<p>
	Now that we know how to convert a save a single 2D texture, let's try saving a more complex cube map texture with mipmaps. to do this, we need to pass an an array of pixmaps to the SaveTexture function, containing all the mipmaps in the DDS file, in the correct order. <a href="https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-file-layout-for-cubic-environment-maps" rel="external nofollow">Microsoft's documentation on the DDS file format</a> is very helpful here.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="pln">std</span><span class="pun">::</span><span class="typ">vector</span><span class="pun">&lt;</span><span class="pln">std</span><span class="pun">::</span><span class="pln">shared_ptr</span><span class="pun">&lt;</span><span class="typ">Pixmap</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> mipchain</span><span class="pun">;</span><span class="pln">
</span><span class="typ">WString</span><span class="pln"> files</span><span class="pun">[</span><span class="lit">6</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="str">"px.hdr"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"nx.hdr"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"py.hdr"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"ny.hdr"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"pz.hdr"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"nz.hdr"</span><span class="pln"> </span><span class="pun">};</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="typ">int</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> n </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">6</span><span class="pun">;</span><span class="pln"> </span><span class="pun">++</span><span class="pln">n</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">auto</span><span class="pln"> pixmap </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadPixmap</span><span class="pun">(</span><span class="typ">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> files</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]);</span><span class="pln">
    pixmap </span><span class="pun">=</span><span class="pln"> pixmap</span><span class="pun">-&gt;</span><span class="typ">Convert</span><span class="pun">(</span><span class="typ">TextureFormat</span><span class="pun">(</span><span class="pln">VK_FORMAT_R16G16B16A16_SFLOAT</span><span class="pun">));</span><span class="pln">
    </span><span class="typ">Assert</span><span class="pun">(</span><span class="pln">pixmap</span><span class="pun">);</span><span class="pln">
    mipchain</span><span class="pun">.</span><span class="pln">push_back</span><span class="pun">(</span><span class="pln">pixmap</span><span class="pun">-&gt;</span><span class="typ">Convert</span><span class="pun">(</span><span class="pln">TEXTURE_BC6H</span><span class="pun">));</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">auto</span><span class="pln"> size </span><span class="pun">=</span><span class="pln"> pixmap</span><span class="pun">-&gt;</span><span class="pln">size</span><span class="pun">;</span><span class="pln">
        size </span><span class="pun">/=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
        pixmap </span><span class="pun">=</span><span class="pln"> pixmap</span><span class="pun">-&gt;</span><span class="typ">Resize</span><span class="pun">(</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> size</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span><span class="pln">
        </span><span class="typ">Assert</span><span class="pun">(</span><span class="pln">pixmap</span><span class="pun">);</span><span class="pln">
        mipchain</span><span class="pun">.</span><span class="pln">push_back</span><span class="pun">(</span><span class="pln">pixmap</span><span class="pun">-&gt;</span><span class="typ">Convert</span><span class="pun">(</span><span class="pln">TEXTURE_BC6H</span><span class="pun">));</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x </span><span class="pun">==</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> and size</span><span class="pun">.</span><span class="pln">y </span><span class="pun">==</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="typ">SaveTexture</span><span class="pun">(</span><span class="typ">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/skybox.dds"</span><span class="pun">,</span><span class="pln"> TEXTURE_CUBE</span><span class="pun">,</span><span class="pln"> mipchain</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">);</span></pre>

<p>
	when we open the resulting DDS file in Visual Studio we can select different cube map faces and mipmap levels, and see that things generally look like we expect them to:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="17418" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/cubemap.png.1706d39023f796653299cd7083432309.png" rel=""><img alt="cubemap.thumb.png.0e73270bcefa841ce01ad85adeb5234e.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17418" data-ratio="61.87" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/cubemap.thumb.png.0e73270bcefa841ce01ad85adeb5234e.png"></a>
</p>

<p>
	Now is a good time to load our cube map up in the engine and make sure it does what we think it does:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="kwd">auto</span><span class="pln"> skybox </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadTexture</span><span class="pun">(</span><span class="typ">GetPath</span><span class="pun">(</span><span class="pln">PATH_DESKTOP</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/skybox.dds"</span><span class="pun">);</span><span class="pln">
</span><span class="typ">Assert</span><span class="pun">(</span><span class="pln">skybox</span><span class="pun">);</span><span class="pln">
world</span><span class="pun">-&gt;</span><span class="typ">SetSkybox</span><span class="pun">(</span><span class="pln">skybox</span><span class="pun">);</span></pre>

<p>
	When we run the program we can see the skybox appearing. It still appears very dark in the engine:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17419" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.5034cfaea3fdd8e1fe3ebfe95e8826f3.jpg" rel=""><img alt="Untitled.thumb.jpg.bb08d6e14021f79340d1a20f209b015f.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17419" data-ratio="52.80" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.bb08d6e14021f79340d1a20f209b015f.jpg"></a>
</p>

<p>
	We can change the sky color to lighten it up, and the image comes alive. We don't have any problems with the dark colors getting washed out, because this is an HDR image:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="pln">world</span><span class="pun">-&gt;</span><span class="typ">SetSkyColor</span><span class="pun">(</span><span class="lit">4.0</span><span class="pun">);</span></pre>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17420" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.9ee719bfe205c04b59bff7d311ee6c1e.jpg" rel=""><img alt="Untitled.thumb.jpg.54d8214b5620706facc1edb414f4b56b.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17420" data-ratio="53.07" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.54d8214b5620706facc1edb414f4b56b.jpg"></a>
</p>

<p>
	Now that we have determined that our pipeline works, let's try converting a sky image at high resolution. I will use <a href="https://polyhaven.com/a/sunflowers" rel="external nofollow">this image</a> from Polyhaven because it has a nice interesting sky. However, there's one problem. Polyhaven stores the image in EXR format, and the cube map generator I am using only loads HDR files. I used <a href="https://convertio.co/exr-hdr/" rel="external nofollow">this online converter</a> to convert my EXR to HDR format, but there are probably lots of other tools that can do the job.
</p>

<p>
	Once we have the new skybox loaded in the cubemap generator, we will again process and save it, but this time at full resolution:
</p>

<p>
	<img alt="Untitled.jpg.1096a30d5bcdf7e9d75b0af0500032cc.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17422" data-ratio="23.73" style="width:500px;height:auto;" width="746" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.1096a30d5bcdf7e9d75b0af0500032cc.jpg">
</p>

<p>
	Save the file like before and run the DDS creation code. If you are running in debug mode, it will take a lot longer to process the image this time, but when it's finished the results will be worthwhile!:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17424" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.fb724d70c6acd349ac809edbb32b7d1c.jpg" rel=""><img alt="Untitled.thumb.jpg.a5ae14d14b6549efebe9a04977061b36.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17424" data-ratio="53.33" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.a5ae14d14b6549efebe9a04977061b36.jpg"></a>
</p>

<p>
	This will be very interesting to view in VR. Our final DDS file is 120 MB, much smaller than the 768 MB an uncompressed R16G16B16A16 image would require. This gives us better performance and requires a lot less hard drive space. When we open the file in Visual Studio we can browser through the different faces and mipmap levels, and everything seems to be in order:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17425" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.67bd7e1f300fc52f2eac89bf2bc48b9d.jpg" rel=""><img alt="Untitled.thumb.jpg.9ede28f8845ac2cab4840b3da6473776.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17425" data-ratio="64.00" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.9ede28f8845ac2cab4840b3da6473776.jpg"></a>
</p>

<p>
	Undoubtedly this process will get easier and more automated with visual tools, but it's important to get the technical basis laid down first. With complete and in-depth support for the DDS file format, we can rely on a widely supported format for textures, instead of black-box file formats that lock your game content away.
</p>
]]></description><guid isPermaLink="false">2780</guid><pubDate>Wed, 22 Jun 2022 14:41:00 +0000</pubDate></item><item><title>Finalizing Terrain</title><link>https://www.leadwerks.com/community/blogs/entry/2778-finalizing-terrain/</link><description><![CDATA[<p>
	I'm wrapping up the terrain features now for the initial release. Here's a summary of terrain in Ultra Engine.
</p>

<p>
	Terrains are an entity, just like anything else, and can be positioned, rotated, or scaled. Non-square terrains are supported, so you can create something with a 1024x512 or whatever resolution. (Power-of-two sizes are required.)
</p>

<p>
	<span style="font-size:18px;">Editing Terrain</span>
</p>

<p>
	Ultra Engine includes an API that lets you modify terrain in real-time. I took something very complicated and distilled it down to a very simple API for you:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="pln">terrain</span><span class="pun">-&gt;</span><span class="typ">SetElevation</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">);</span></pre>

<p>
	That's it! The engine will automatically update normals, physics, raycasting, rendering, and other data for you behind the scenes any time you modify the terrain, in a manner that maximizes efficiency.
</p>

<p>
	<span style="font-size:18px;">Materials</span>
</p>

<p>
	Leadwerks supports 32 terrain texture layers. Unity supports eight. Thanks to the flexibility of shaders in Vulkan, any terrain in Ultra can have up to a whopping <em>256 different materials</em> applied to it, and it will always be rendered in a single pass at maximum speed.
</p>

<p>
	To apply a material at any point on the terrain, you just call this method. The weight value lets you control how much influence the material as at that point, i.e. its alpha transparency:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="pln">terrain</span><span class="pun">-&gt;</span><span class="typ">SetMaterial</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> material</span><span class="pun">,</span><span class="pln"> weight</span><span class="pun">)</span></pre>

<p>
	This <a href="https://www.ultraengine.com/learn/Terrain_SetMaterial" rel="external nofollow">example</a> shows how to paint materials onto the terrain.
</p>

<p>
	<img alt="terrain_setmaterial.jpg" class="ipsImage" data-ratio="89.60" height="399" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="https://raw.githubusercontent.com/Leadwerks/Documentation/master/Images/terrain_setmaterial.jpg">
</p>

<p>
	I'm quite happy with how the documentation system has turned out, and I feel it is representative of the quality I want your entire user experience to be.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17410" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.9c3a803ca5139e6b48873eaee226653f.jpg" rel=""><img alt="Untitled.thumb.jpg.52f6008e39abcff77529a49b05be0cb3.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17410" data-ratio="63.20" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.52f6008e39abcff77529a49b05be0cb3.jpg"></a>
</p>

<p>
	All the API examples load media from the web, so there's no need to manually download any extra files. Copy and paste the code into any project, and it just works.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="com">//Create base material</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> diffusemap </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadTexture</span><span class="pun">(</span><span class="str">"https://raw.githubusercontent.com/Leadwerks/Documentation/master/Assets/Materials/Ground/river_small_rocks_diff_4k.dds"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> normalmap </span><span class="pun">=</span><span class="pln"> </span><span class="typ">LoadTexture</span><span class="pun">(</span><span class="str">"https://raw.githubusercontent.com/Leadwerks/Documentation/master/Assets/Materials/Ground/river_small_rocks_nor_gl_4k.dds"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">auto</span><span class="pln"> ground </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CreateMaterial</span><span class="pun">();</span><span class="pln">
ground</span><span class="pun">-&gt;</span><span class="typ">SetTexture</span><span class="pun">(</span><span class="pln">diffusemap</span><span class="pun">,</span><span class="pln"> TEXTURE_DIFFUSE</span><span class="pun">);</span><span class="pln">
ground</span><span class="pun">-&gt;</span><span class="typ">SetTexture</span><span class="pun">(</span><span class="pln">normalmap</span><span class="pun">,</span><span class="pln"> TEXTURE_NORMAL</span><span class="pun">);</span></pre>

<p>
	<span style="font-size:18px;">Tessellation and Displacement</span>
</p>

<p>
	Tessellation works great with terrain, which is modeled with quads. There are a lot of high-quality free PBR materials that are great for VR available on the web, so you will have no shortage of interesting materials to paint your terrains with. Here are a few sites for you to peruse:
</p>

<p>
	<a href="https://matlib.gpuopen.com/main/materials/all" rel="external nofollow">https://matlib.gpuopen.com/main/materials/all</a><br>
	<a href="https://icons8.com/l/3d-textures/?ref=joshklint" rel="external nofollow">https://icons8.com/l/3d-textures</a><br>
	<a href="https://ambientcg.com/" rel="external nofollow">https://ambientcg.com/</a><br>
	<a href="https://polyhaven.com/textures" rel="external nofollow">https://polyhaven.com/textures</a><br>
	<a href="https://freepbr.com/" rel="external nofollow">https://freepbr.com/</a><br>
	<a href="https://www.cgbookcase.com/textures" rel="external nofollow">https://www.cgbookcase.com/textures</a>
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17409" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.fc8712ac76404feb00502c2115de3b3b.jpg.939a1fbd5b933f7a423775e156a76de7.jpg" rel=""><img alt="Untitled.jpg.fc8712ac76404feb00502c2115de3b3b.thumb.jpg.ac9792ddd3ef5eb77c2b75d9e2f30bac.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17409" data-ratio="53.73" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.fc8712ac76404feb00502c2115de3b3b.thumb.jpg.ac9792ddd3ef5eb77c2b75d9e2f30bac.jpg"></a>
</p>

<p>
	<span style="font-size:18px;">Cutting Holes</span>
</p>

<p>
	You can cut holes in the terrain by hiding any tile. This is useful for making caves, and can be used in the future for blending voxel geometry into a heightmap terrain.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17408" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.jpg.b2c3fed2ab19fc4e820a61bb208ef307.jpg" rel=""><img alt="Untitled.thumb.jpg.d1df97c009882ed961242c1b20e0aa49.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17408" data-ratio="54.40" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Untitled.thumb.jpg.d1df97c009882ed961242c1b20e0aa49.jpg"></a>
</p>

<p>
	<span style="font-size:18px;">Shader Design</span>
</p>

<p>
	Shaders in Ultra are <em>big</em> and quite complicated. I don't expect anyone to make 100% custom shaders like you could with the simpler shaders in Leadwerks. I've structured shaders so that the entry point shader defines a user function, then includes the base file, which calls the function:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="com">#version 450</span><span class="pln">
</span><span class="com">#extension GL_GOOGLE_include_directive : enable</span><span class="pln">
</span><span class="com">#extension GL_ARB_separate_shader_objects : enable</span><span class="pln">

</span><span class="com">#define</span><span class="pln"> LIGHTING_PBR
</span><span class="com">#define</span><span class="pln"> USERFUNCTION

</span><span class="com">#include</span><span class="pln"> </span><span class="str">"../Base/Materials.glsl"</span><span class="pln">
</span><span class="com">#include</span><span class="pln"> </span><span class="str">"../Base/TextureArrays.glsl"</span><span class="pln">

</span><span class="kwd">void</span><span class="pln"> </span><span class="typ">UserFragment</span><span class="pun">(</span><span class="pln">inout vec4 color</span><span class="pun">,</span><span class="pln"> inout vec3 emission</span><span class="pun">,</span><span class="pln"> inout vec3 normal</span><span class="pun">,</span><span class="pln"> inout </span><span class="typ">float</span><span class="pln"> metalness</span><span class="pun">,</span><span class="pln"> inout </span><span class="typ">float</span><span class="pln"> roughness</span><span class="pun">,</span><span class="pln"> inout </span><span class="typ">float</span><span class="pln"> ambientocclusion</span><span class="pun">,</span><span class="pln"> in vec3 position</span><span class="pun">,</span><span class="pln"> in vec2 texcoords</span><span class="pun">,</span><span class="pln"> in vec3 tangent</span><span class="pun">,</span><span class="pln"> in vec3 bitangent</span><span class="pun">,</span><span class="pln"> in </span><span class="typ">Material</span><span class="pln"> material</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
	</span><span class="com">// Custom code goes here...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">#include</span><span class="pln"> </span><span class="str">"../Base/base_frag.glsl"</span></pre>

<p>
	This organizes shaders so custom behavior can be added on top of the lighting and other systems, and paves the way for a future node-based shader designer.
</p>

<p>
	<span style="font-size:18px;">Future Development</span>
</p>

<p>
	Ideas for future development to add to this system include voxel-based terrains for caves and overhangs, roads (using the decals system), large streaming terrains, and seamless blending of models into the terrain surface.
</p>
]]></description><guid isPermaLink="false">2778</guid><pubDate>Fri, 17 Jun 2022 08:35:00 +0000</pubDate></item><item><title>Smart Tessellation</title><link>https://www.leadwerks.com/community/blogs/entry/2779-smart-tessellation/</link><description><![CDATA[<p>
	I've actually been doing a lot of work to finalize the terrain system, but I got into tessellation, and another rabbit hole opened up. I've been thinking about detailed models in VR. Tessellation is a nice way to easily increase model detail. It does two things:
</p>

<ul>
	<li>
		Curved surfaces get smoother (using point-normal triangles or quads)
	</li>
	<li>
		A displacement map can be used to make small geometric detail to a surface.
	</li>
</ul>

<p>
	These are really nice features because they don't require a lot of memory or disk space, and they're automatic. However, tessellation also has a nasty tendency to create cracks in geometry, so it tends to work best with organic models like rocks that are carefully wrapped. I was able to mitigate some of these problems with a <a href="https://www.leadwerks.com/community/blogs/entry/2440-advanced-tessellation-in-vulkan/" rel="">per-vertex displacement value,</a> which dampens displacement wherever there is a mesh seam:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17374" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/banner.jpg.e6f44a8ff6c653eed66d9eb38613d964.jpg" rel=""><img alt="banner.thumb.jpg.9a4e49ec0513736cbcd91b169ba47f6c.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17374" data-ratio="54.53" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/banner.thumb.jpg.9a4e49ec0513736cbcd91b169ba47f6c.jpg"></a>
</p>

<p>
	However, this does not help round edges become rounder. With this cylinder, because the faces are not a continuous rounded surface, a crack appears at the sharp edges:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17375" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/crack.jpg.baa63ba9432e3748d995ddd73c64c044.jpg" rel=""><img alt="crack.thumb.jpg.8f3d0e6ced0823eecc21cb784e6a61ec.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17375" data-ratio="58.67" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/crack.thumb.jpg.8f3d0e6ced0823eecc21cb784e6a61ec.jpg"></a>
</p>

<p>
	What if there was a way to properly tessellate mechanical shapes like this? Wouldn't it be great if all your models could just automatically gain new detail that scales with the camera distance, with no need for extra Lod versions?
</p>

<p>
	Well, I had an idea how it might be possible, and a few hours later I came up with something. Here is the original model with no tessellation applied:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17376" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image1.jpg.b5fc7a0372b09565a694f3de59a36816.jpg" rel=""><img alt="Image1.thumb.jpg.f9a7a6b6b24296a05bc93a4efd846748.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17376" data-ratio="58.67" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image1.thumb.jpg.f9a7a6b6b24296a05bc93a4efd846748.jpg"></a>
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17378" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image3.jpg.842a835ce8f8f8b4c4bd2a9858494af9.jpg" rel=""><img alt="Image3.thumb.jpg.906525aea40a92b07bd109f2136ee3bc.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17378" data-ratio="58.67" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image3.thumb.jpg.906525aea40a92b07bd109f2136ee3bc.jpg"></a>
</p>

<p>
	Here is the same model with my new and improved tessellation shader, suitable for organic <em>and</em> mechanical shapes:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17377" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image2.jpg.2d09347ed3179243175ef2e7acca03ae.jpg" rel=""><img alt="Image2.thumb.jpg.d69f165deb0c7aa9c1d6565a45c9fe5c.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17377" data-ratio="58.67" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image2.thumb.jpg.d69f165deb0c7aa9c1d6565a45c9fe5c.jpg"></a>
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17379" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image4.jpg.295706c2247083e924cb06775a18fcc4.jpg" rel=""><img alt="Image4.thumb.jpg.356127e3f1f95c5aca9ff50ce063acb5.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17379" data-ratio="58.67" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/Image4.thumb.jpg.356127e3f1f95c5aca9ff50ce063acb5.jpg"></a>
</p>

<p>
	A closeup view reveals perfectly aligned geometry with no gaps or cracks:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17380" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/banner.jpg.4ecd29b8b767d7bf95c232f3c82e2559.jpg" rel=""><img alt="banner.thumb.jpg.e3d96355a2ec1dadcfb30a183745cf51.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17380" data-ratio="53.73" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_06/banner.thumb.jpg.e3d96355a2ec1dadcfb30a183745cf51.jpg"></a>
</p>

<p>
	The distribution of polygons can probably be made more uniform with additional work. This feature uses two additional bytes in the vertex structure to control a normal for tessellated curves. I think it will be possible to automatically detect seams and assign tessellation normals in the final editor. If no separate tessellation normals are assigned, then the default tessellation behavior occurs. This is a very promising technique because it could allow you to add a lot of detail to any model very easily with barely any effort.
</p>
]]></description><guid isPermaLink="false">2779</guid><pubDate>Tue, 07 Jun 2022 12:34:00 +0000</pubDate></item><item><title>Better Intellisense in Ultra Engine</title><link>https://www.leadwerks.com/community/blogs/entry/2776-better-intellisense-in-ultra-engine/</link><description><![CDATA[<p>
	Ultra Engine makes much better use of class encapsulation with strict public / private / protected members. This makes it so you can only access parts of the API you are meant to use, and prevents confusion about what is and isn't supported. I've also moved all internal engine commands into separate namespaces so you don't have to worry about what a RenderMesh or PhysicsNode does. These classes won't appear in the intellisense suggestions unless you were to add "using namespace UltraRender" to your code, which you should never do.
</p>

<p>
	There's still a lot of extra stuff intellisense shows you from Windows headers and other things:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="17361" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/notsimple.png.4a94aa6be53017b29afc6db5f7f20f9f.png" rel=""><img alt="notsimple.thumb.png.0beb414acdeb9f29b063e823707ab6fe.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17361" data-ratio="29.47" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/notsimple.thumb.png.0beb414acdeb9f29b063e823707ab6fe.png"></a>
</p>

<p>
	Using the __INTELLISENSE__ macro in Visual Studio, I was able to trick the intellisense compiler into skipping include files that link to third party libraries and system headers the user doesn't need to worry about. This strips all the unnecessary stuff out of the API, leaving just basic C++ commands, some STL classes, and the Ultra Engine API. With all the garbage removed, the API seems much friendlier:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="17362" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/simple.png.c868d475cc4b39800b3cfb00d353ce07.png" rel=""><img alt="simple.thumb.png.ca3fa73542d45ab3d332009856bb5db6.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17362" data-ratio="29.47" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/simple.thumb.png.ca3fa73542d45ab3d332009856bb5db6.png"></a>
</p>

<p>
	This is one more detail that I feel helps make C++ with Ultra Engine about as easy as C# programming. You can disable this feature by adding _ULTRA_SIMPLE_API=0 in your project macros.
</p>
]]></description><guid isPermaLink="false">2776</guid><pubDate>Sun, 29 May 2022 09:28:00 +0000</pubDate></item><item><title>Shadow Filtering</title><link>https://www.leadwerks.com/community/blogs/entry/2774-shadow-filtering/</link><description><![CDATA[<p>
	Happy Friday! I am taking a break from global illumination to take care of some various remaining odds and ends in Ultra Engine.
</p>

<p>
	Variance shadow maps are a type of shadowmap filter technique that use a statistical sample of the depth at each pixel to do some funky math stuff. <a href="https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-8-summed-area-variance-shadow-maps" rel="external nofollow">GPU Gems 3</a> has a nice chapter on the technique. The end result is softer shadows that run faster. I was wondering where my variance shadow map code went, until I realized this is something I only prototyped in OpenGL and never implemented in Vulkan until now. Here's my first pass at variance shadow maps in Vulkan:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17269" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/Image1.jpg.b1157285c5f3a956891e5e12ea284c08.jpg" rel=""><img alt="Image1.thumb.jpg.945bda23346fb4f88b231d4af9097717.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17269" data-ratio="55.73" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/Image1.thumb.jpg.945bda23346fb4f88b231d4af9097717.jpg"></a>
</p>

<p>
	There are a few small issues but they are no problem to work out. The blurring is taking place before the scene render, in the shadow map itself, which is a floating point color texture instead of a depth texture. (This makes VSMs faster than normal shadow maps.) The seams you see on the edges in the shot above are caused by that blurring, but there's a way we can fix that. If we store one sharp and one blurred image in the variance shadow map, we can interpolate between those based on distance from the shadow caster. Not only does this get rid of the ugly artifacts (say goodbye to shadow acne forever), but it also creates a realistic penumbra, as you can see in the shot of my original OpenGL implementation. Close to the shadow caster, the shadow is well-defined and sharp, but it gets much blurrier the further away it gets from the object:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17270" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/large.penumbra.jpg.c2d9c83e795344954b2508f1ab318461.jpg.95133ae766a0fb4b995d3bd86a128404.jpg" rel=""><img alt="large.penumbra.jpg.c2d9c83e795344954b2508f1ab318461.thumb.jpg.788fcd0bf248e9ca0d06b463fa6a57e4.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17270" data-ratio="57.20" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/large.penumbra.jpg.c2d9c83e795344954b2508f1ab318461.thumb.jpg.788fcd0bf248e9ca0d06b463fa6a57e4.jpg"></a>
</p>

<p>
	Instead of blurring the near image after rendering, we can use MSAA to give it a fine-but-smooth edge. There is no such thing as an MSAA depth shadow sampler in GLSL, although I think there should be, and I have lobbied on behalf of this idea.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="17271" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/large.1994949306_shadowMSAA.png.88ce6757b8bc50be7bebcc2f5150b857.png.516d37f5fe25d8c546753d90320ca18d.png.8d4313a108c722b5df4af0017b22c3b7.png" rel=""><img alt="large.1994949306_shadowMSAA.png.88ce6757b8bc50be7bebcc2f5150b857.png.516d37f5fe25d8c546753d90320ca18d.thumb.png.e3480da66388e46adc21e30a82f10323.png" class="ipsImage ipsImage_thumbnailed" data-fileid="17271" data-ratio="75.07" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/large.1994949306_shadowMSAA.png.88ce6757b8bc50be7bebcc2f5150b857.png.516d37f5fe25d8c546753d90320ca18d.thumb.png.e3480da66388e46adc21e30a82f10323.png"></a>
</p>

<p>
	Finally, in my Vulkan implementation I used a compute shader instead of a fragment shader to perform the blurring. The advantage is that a compute shader can gather a bunch of samples and store them in memory, then access them to process a group of pixels at once. Instead of reading 9x9 pixels for each fragment, it can read a block of pixels and process them all at once, performing the same number of image writes, but much fewer reads:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="com">// Read all required pixel samples</span><span class="pln">
x </span><span class="pun">=</span><span class="pln"> </span><span class="typ">int</span><span class="pun">(</span><span class="pln">gl_WorkGroupID</span><span class="pun">.</span><span class="pln">x</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> BATCHSIZE</span><span class="pun">;</span><span class="pln">
y </span><span class="pun">=</span><span class="pln"> </span><span class="typ">int</span><span class="pun">(</span><span class="pln">gl_WorkGroupID</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> BATCHSIZE</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">coord</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> max</span><span class="pun">(</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> EXTENTS</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln"> coord</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&lt;</span><span class="pln"> min</span><span class="pun">(</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> BATCHSIZE </span><span class="pun">+</span><span class="pln"> EXTENTS</span><span class="pun">,</span><span class="pln"> outsize</span><span class="pun">.</span><span class="pln">x</span><span class="pun">);</span><span class="pln"> </span><span class="pun">++</span><span class="pln">coord</span><span class="pun">.</span><span class="pln">x</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">coord</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> max</span><span class="pun">(</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> EXTENTS</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln"> coord</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&lt;</span><span class="pln"> min</span><span class="pun">(</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> BATCHSIZE </span><span class="pun">+</span><span class="pln"> EXTENTS</span><span class="pun">,</span><span class="pln"> outsize</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span><span class="pln"> </span><span class="pun">++</span><span class="pln">coord</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        color </span><span class="pun">=</span><span class="pln"> imageLoad</span><span class="pun">(</span><span class="pln">imagearrayCube</span><span class="pun">[</span><span class="pln">inputimage</span><span class="pun">],</span><span class="pln"> coord</span><span class="pun">);</span><span class="pln">
        samples</span><span class="pun">[</span><span class="pln">coord</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> </span><span class="typ">int</span><span class="pun">(</span><span class="pln">gl_WorkGroupID</span><span class="pun">.</span><span class="pln">x</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> BATCHSIZE </span><span class="pun">+</span><span class="pln"> EXTENTS</span><span class="pun">][</span><span class="pln">coord</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> </span><span class="typ">int</span><span class="pun">(</span><span class="pln">gl_WorkGroupID</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> BATCHSIZE </span><span class="pun">+</span><span class="pln"> EXTENTS</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> color</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	This same technique will be used to make post-processing effects faster. I previously thought the speed of those would be pretty much the same in every engine, but now I see ways they can be improved significantly for a general-use speed increase. <a contenteditable="false" data-ipshover="" data-ipshover-target="https://www.leadwerks.com/community/profile/4-klepto2/?do=hovercard" data-mentionid="4" href="https://www.leadwerks.com/community/profile/4-klepto2/" rel="">@klepto2</a> has been talking about the benefits of compute shaders for a while, and he is right, they are very cool. Most performance-intensive post-processing effects perform some kind of gather operation, so compute shaders can make a big improvement there.
</p>

<p>
	One issue with conventional VSMs is that all objects must cast a shadow. Otherwise, an object that appears in front of a shadow caster will be dark. However, I added some math of my own to fix this problem, and it appears to work with no issues. So that's not something we need to worry about.
</p>

<p>
	All around, variance shadow maps are a big win. They run faster, look better, and eliminate shadow acne, so they basically kill three birds with one stone.
</p>
]]></description><guid isPermaLink="false">2774</guid><pubDate>Fri, 20 May 2022 13:59:00 +0000</pubDate></item><item><title>Finalizing Shaders and Real-time Global Illumination Progress</title><link>https://www.leadwerks.com/community/blogs/entry/2772-finalizing-shaders-and-real-time-global-illumination-progress/</link><description><![CDATA[<p>
	I'm finalizing the shaders, and I was able to pack a lot of extra data into a single entity 4x4 matrix:
</p>

<ul>
	<li>
		Orthogonal 4x4 matrix
	</li>
	<li>
		RGBA color
	</li>
	<li>
		Per-entity texture mapping offset (U/V), scale (U/V), and rotation
	</li>
	<li>
		Bitwise entity flags for various settings
	</li>
	<li>
		Linear and rotational velocity (for motion blur)
	</li>
	<li>
		Skeleton ID
	</li>
</ul>

<p>
	All of that info can be fit into just 64 bytes. The shaders now use a lot of snazzy new function like this:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted">
<span class="kwd">void</span><span class="pln"> </span><span class="typ">ExtractEntityInfo</span><span class="pun">(</span><span class="pln">in </span><span class="typ">uint</span><span class="pln"> id</span><span class="pun">,</span><span class="pln"> out mat4 mat</span><span class="pun">,</span><span class="pln"> out vec4 color</span><span class="pun">,</span><span class="pln"> out </span><span class="typ">int</span><span class="pln"> skeletonID</span><span class="pun">,</span><span class="pln"> out </span><span class="typ">uint</span><span class="pln"> flags</span><span class="pun">)</span><span class="pln">
mat4 </span><span class="typ">ExtractCameraProjectionMatrix</span><span class="pun">(</span><span class="pln">in </span><span class="typ">uint</span><span class="pln"> cameraID</span><span class="pun">,</span><span class="pln"> in </span><span class="typ">int</span><span class="pln"> eye</span><span class="pun">)</span></pre>

<p>
	So all the storage / decompression routines are in one place instead of hard-coding a lot of matrix offset in different shaders.
</p>

<p>
	A per-entity linear and angular velocity is being calculated and sent to the rendering thread. This is not related to the entity velocity in the physics simulation, although they will most often be the same. Rather, this is just a measure of the distance the entity moved since the last world render, so it will work with any motion, physics or otherwise.
</p>

<p>
	In the video below, the linear velocity is being added to the camera position to help predict the region of the GI lighting area, so the camera stays roughly in the center as the GI updates. A single 128x128x128 volume texture is being used here, with a voxel size of 0.25 meters. In the final version, you probably want to use 3-4 stages, maybe using a 64x64x64 texture for each stage.
</p>

<div class="ipsEmbeddedVideo">
	<div>
		<iframe allowfullscreen="" frameborder="0" height="113" width="200" data-embed-src="https://www.youtube.com/embed/ayOP7Cfiibk?feature=oembed"></iframe>
	</div>
</div>

<p>
	<span>Performance of our voxel cone step tracing is quite good, with FPS in the mid-hundreds on a mid-range notebook GPU. The last big question is how to handle dynamic objects, particular fast-moving dynamic objects. I have some ideas but I'm not sure how hard this is going to be. My first attempt was slow and ugly. You can probably guess which dragon is static and which is dynamic:</span>
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="17243" href="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/Untitled.jpg.8cc81928e39e9d57368b34142b1e8668.jpg" rel=""><img alt="Untitled.thumb.jpg.9853103d11b572476d16a6a8b165dc02.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="17243" data-ratio="51.47" width="750" src="https://www.ultraengine.com/community/applications/core/interface/js/spacer.png" data-src="//leadwerksstorage.s3.us-east-2.amazonaws.com/monthly_2022_05/Untitled.thumb.jpg.9853103d11b572476d16a6a8b165dc02.jpg"></a>
</p>

<p>
	It is a difficult problem. I would like the solution to be as cool as the rest of this system as I finish up this feature. Also, I think you will probably want to still use SSAO so dynamic objects blend into the GI lighting better.
</p>

<p>
	Everyone should have access to fast real-time global illumination for games.
</p>
]]></description><guid isPermaLink="false">2772</guid><pubDate>Mon, 09 May 2022 17:15:00 +0000</pubDate></item></channel></rss>
