tag:blogger.com,1999:blog-213825082024-03-12T23:11:54.679-07:00Shiny DynamicsLisp. 3D graphics. Compilers. Operating systems. Mobile devices.
Cool stuff.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.comBlogger18125tag:blogger.com,1999:blog-21382508.post-89136085304050526752016-12-07T10:17:00.000-08:002016-12-08T07:40:31.930-08:00Changing imaxima's Output Font
<p>
I use <code>imaxima</code> from Emacs to interact with the Maxima symbolic algebra
system. <code>imaxima</code> produces nicely typeset output from the results of
Maxima, instead of crude ASCII graphics, and also supports embedding
plots and drawings directly in an Emacs buffer. <code>imaxima</code> works by
running LaTeX on the output of each Maxima command you enter; it then
uses Ghostscript to produce an image that is displayed in the output
buffer.
</p>
<p>
I also use the <a href="http://ethanschoonover.com/solarized">Solarized</a> <a href="https://github.com/sellout/emacs-color-theme-solarized">color theme</a> with Emacs, which I find quite
easy on my eyes. However, there is a problem: <code>imaxima</code> output, which
uses Computer Modern fonts by default, becomes quite hard for me to
read:
</p>
<div class="figure">
<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtjYD4yAoddr3K6IdPTwKmV7eiA6J_l8Dykq0TY2CH3MvyaZJMeFpg42tVWDifWQNWuoTmqF2crgFHqsXBUzVeif2u6R9YvXYkSl2b6x4e72ewvXvUJlAPn5-3oW7iHfniPDnk/s1600/cm-cropped.png" alt="cm-cropped.png" />
</p>
</div>
<p>
The anti-aliasing performed by Ghostscript interacts with the
Solarized colors in a way that makes small letters wash out.
</p>
<div id="outline-container-org7f8b7e7" class="outline-2">
<h2 id="org7f8b7e7"><span class="section-number-2">1</span> Changing the font</h2>
<div class="outline-text-2" id="text-1">
<p>
Fortunately, it's not too hard to change the font used for <code>imaxima</code>'s
LaTeX output, but there are a couple of gotchas
to keep in mind. <a href="http://tex.stackexchange.com/questions/59702/suggest-a-nice-font-family-for-my-basic-latex-template-text-and-math">This
StackExchange question</a> is a good starting point for choosing a font
that should work nicely on the screen. The variable <code>imaxima-latex-preamble</code> is defined for
just this purpose; in fact, the documentation says "This can be used
to change, say, the document font." However, changing this variable's
value won't change the font during an <code>imaxima</code> session. It turns out
that, at the start of a session, <code>imaxima</code> loads several packages and
and definitions into LaTeX and then creates a "LaTeX format
file," which is a dump of the LaTeX state. This format file is used
when LaTeX is invoked to process each line of Maxima output because
this is much faster than loading packages every time. The value of
<code>imaxima-latex-preamble</code> is included when the format file is loaded
and dumped, so if you change it you need to kill your <code>imaxima</code> buffer
and restart before seeing different results.
</p>
<p>
I chose the Libertine font, which looks nice:
</p>
<div class="figure">
<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFV0EIMyyiMMwqmNkVR-IpueiBUB3VS7NzmlqFxV04vwy6JzJdDGCOxI0skQp3XW2s9ceDK0RYq7g05dtyaoA4dJItwUDfMX4xCQcf_g7IDCpDuZ2pBJoho8gOpIn-BkelR0Wc/s1600/libertine-cropped.png" alt="libertine-cropped.png" />
</p>
</div>
<p>
I achieved this by following the example of that StackExchange page:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span style="color: #859900;">setq</span> imaxima-latex-preamble <span style="color: #2aa198;">"\\usepackage{libertine} \\usepackage[libertine]{newtxmath}"</span>)
</pre>
</div>
<p>
But this leads to the second gotcha.
</p>
<pre class="example">
(%i8) v1:matrix([x,y,z]);
LaTeX error in: \pmatrix{x&\linebreak[0]y&\linebreak[0]z\cr }
</pre>
<p>
The problem is caused by the <code>newtxmath</code> package, which loads the
<a href="http://www.ams.org/publications/authors/tex/amslatex">AMS-LaTeX</a> package. Somewhere in there the <code>pmatrix</code> is redefined in an
incompatible way. Luckily we can work around this! The maxima
distribution comes with a file <code>mactex-utilities.lisp</code> that redefines
the function that translates matrix expressions to LaTeX. The new
definition emits a call to <code>pmatrix</code> that is compatible with the
AMS-LaTeX version.
</p>
<p>
We could load this file using the Maxima initialization file
<code>maxima-init.mac</code> (usually found in the <code>.maxima</code> directory), but we
might not want this change for every use of Maxima. I've instead
chosen to load the file using a startup hook from Emacs to send the
loading command to the <code>imaxima</code> process:
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp">(add-hook 'imaxima-startup-hook
(<span style="color: #859900;">lambda</span> ()
(<span style="color: #859900;">let</span> ((b (get-buffer <span style="color: #2aa198;">"*imaxima*"</span>))
(p (get-process <span style="color: #2aa198;">"imaxima"</span>)))
(<span style="color: #859900;">if</span> (<span style="color: #859900;">and</span> b p)
(apply comint-input-sender
(list (get-process <span style="color: #2aa198;">"imaxima"</span>)
<span style="color: #2aa198;">"load(\"mactex-utilities\");"</span>))))))
</pre>
</div>
<p>
Let the math hacking commence!
</p>
<div class="figure">
<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb-KHac9mOtFo6AT7BnWwXZhWzZdWZ_zTrDeG0gtdIg0mgWLRtCy7Q08s60intN58oxU2CxTyzW-3SybrRbYPMqfrrFilQmQRIlXPXf4qEiruyiwgmRG0GIwOXyCRGJJqKSsR-/s1600/mactex-utilities-cropped.png" alt="mactex-utilities-cropped.png" />
</p>
</div>
</div>
</div>
Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-31962651414793855212012-04-21T08:02:00.000-07:002012-04-21T08:02:56.915-07:00Blender Wood Texture part 2In the last post we developed a wood texture that is based on the actual growth process of trees. It looks pretty good, but it lacks any variation that would make it look like wood taken from a real tree: the rings are circular, and the rings follow a strict logarithmic progression in which the interval between each successive ring is smaller. In real life, there is a lot of variation in the ring pattern. The trunk is not necessarily circular, for many reasons, including but limited to: the tree species, presence of nearby trees, prevailing wind direction and strength, and ground slope. The spacing between rings depends on the weather in a given year.
<p>
We won't attempt to model the phenomena behind these variations, but we can simulate them as random processes. In computer graphics, "random" usually doesn't mean completely random, because truly random values would introduce horrible artifacts. Instead, we use a source of randomness called <a href="http://en.wikipedia.org/wiki/Perlin_noise">Perlin noise</a>. This is an effect that varies a random value (or vector) smoothly in space. A 2D slice of Perlin noise looks a lot like the surface of a cloud; indeed, the Blender Perlin noise texture is called "Clouds." We can access a cloud texture using the generated coordinates from our 3D timber objects, and we can also use any other value we wish to noisify, such as distance from the center of the trunk or tree age, to do a look-up in the cloud texture.
<h3>Ring Distortion</h3>
<p>
The first new variation we will introduce is to the shape of the trunk. We want to slowly change from circular rings at the center of the tree to something that might be quite distorted at the outside. After some experimentation, I've found that it works best to scale the radial distance, which use as the input to the age calculation, by a factor based on the angular position around trunk. We implement this by normalizing the radial vector to a unit circle, doing a lookup in the cloud texture, scaling the noise value down quite a bit to a value near 1, and multiplying our radius value by this. Here is the modified material node network:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit0iM-0aZ9UiS5idg3H1xbBBvRTycdjnrhUw4Bi6WK-MEk2vW6toqDxtqHf63YFedjUeiHwbwflg1Yts2OdMvRGZsJccfGtMsOqbC7Zjra1VPL7m30USaPFW4HUPKqicmGkzDF/s1600/node6.png" imageanchor="1" style=""><img border="0" height="186" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit0iM-0aZ9UiS5idg3H1xbBBvRTycdjnrhUw4Bi6WK-MEk2vW6toqDxtqHf63YFedjUeiHwbwflg1Yts2OdMvRGZsJccfGtMsOqbC7Zjra1VPL7m30USaPFW4HUPKqicmGkzDF/s320/node6.png" /></a></div>
and the results:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRXVu5FN6GIvn7Oh6_e3Xoa-X-uYZBXE2S84gxD06_FQr2kCT0YTxwsPdk9t5IrWN8gqxPFzUwTMMkW2Di6nh9tLXKeN1J4BiNkD2r4P4uBkzTZ0waBhsDO6bQPft2L6LQqisH/s1600/rend6.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRXVu5FN6GIvn7Oh6_e3Xoa-X-uYZBXE2S84gxD06_FQr2kCT0YTxwsPdk9t5IrWN8gqxPFzUwTMMkW2Di6nh9tLXKeN1J4BiNkD2r4P4uBkzTZ0waBhsDO6bQPft2L6LQqisH/s320/rend6.png" /></a></div>
Although I'm exaggerating the effect in order to show it clearly in the rendering, the results are not bad here. The variation in the circular shape of the rings is indeed more pronounced in the outer rings, and it has introduced interesting changes in the grain pattern on the long surfaces of the timbers. The waviness in the rings apparent on the square timber may not look much like any real tree, but we will see later how we can vary the effect's intensity to achieve the desired look.
<h3>Growth Variation</h3>
<p>
Next, we will vary the distance between rings with a similar node setup that scales the output of our age calculation:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHrpHeoOsQzVZjo1Angpt6nBwqUqfEer6AXj9vMh_pZECjZ9iJ9dlFVzrllUZNu_yJke-hiWe7MBTbhTLkCCI7atugdoYKsKwGUeO19OaAPPlYk_T0PXIQvn8yPbXz8td6x9So/s1600/node7.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHrpHeoOsQzVZjo1Angpt6nBwqUqfEer6AXj9vMh_pZECjZ9iJ9dlFVzrllUZNu_yJke-hiWe7MBTbhTLkCCI7atugdoYKsKwGUeO19OaAPPlYk_T0PXIQvn8yPbXz8td6x9So/s320/node7.png" /></a></div>
For our test render, I've turned off the previous effect in order to see only the new one:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSog5vFtcXy29yk4HUa7XFHxagpsaCogTK6y12Va5005_EKH9a8NGe5fu7F9S1kJ7jkzuZBw5CYOiLGw3h43pnLFMU0nxBfO7ruvVwZDbzrQdMf9ZjeIvNl-6S0x3VmpYQ3f1T/s1600/rend7.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSog5vFtcXy29yk4HUa7XFHxagpsaCogTK6y12Va5005_EKH9a8NGe5fu7F9S1kJ7jkzuZBw5CYOiLGw3h43pnLFMU0nxBfO7ruvVwZDbzrQdMf9ZjeIvNl-6S0x3VmpYQ3f1T/s320/rend7.png" /></a></div>
which is pretty successful. While we have preserved the basic trend of the rings getting closer together at the outside of the tree, there is significant variation in the ring spacing.
<p>
After putting the two effects together and reducing the amplitude of the radial variations, we get this:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPLDCP2bF40sH0VE0FLylklSafKZbWxXjxlUWnqLk6YfEvUYeSf05fsz_ZgD28YkK4Ju0GxCkSV8-8MlVBFhJlykvhtPPJmDL8-UjSc_THL_yqwBreZRXTrGUVCvWsQq7qcnGQ/s1600/rend9.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPLDCP2bF40sH0VE0FLylklSafKZbWxXjxlUWnqLk6YfEvUYeSf05fsz_ZgD28YkK4Ju0GxCkSV8-8MlVBFhJlykvhtPPJmDL8-UjSc_THL_yqwBreZRXTrGUVCvWsQq7qcnGQ/s320/rend9.png" /></a></div>
This is very close to a good wood texture, but there is still a lot of repetition, particularly in the visible grain where a surface grazes a dark ring, such as directly over the center of the tree trunk in the flat board. We can break up this repetition by introducing a bit of noise all over, which we can justify by considering tree growth as an organic process. The noise is introduced at the end of the node network, using 3D position as the input to the noise texture:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMkkR6Hxe6S69FjVi-qLPXbHnjzovTN2OzVGr5bCo9OXEVhzHYLnSxPDuP_-SBYpKneyvS97aX5iNK99OK3Doq0fpzCEU99Vwyavc13isw_lR2W2II7SMeGREzOfHJwtSINote/s1600/node10.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMkkR6Hxe6S69FjVi-qLPXbHnjzovTN2OzVGr5bCo9OXEVhzHYLnSxPDuP_-SBYpKneyvS97aX5iNK99OK3Doq0fpzCEU99Vwyavc13isw_lR2W2II7SMeGREzOfHJwtSINote/s320/node10.png" /></a></div>
and gives an appearance that we will happily use in our technical illustrations:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVI5hmhk-h26uHCSgC-uW2CEdg2woHTrHjbuNxGEPtHkGlXCEoSBEfqT2xvKga7GgkhirPx6K95LnKYDVkniU-US-2oZKepBO_kMcLR-ikAyIuVLTZFvuAsX-zfa5xeB4IDH7Q/s1600/rend10.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVI5hmhk-h26uHCSgC-uW2CEdg2woHTrHjbuNxGEPtHkGlXCEoSBEfqT2xvKga7GgkhirPx6K95LnKYDVkniU-US-2oZKepBO_kMcLR-ikAyIuVLTZFvuAsX-zfa5xeB4IDH7Q/s320/rend10.png" /></a></div>
<h3>Node Groups</h3>
<p>
The final node network is fairly complicated:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgPdgfc3IOd8qQ9l6qpC2InayUfsXT8_htQaxjTPQWm9MQr7q4ilwNyVrca4DNJzgf8XYNVpyN726jp-KANo8kuAGZqcpWkfFR1llO16L5m7BYjiihfYCea8avHTnYevCTC5qb/s1600/node10-full.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgPdgfc3IOd8qQ9l6qpC2InayUfsXT8_htQaxjTPQWm9MQr7q4ilwNyVrca4DNJzgf8XYNVpyN726jp-KANo8kuAGZqcpWkfFR1llO16L5m7BYjiihfYCea8avHTnYevCTC5qb/s320/node10-full.png" /></a></div>
It would not be convenient to copy this for different wood materials. We can use Blender's group feature to gather all the nodes into a single block that can be used in many materials in a file, and be linked or appended into other Blender files. Select all the nodes in the material, except for the coordinate input node and the material and output nodes at the end. Then, use the "Group" command from the Node menu. Our network uses two coordinate inputs, but we can have one input to the group and route that value where it is needed inside the group:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf3I_fhaQVolIJygmAGIHxpk9Zmn-oRgDQ8lKFPdLm0zGt01Z72Juag5tunz8NwrNNSIgcDkrxwyvEGxAKSA2VNVNh58f_JUuMAtp7OYS7dNr8HDgTUWdVD3fSECU7SICsOOzP/s1600/group-expanded.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf3I_fhaQVolIJygmAGIHxpk9Zmn-oRgDQ8lKFPdLm0zGt01Z72Juag5tunz8NwrNNSIgcDkrxwyvEGxAKSA2VNVNh58f_JUuMAtp7OYS7dNr8HDgTUWdVD3fSECU7SICsOOzP/s320/group-expanded.png" /></a></div>
When we are done editing the group, the network looks like this:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy9k8BePRIHxwSZ60lmaO-Hh53LpajTuGQXuV3RpOhzde6quxrvZUgVMaRr8WOAlN0yDlgo2U2SSTTb32xU2HKjzAb-WqU1hP3zyXIMED1NmmpPDsaiqqGCbuAwW1clRotSz5z/s1600/group.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy9k8BePRIHxwSZ60lmaO-Hh53LpajTuGQXuV3RpOhzde6quxrvZUgVMaRr8WOAlN0yDlgo2U2SSTTb32xU2HKjzAb-WqU1hP3zyXIMED1NmmpPDsaiqqGCbuAwW1clRotSz5z/s320/group.png" /></a></div>
Much neater, but perhaps not very useful to an artist. For our final refinement, we will expose all the parameters that would be useful for achieving different wood effects as inputs to the group as well as the value used to mix the early and late colors. The new group is impressive in its collapsed state:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieejeyNa7OFO9aCu70ZYJyWfrD7RzLUzF2W21l1pC0NPe9JYXOnrMiLXwWzCptGdTmGc-E66Q_3Q-K-ruATRBjKb20YAFnXVCPKgyS6briWd4WndyKbYGA46kKE_fTz3Ub0tdZ/s1600/group4.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieejeyNa7OFO9aCu70ZYJyWfrD7RzLUzF2W21l1pC0NPe9JYXOnrMiLXwWzCptGdTmGc-E66Q_3Q-K-ruATRBjKb20YAFnXVCPKgyS6briWd4WndyKbYGA46kKE_fTz3Ub0tdZ/s320/group4.png" /></a></div>
The interior of the group is now fairly complex and is starting to strain the ability of the screenshots to illustrate what we are doing. On the left:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHHJ7bQfgDPLfhoIuJygDwIkT0Fz1jh20RkQxhwxLqDRzTnMB-NE3LRSkvQ7o1GSGRQQWIERzx6eCYBr94Rs06h1weoIpGimuzndDkscgJHaA8KGy3dfuLbxY52gK25fCTFWSc/s1600/group4-left.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHHJ7bQfgDPLfhoIuJygDwIkT0Fz1jh20RkQxhwxLqDRzTnMB-NE3LRSkvQ7o1GSGRQQWIERzx6eCYBr94Rs06h1weoIpGimuzndDkscgJHaA8KGy3dfuLbxY52gK25fCTFWSc/s320/group4-left.png" /></a></div>
and the right:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCMiLp8opRxpr143Ot4gCB_nlpnclphMgDx3qJLsZEzF-ZmfHvn0EUCu1Hg7fATN85QeqM2kQoAX7vlzOmbQHIl528Rjzm8tnREN9liQNPFEbwGcHsw3nhlRhlGBT_zSAaDTGm/s1600/group4-right.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCMiLp8opRxpr143Ot4gCB_nlpnclphMgDx3qJLsZEzF-ZmfHvn0EUCu1Hg7fATN85QeqM2kQoAX7vlzOmbQHIl528Rjzm8tnREN9liQNPFEbwGcHsw3nhlRhlGBT_zSAaDTGm/s320/group4-right.png" /></a></div>
Some implementation details have been changed because Blender doesn't allow inputs to mapping nodes other than the vector to be transformed.
<p>
So, that's our wood grain material. You can download <a href="http://www.bricoworks.com/moore/woodgrain.blend">the blend file here</a> and play with it yourself. Time to do some more carpentry illustrations!Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com1tag:blogger.com,1999:blog-21382508.post-80839237568958969762012-04-15T01:42:00.000-07:002012-04-21T08:05:08.953-07:00A Wood Texture for BlenderOn <a href="http://stereotomy-blog.blogspot.com">Stereotomy Blog</a> I've been running a series of posts on classic French carpentry drawing techniques. I illustrate somewhat obscure drafting constructions in 3D in order to visualize the geometry on which they are based. This falls in the realm of technical illustration, in which rendering is not photo-realistic, but more "artistic" with the goal of communication and clarity. I have used the open source package <a href="http://www.blender.org">Blender</a> for all these illustrations, and I thought it would be interesting to explain some of the methods I used.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRe8yv7yLEoJBxfK5tgjjtYccpgBqih8M-hh50egy4lp5ffef3-DKAD79uZTPfWJS0yCUaO-LB1kjqiNeYMXuoLmxULRHbO-4LWAHH3rwTuRqRpOQcX-JIC4dHSgQ-mTXRPRPg/s1600/0001.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRe8yv7yLEoJBxfK5tgjjtYccpgBqih8M-hh50egy4lp5ffef3-DKAD79uZTPfWJS0yCUaO-LB1kjqiNeYMXuoLmxULRHbO-4LWAHH3rwTuRqRpOQcX-JIC4dHSgQ-mTXRPRPg/s320/0001.png" /></a></div><br />
<p>The topic of the series is carpentry, so it is critical to be able to render wood in a convincing way. The most important feature of wood planks and timbers is the grain, which results from cutting flat planes through the cylindrical tree rings in a tree trunk. Broadly speaking, tree rings result from fast and slow growth at different times of the year. The orientation of the grain has important consequences for the strength of structural members and joints. It also has a large effect on the movement of wood with time: wood swells and shrinks radially across its rings, but hardly at all along the original length of the tree. In some applications it is important to orient two pieces so that, for example, the young wood from the top of a tree is abutting or the outer growth rings end up next to each other.<br />
<p>The patterns resulting from the tree structure are readily apparent on cut boards. The end grain shows the characteristic tree rings, while the faces of a board will show different patterns depending on how the board was cut from a log. <a href="http://www.logsdotcom.com/prod11.asp">This site</a> has some good illustrations of the different possibilities. Also, There are fewer rings at the top of a tree, which produces a diagonal orientation to the grain on some sides of a board and, if the board is cut near the center of a log, a distinctive wishbone pattern. If we can show these features in an illustration, then the reader will instantly know how a particular timber or board should be oriented.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe_d8NoM2LT6HA_o7TFtPvDm7ADHAd5-Ic8-WdgUzSRW5tztjJZ_HsLFZyHh47DU9B5xijrkBnTLcFudDTp_31iab1NNWWhqSu3M0Vn6OoSnatojfd9zPJcBDKQBKXpmOzMpbB/s1600/0022.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhe_d8NoM2LT6HA_o7TFtPvDm7ADHAd5-Ic8-WdgUzSRW5tztjJZ_HsLFZyHh47DU9B5xijrkBnTLcFudDTp_31iab1NNWWhqSu3M0Vn6OoSnatojfd9zPJcBDKQBKXpmOzMpbB/s320/0022.png" /></a></div><br />
In the 3D modeling world, wood grain is best implemented as a procedural texture which calculates the surface color at every visible point. We can thus develop a mathematical model that mimics the ring structure of a tree and use it to drive the procedural texture. In Blender, the most flexible way to develop a procedural texture is to use the material node editor, which enables the modeler to assemble networks of building blocks that calculate the surface properties of an object.<br />
<p>Here is our wish list of wood texture features:<br />
<ul> <li> The texture should be easy to apply to a 3D object that represents a particular timber or board. It should be aligned with the local orientation of that timber.<br />
<li> The texture needs to model the growth rings of a tree. Physical characteristics should be represented: tree rings get closer together at the outside of a tree and, as mentioned before, there are fewer tree rings at the top of a tree. The actual spacing of the rings is not critical. A physically correct model might produce so many rings that we would get severe aliasing at the scales which interest us. Also, there is a wide variation in the spacing. Nevertheless, the rings must be suggestive.<br />
<li> For artistic purposes, the modeler should be able to specify the light and dark colors of the ring. Also, there should be some variation in the rings: growth rates are different from year to year, and tree trunks are not all perfectly round.<br />
<li> The grain should look good and not exhibit aliasing artifacts, or at least provide ways for the modeler to avoid aliasing. On the other hand, the grain texture is not photo-realistic. I don't care at all about knots in the wood, nor about the reflectance properties of the wood at a micro level.<br />
</ul>
<h3>Blender's Wood Texture</h3>
Blender already has a wood texture. Here is an example of it applied to a rectangular shape:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJ4_erkgC6qTpEFjmLe_4Fpjfvmdd8-2F9pFiid-HY3hcR9Ak7YKzixV6eZLPxv59yhE5FhTOvcDs_erIdRQtzsp0_mFh4QuqmII3-g8d_jiRvrWC_Zhrl8y2CDysuol_AUMAU/s1600/bwood.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJ4_erkgC6qTpEFjmLe_4Fpjfvmdd8-2F9pFiid-HY3hcR9Ak7YKzixV6eZLPxv59yhE5FhTOvcDs_erIdRQtzsp0_mFh4QuqmII3-g8d_jiRvrWC_Zhrl8y2CDysuol_AUMAU/s320/bwood.png" /></a></div>
I am not showing the texture in an attractive light. It can be used to render convincing wood, as shown in <a href="http://wiki.blender.org/index.php/Doc:2.4/Tutorials/Textures/Wood">this tutorial</a>. The point of my rendering is to demonstrate that its structure is made of concentric spheres. This isn't suitable for rendering the cylindrical structure of a tree trunk unless you stretch it out in one direction or only use a 2D slice of the texture. Even if we did that, it would still be complicated to implement the other features we want, such as having fewer rings at the top of the tree. We could use this built-in texture as a basis for the repeating pattern of our texture, but we will get more flexibility from implementing our own periodic function.
<p><h3>Age from Radius</h3>Our procedural shader, implemented as a Blender material node network (called a "noodle" in the blender world), will take as input coordinates from the surface of a piece of wood, calculate the age of the tree at that point and thus the corresponding ring color. If our modeling scale is in centimeters, for our initial effort we can assume that a tree's radius grows at 1 centimeter per year and so use the radial distance from the center of the tree as the age.
<p>We now need to choose the proper coordinate system in which to calculate that distance. Blender provides coordinates that are called "generated" coordinates or "Orco" (for Original Coordinates) in different parts of the program. These are conveniently aligned with the local orientation of an object and its origin, but by default they are scaled to the bounding box of the object, which is not good. Fortunately you can turn off this scaling. This screen shot shows the desired option choice in the mesh panel:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibNWlPjHJMEJ_VaDuiW963idtmGtXXsnWLudXZYONQ-lF7hEW8fR2I0AV3uA8_qZ8ihlkxplv0vGm2c2qGbTGDNNXWJkeGSD94hue06fKo9kKiTzvHemjgqCYHEw5CixoRYLW8/s1600/geom.png" imageanchor="1" style=""><img border="0" height="232" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibNWlPjHJMEJ_VaDuiW963idtmGtXXsnWLudXZYONQ-lF7hEW8fR2I0AV3uA8_qZ8ihlkxplv0vGm2c2qGbTGDNNXWJkeGSD94hue06fKo9kKiTzvHemjgqCYHEw5CixoRYLW8/s320/geom.png" /></a></div>
Note that the sample timber's origin is in the middle of the end face, and its X axis extends along its length.
<p>We know how to get the age from the radial distance, and we can derive a function that ranges from 0 to 1 during year by using the non-integer part of the age. That will give us a value that can be used almost directly to choose a color. Blender doesn't have a truncate function, but it does have rounding function, and we can use that, after subtracting .5 from the value, to get the integer part, then do another subtraction to get the non-integer part. However, if we used that value as is, we would get terrible aliasing at the sudden transition from 1 to 0 at the end of the cycle. We use a color ramp and choose a cubic interpolating spline to get a smooth transition.
<p>Here is our first effort at a node network:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG59XDqwuG_u2EJG2phzqV1EopBPPURDKOMxHerR2umnTnUtYwYyyhUGk2V0UFtSg3JGDk7IKSgsL9RsPzvv05sO7cbx-yAiDK5u-QQCi7zEt59_A4LTroKEKsNoyBlkqVC7q4/s1600/node1.png" imageanchor="1" style=""><img border="0" height="231" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG59XDqwuG_u2EJG2phzqV1EopBPPURDKOMxHerR2umnTnUtYwYyyhUGk2V0UFtSg3JGDk7IKSgsL9RsPzvv05sO7cbx-yAiDK5u-QQCi7zEt59_A4LTroKEKsNoyBlkqVC7q4/s320/node1.png" /></a></div>
We use "orco" coordinates as input to the process. First, we use the mapping node to 0 the X coordinate, so we only deal with radial distance from the center of the trunk. We then get the length of the coordinate vector, with dot product and square root, and feed that to the truncating process described above. This gives us this initial result:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrSm4e6fVx-KhVpwzv-LG26Q0n93Q9yByVtRTpzIfzzYy_vmCQx-cmUQcfoKtZijP88QkiCI7P8_7R_M0ri_bZ7iy4t-nNQvXy9QwYceX74qBHEGBhRwp3xc0IwAtCWXMg8YXl/s1600/rend1.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrSm4e6fVx-KhVpwzv-LG26Q0n93Q9yByVtRTpzIfzzYy_vmCQx-cmUQcfoKtZijP88QkiCI7P8_7R_M0ri_bZ7iy4t-nNQvXy9QwYceX74qBHEGBhRwp3xc0IwAtCWXMg8YXl/s320/rend1.png" /></a></div>
Not bad, but not very interesting. The grain is absolutely parallel to the long axis of the timbers. The pattern on the long surfaces, tangential to the tree rings, is not very convincing.
<p>The first improvement we can make is to simulate the fact that vertical growth happens at the top of a tree, which implies that there are fewer rings as one climbs the tree. We implement this by adding a value that increases as we move up the tree trunk to our radius / age calculation before truncating it; this has the effect of moving the pattern in towards the origin as we move up the tree. Here's the next effort at a node network:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhehEfyZaRgdlVuZOLVk0bzKmLg1aBP0kXH2d5Af7_nLbnhsOfRWGwshEkwbBAzIOD43pvwfiaVkx-XKL4iZ33lx7-W_MKugSmAjv4dFL_IV6luD7xnXx9di6t67KH7AuWLxxgY/s1600/node2.png" imageanchor="1" style=""><img border="0" height="231" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhehEfyZaRgdlVuZOLVk0bzKmLg1aBP0kXH2d5Af7_nLbnhsOfRWGwshEkwbBAzIOD43pvwfiaVkx-XKL4iZ33lx7-W_MKugSmAjv4dFL_IV6luD7xnXx9di6t67KH7AuWLxxgY/s320/node2.png" /></a></div>
and the rendering:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoZ0NJadC1j98xXsisZHzSUM7Wx6o_CYLCgptiKEKTi-REIikI4aBjNcp17aeiNAS4UE4yv46kWhTCoaH2qkdrRPtjVEDKXzDXft2svo8GWpTYNsBg0KwW65O1_hIc5jPjb8CX/s1600/rend2.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoZ0NJadC1j98xXsisZHzSUM7Wx6o_CYLCgptiKEKTi-REIikI4aBjNcp17aeiNAS4UE4yv46kWhTCoaH2qkdrRPtjVEDKXzDXft2svo8GWpTYNsBg0KwW65O1_hIc5jPjb8CX/s320/rend2.png" /></a></div>
Now, the grain appears to be diagonal to the length of the timber, and the surface that cuts through the top of the rings has a wishbone pattern that is reasonably realistic.
<p>Several factors cause the radial growth of a tree to slow down as it gets older. There is a fixed number of cells at the outside of a tree where growth occurs; these cells spread out and have less capacity to grow outwards. Also, a tree simply becomes less "vigorous" with age. Foresters use very complicated empirical models to estimate tree growth. I will choose a simple logarithmic function that captures the basic idea of a the rings getting closer together. It is plausible that a tree trunk would grow as much in its first 50 years as it does in the 500 years after that. The inverse of a logarithmic growth function is an exponential function; we implement that in our node network, and after trying some different values we arrive at this:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQekwaYoZIGDJ-GyZJOjRwJZMPON7y8jIouA9Owhpl6LQNfs8OPO1BYbYZet8hEvh14NPgUVFFGB1mZztVV0wHSnNNRWz5B5QXO5xBcyi5wxot9oB8zhIFq8WrFSf6OTXMYrwI/s1600/node3-exp.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQekwaYoZIGDJ-GyZJOjRwJZMPON7y8jIouA9Owhpl6LQNfs8OPO1BYbYZet8hEvh14NPgUVFFGB1mZztVV0wHSnNNRWz5B5QXO5xBcyi5wxot9oB8zhIFq8WrFSf6OTXMYrwI/s320/node3-exp.png" /></a></div>
and get these results:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1oXCKrr5aESXr9kfE-N934C3TD8I0ss4kiqgnFHSc-zTnQpq2l2edliXW_nWQyrGLjKdGc2X4dYZss_wH-QCqwEoZGyScrVUHbrqDnvKfkVgG8kfko25gloGa7Vo4TFwJRhbU/s1600/rend3-exp.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1oXCKrr5aESXr9kfE-N934C3TD8I0ss4kiqgnFHSc-zTnQpq2l2edliXW_nWQyrGLjKdGc2X4dYZss_wH-QCqwEoZGyScrVUHbrqDnvKfkVgG8kfko25gloGa7Vo4TFwJRhbU/s320/rend3-exp.png" /></a></div>
The first few rings are a little far apart in this model, but the older grain looks pretty good. We can play with the scale factor applied to the exponential to get the desired look.
<p>We are now ready to move from black-and-white to real wood colors. The output of the color ramp node is used to choose the mixture of the dark and light wood colors. The Blender mix node implements this:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7O76ZqBzONNII-BLxRj2rGFiFemAl-CsLE-pJijciFPoa2Prvhn_18xA4wcrAt9PtRXt3KB-3z66uW7eSYq-VXi8o1kQrERidA7o5-21gKDNfUWn3ZCPa1kdLEwEXfM2pZzJZ/s1600/node4-exp.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7O76ZqBzONNII-BLxRj2rGFiFemAl-CsLE-pJijciFPoa2Prvhn_18xA4wcrAt9PtRXt3KB-3z66uW7eSYq-VXi8o1kQrERidA7o5-21gKDNfUWn3ZCPa1kdLEwEXfM2pZzJZ/s320/node4-exp.png" /></a></div>
and we are starting to get something that looks like wood:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzD-OlNwENz07LAwx-6WKL9rQ_DNy_GvEF11UrBSovW5OUAlHntK5xSMia5UYV8X9b9pVnLyWEMEHPeKQ7tJPrXkIlN1CUetMPpfzFYTO18p6Tz7NM5VWn1ofkC0sjWoI94ARZ/s1600/rend4-exp.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzD-OlNwENz07LAwx-6WKL9rQ_DNy_GvEF11UrBSovW5OUAlHntK5xSMia5UYV8X9b9pVnLyWEMEHPeKQ7tJPrXkIlN1CUetMPpfzFYTO18p6Tz7NM5VWn1ofkC0sjWoI94ARZ/s320/rend4-exp.png" /></a></div>
One remaining fault of our scheme is that the center of the tree trunk is fixed at the center of the 3D object, so that the oldest rings always appear on the piece. This isn't particularly realistic: lumber could come from any part of a big tree. It is easy enough to relocate an object's origin in Blender, but it becomes inconvenient to manipulate the object if the origin is completely outside it. In my models I like to fix the origin to a corner of a timber. So, we can add a mapping node that displaces the whole ring structure:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF7Y0VlNnGv0r0TZTM1ZhKsdvl97Gho3EztPjg_qww3WTFuUp6DQhngCjw6gX836xGxYbkFSjYIk216-gdDNexmFBx3Iu4ykGNq58dQ4GnEFFf9EXePVg489vrvtfPCyFvQEi6/s1600/node5-exp.png" imageanchor="1" style=""><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF7Y0VlNnGv0r0TZTM1ZhKsdvl97Gho3EztPjg_qww3WTFuUp6DQhngCjw6gX836xGxYbkFSjYIk216-gdDNexmFBx3Iu4ykGNq58dQ4GnEFFf9EXePVg489vrvtfPCyFvQEi6/s320/node5-exp.png" /></a></div>
with pleasing results:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ4VhG-RAzkFulgvsC7QWdl6w9szs1rdZW7jhiv3J3QX8eBMAuNsuBKuLWUtaeAftLu1qf87QapRi0SIdlH3gNCWfumyGk19pQ20s3szsh5sjIio4WcNTo90yJSPPAwCefLBSd/s1600/rend5-exp.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ4VhG-RAzkFulgvsC7QWdl6w9szs1rdZW7jhiv3J3QX8eBMAuNsuBKuLWUtaeAftLu1qf87QapRi0SIdlH3gNCWfumyGk19pQ20s3szsh5sjIio4WcNTo90yJSPPAwCefLBSd/s320/rend5-exp.png" /></a></div>
We are more interested in rendering assemblies of wood than isolated boards, and it is here where our texture starts to shine. In showing a portion of a frame corner:
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguTa7WQfDIolqw9hs5SiLBs6iPTobwTZozUT9cF2aYxqDz6cT8EBo5dO3G4Am0F4vN3X-iOgCVHRlr6bpjtq3Gd_6UaKhApIFXLZb6Ox6UK2h2WYCjmNHHAF37rhY_lNgFCTC5/s1600/frame-exp.png" imageanchor="1" style=""><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguTa7WQfDIolqw9hs5SiLBs6iPTobwTZozUT9cF2aYxqDz6cT8EBo5dO3G4Am0F4vN3X-iOgCVHRlr6bpjtq3Gd_6UaKhApIFXLZb6Ox6UK2h2WYCjmNHHAF37rhY_lNgFCTC5/s320/frame-exp.png" /></a></div>
The wood texture clearly shows the grain orientation on the two pieces, and also highlights the joint where they meet. It also looks pretty good. In real life a cabinet maker might not want the joint to be so obvious, but I've chosen exaggerated colors for the early and late growth. In a technical illustration we want to see those joints. If we wanted to make a more realistic rendering, even though that is not one of our objectives for the texture, we would simply choose different colors.
<p>There are still some problems with the texture. The rings are perfectly circular and regular, with no variation from year to year or around the circumference of the tree. The material node network is flexible, but the various parameters are scattered around it and would be painful for a user to manipulate. Also, it will be messy to copy the network into all the various wood materials that we might wish to correct. In <a href="http://shiny-dynamics.blogspot.fr/2012/04/blender-wood-texture-part-2.html">the next installment</a>, we will tackle the first issue using noise textures and the others using Blender node groups.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com1tag:blogger.com,1999:blog-21382508.post-1284671212013233462010-09-01T07:42:00.000-07:002010-09-02T01:28:57.956-07:00The Seamless Engine Goes GlobalI've been doing more work on the terrain engine I described in my <a href="http://shiny-dynamics.blogspot.com/2010/08/terrain-engine-for-osgearth.html">last post</a>, which I'm now calling "seamless." It now works pretty well on a global scale.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1MCgJI2AhQ4Q_dqCb1KKGrm9CjZQOPZP0GwpDm580g59yXfIQ4p_FIonYzSQVCSBrnOOpathZr4z0O7MW2KutLP6QxAwh-YgvLLbib0gJBQew6nsVPjNYeza9ZWbTpEpCt5Or/s1600/globe.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1MCgJI2AhQ4Q_dqCb1KKGrm9CjZQOPZP0GwpDm580g59yXfIQ4p_FIonYzSQVCSBrnOOpathZr4z0O7MW2KutLP6QxAwh-YgvLLbib0gJBQew6nsVPjNYeza9ZWbTpEpCt5Or/s320/globe.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5511971937568980738" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFwLPzsHmL7EISQAVt0_fdgKIz1agBoYhH_ypAYydm5K-8tEFaBmDppOfE5_3SyfGpgg1Wj8C8NPfxR0jcuv8dwMVz7C98z-R4ZsWpo83fTVQWKWr9aRK2nQzg_tjyoeKWckAJ/s1600/poly.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFwLPzsHmL7EISQAVt0_fdgKIz1agBoYhH_ypAYydm5K-8tEFaBmDppOfE5_3SyfGpgg1Wj8C8NPfxR0jcuv8dwMVz7C98z-R4ZsWpo83fTVQWKWr9aRK2nQzg_tjyoeKWckAJ/s320/poly.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5511972172730166530" /></a><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiXkGaS_oK2FDNhmGm4hgJljp4iXN5ESCIhBSZ6RY5wC9ezh5jN6IrI1x7tasHWjDpB2u_BmuCdktLzI4FcQ2mkyHFmX-4aHSPQjhypfuexF03i3PdbkGGk_J9hfjY5JCn4shl/s1600/wire.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiXkGaS_oK2FDNhmGm4hgJljp4iXN5ESCIhBSZ6RY5wC9ezh5jN6IrI1x7tasHWjDpB2u_BmuCdktLzI4FcQ2mkyHFmX-4aHSPQjhypfuexF03i3PdbkGGk_J9hfjY5JCn4shl/s320/wire.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5511972291331359474" /></a><br /><br />There's still lots of work to do, especially on optimization at the OpenGL level, but I'm quite pleased, especially with how easy it is to get the height and image data out of osgEarth.<br /><br />I encountered an interesting issue with the <a href="http://www.progonos.com/furuti/MapProj/Normal/ProjPoly/projPoly2.html#CubQSC">Quadrilateralized Spherical Cube</a> that I was planning to use. I did find a good formal reference to the math behind the projection at <a href="http://www.atnf.csiro.au/people/mcalabre/WCS/">this site</a>. It turns out that the COBE satellite didn't use the QSC projection, but a slightly simpler one. Anyway, the seamless terrain algorithm requires a good estimate of the edge length of a patch on the grid and also a calculation of the minimum distance from the eye point to the edge. These are both difficult to calculate with the QSC projection, in part because the projection is not differentiable on the diagonals of the cube faces. I came up with another projection in which the grid lines are all great circles on the sphere:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG7Q4m4pDT17q_fX6OFDxpuVjb6hCNUHb_hNpAq1EBisZ8K47X5Xtkn5T8AK0kXx_i2GCl3kUHv42ZpCFGaZSM1YYewOCXE2Sm2hW-OBqUj7ihrg9WPY18gXJULCKW7OG2F1I7/s1600/euler.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 215px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG7Q4m4pDT17q_fX6OFDxpuVjb6hCNUHb_hNpAq1EBisZ8K47X5Xtkn5T8AK0kXx_i2GCl3kUHv42ZpCFGaZSM1YYewOCXE2Sm2hW-OBqUj7ihrg9WPY18gXJULCKW7OG2F1I7/s320/euler.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5511968532550686610" /></a><br /><br />I'm calling this projection an "Euler projection" by analogy to one class of Euler angles in which a rotation is measured with respect to fixed axes. For the equatorial cube faces, the vertical grid lines are the same as meridians of longitude, but the "latitude" is measured perpendicular to the horizontal axis, not in the longitudinal plane. The fact that the grid lines are now all segments of great circles makes the math almost trivially easy.<br /><br />Now, we're not displaying a sphere (unlike some programs :) but an ellipsoid, so this measure will break down as we get close to the earth, but I'm prepared to hack that problem away.<br /><br />The source to the seamless engine is available from <a href="http://gitorious.org/seamless"> this Git repository</a>. Compilation requires osgEarth and Open Scene Graph from SVN and the whole thing is still a bit raw.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-87664015242693729702010-08-15T14:53:00.000-07:002010-08-15T15:26:31.031-07:00a terrain engine for osgEarthLately I've been working on an experimental terrain engine for <a href="http://www.osgearth.org/">osgEarth</a> on behalf of the folks at <a href="http://pelicanmapping.com/">Pelican Mapping</a>. osgEarth is a cool project that imports a variety of geographic raster and vector data into Open Scene Graph without requiring a lengthy pre-processing step. I've implemented the algorithm described in <a href ="http://wscg.zcu.cz/wscg2007/Papers_2007/full/C43-full.pdf">Seamless Patches for GPU-Based Terrain Rendering</a>. The main feature of this technique is that LOD is determined at the patch edges, so that the polygons on either side of an edge always line up. The four triangles of a square patch may have different resolutions and are stitched together by a small number of strips. Here's a scene with Mt. Fuji: <br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4KJzD3no_f6Nn6sPedq7iksm9qqiCjSJuCX1-fGZeoi5p_I30Gc_d72CWNx8tYFyn2_27i9c2HKn61EbkWqJRT2d9U2x7tWrCyAI5pdwZPi7AOrV7Rv4wC4CBw-x5Gt_5oFj7/s1600/fuji.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 182px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4KJzD3no_f6Nn6sPedq7iksm9qqiCjSJuCX1-fGZeoi5p_I30Gc_d72CWNx8tYFyn2_27i9c2HKn61EbkWqJRT2d9U2x7tWrCyAI5pdwZPi7AOrV7Rv4wC4CBw-x5Gt_5oFj7/s320/fuji.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5505762156285004802" /></a><br /><p><br />And the wire frame view:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeSOiIQoHHGE2zzYW0YwnhU00CShBidJe704qSxhyphenhyphennGY4tce2wAI736FY4IUVQO_M22NO97fKBLsbSGyJ7c8tTd8AYmWGv8r-D_AnUHDsSe4Q434TFEhbXgQtzFyKR6Pjm_uNx/s1600/wirefuji.jpg."><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 182px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeSOiIQoHHGE2zzYW0YwnhU00CShBidJe704qSxhyphenhyphennGY4tce2wAI736FY4IUVQO_M22NO97fKBLsbSGyJ7c8tTd8AYmWGv8r-D_AnUHDsSe4Q434TFEhbXgQtzFyKR6Pjm_uNx/s320/wirefuji.jpg." border="0" alt=""id="BLOGGER_PHOTO_ID_5505762459052348978" /></a><br /><p><br />Of course, the objective is to get to a whole-earth scheme. I'm going with a projected cube approach, where the basic seamless algorithm is run on the six sides of the cube. I like the <a href="http://www.progonos.com/furuti/MapProj/Normal/ProjPoly/projPoly2.html#CubQSC">Quadrilateralized Spherical Cube</a> which was developed for processing the data from the NASA <a href="http://lambda.gsfc.nasa.gov/product/cobe/skymap_info_new.cfm">Cobe</a> satellite. A nice property of the projection is that is equal-area, so that a recursive devision in the cube face space should result in 4 equal-area spherical quads on the earth. It looks pretty:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcS3VEM_CSolGWQuWRfwCnL5ffa8VYXbEmsU1imUdSVOSft3fyXBbNUWkUs7vlDk3cRncsR0tP8RrzexNErgSmwwZjlROMpTFINkCcxIqYqBJdAqy7l2_JIOk6xlN-l_bYdVuz/s1600/cobe.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 215px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcS3VEM_CSolGWQuWRfwCnL5ffa8VYXbEmsU1imUdSVOSft3fyXBbNUWkUs7vlDk3cRncsR0tP8RrzexNErgSmwwZjlROMpTFINkCcxIqYqBJdAqy7l2_JIOk6xlN-l_bYdVuz/s320/cobe.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5505765868195673170" /></a><br /><p><br />Here's hoping that the calculation of the projection doesn't become a big bottleneck. Incidently, it was very hard to find the equations describing the projection: I eventually found a nice formulation <a href="http://www.sai.msu.su/~megera/wiki/SphereCube">here</a>. But beware: there's a typo in Equation 3-38. "cos(theta)" should be "cos(phi)."Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com1tag:blogger.com,1999:blog-21382508.post-43760099740706348582010-06-30T01:56:00.000-07:002010-06-30T02:41:56.843-07:00Cool LinksHere are some cool blogs I've come across recently. <a href="http://virtualglobeandterrainrendering.blogspot.com/">Virtual Globe and Terrain Rendering</a> is devoted to a book-in-progress of the same name and has a nice collection of links to resources and other interesting blogs. <a href="http://rastergrid.com/blog/">RasterGrid</a> has good articles on new features of OpenGL; I especially like <a href="http://rastergrid.com/blog/2010/02/instance-culling-using-geometry-shaders/">Instance Culling Using Geometry Shaders</a>, about doing culling on the GPU, and <a href="http://rastergrid.com/blog/2010/01/uniform-buffers-vs-texture-buffers/">Uniform Buffers vs Texture Buffers</a>. For an old graphics fossil like me, these are great topics in modern 3D graphics programming. Also, the <a href="http://outerra.blogspot.com/">Outerra</a> engine guys are doing cool stuff and writing about it too. I've added these to my blog list, and so should you!Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-54727270159127791132010-06-16T14:45:00.000-07:002010-06-16T14:54:57.504-07:00Everything You Always Wanted to Know About StateSets (But Were Afraid to Ask)Here's <a href="http://www.bricoworks.com/articles/stategraph/stategraph.html">part 2</a> of my articles on Open Scene Graph graphics state management. <a href="http://www.bricoworks.com/articles/stateset/stateset.html">Part 1</a> covered the <code>StateSet</code> class, which OSG programmers should be familiar with. This part looks behind the scenes at how the OSG "backend" optimizes rendering for minimizing graphics state changes. Enjoy!Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-62995430983937347382010-05-13T03:41:00.000-07:002010-05-13T04:26:09.548-07:00Paper Models in BlenderI just discovered this <a href="http://pixeloz.deviantart.com/art/Free-Paper-Model-Design-E-Book-158441085">e-book</a> on using free software, like <a href="http://www.blender.org/">Blender</a> and <a href="www.inkscape.org">Inkscape</a>, to design paper models. Actually, these <a href="http://www.currell.net/models/gall_titanic.htm">models</a> are usually made of thicker card stock than paper. Anyway, this is about as far from real-time simulation as you can get, but the e-book is still a lot of fun.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com2tag:blogger.com,1999:blog-21382508.post-58176077202327587372010-04-16T02:17:00.000-07:002010-04-16T02:20:42.268-07:00I've written the first part of a series on Open Scene Graph graphics state optimization <a href="http://www.bricoworks.com/articles/stateset/stateset.html">here</a>. This goes into some detail about graphics modes, attributes, the <code>StateSet</code> object, and the low level <code>State</code> object. Check it out.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-82151250338173409282010-03-23T08:03:00.001-07:002010-03-23T08:05:46.577-07:00Floating Point Depthbuffers in OSGI submitted a new demo to the <a href="http://www.openscenegraph.org">Open Scene Graph</a> project that shows how to set up a floating point depth buffer. The standard way to do this is to create a <a href="http://www.gamedev.net/reference/articles/article2331.asp">Frame Buffer Object</a> and attach a depth texture with a floating point format. That won't work if you're doing multisampling antialiasing; it that case, you attach a "render buffer" that has the appropriate number of samples to the FBO. Open Scene Graph takes care of many of the details for you.<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPGLTHzhd8nfXycSn62QQYzbEnH2ImOhyxxG5Re_HbHSNIGbOFyHo5_ORlhPdNtnw2YJYFtaKt-cDkICCksXlXpVNBogVl5gVum2P7Zmv1YNZy5pnlDutRFyF5hkXUPmsrNFop/s1600-h/screen_shot_0_0.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPGLTHzhd8nfXycSn62QQYzbEnH2ImOhyxxG5Re_HbHSNIGbOFyHo5_ORlhPdNtnw2YJYFtaKt-cDkICCksXlXpVNBogVl5gVum2P7Zmv1YNZy5pnlDutRFyF5hkXUPmsrNFop/s320/screen_shot_0_0.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5451845381897461938" /></a><br />The demo shows how to set up an FBO with a floating point depth buffer, as well as the effects of "inverting" the depth test. Setting 1.0 to correspond to the near plane and 0.0 to the far plane turns out to have <a href="http://www.humus.name/index.php?ID=255">better behavior than the more usual way.</a> Also, the demo allows the user to cycle between all the available multisampling modes, as well as between floating point and integer depth buffers, of course. If you can't wait for this to show up in the OSG source tree, <a href="http://article.gmane.org/gmane.comp.graphics.openscenegraph.cvs/7284">get the patch</a> yourself.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-60583728345661618222010-03-01T01:15:00.000-08:002010-03-01T01:20:45.162-08:00Vertex Cache Optimization for OSGI implemented Tom Forsyth's <a href="http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html">Vertex Cache Optimization</a> algorithm in <a href="www.openscenegraph.org">Open Scene Graph</a> (OSG). The quick summary is that indexed lists of triangles in a mesh are more efficient than traditional representations like triangle strips. GPUs keep a cache of the post-transform values for the most recently used vertices in a mesh. This can avoid a whole lot of work, such as rerunning vertex shaders. Tristrips don't make good use of this cache because they introduce a new vertex for every triangle and only reuse the last two vertices. On the other hand, a random mesh order isn't good either; the triangles in a mesh need to be in a good order to take proper advantage of the cache. Tom describes an efficient algorithm for optimizing a mesh for the vertex cache. It produces an order that is "oblivious" to the actual cache size i.e., it will work well on a range of hardware.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2IgALXQPlS0zEXcCaWFVmSYWRFfazMDCXfZ9H0yJWMck7x8zv7ljzytGQqbsWsB9VM7mSeAKEAuSdAmo7d7yrrl_3KNvyr_oFjzm-4KO13SdnFYkNpP08Jxe74NYITImy7h5c/s1600-h/screen_shot_0_0.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2IgALXQPlS0zEXcCaWFVmSYWRFfazMDCXfZ9H0yJWMck7x8zv7ljzytGQqbsWsB9VM7mSeAKEAuSdAmo7d7yrrl_3KNvyr_oFjzm-4KO13SdnFYkNpP08Jxe74NYITImy7h5c/s320/screen_shot_0_0.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5443592093647930802" /></a><br />The algorithm does work as advertised. I tried it on the <a href="http://www.cc.gatech.edu/projects/large_models/happy.html">Happy Buddha</a> model, which is a monster-size mesh with over a million triangles. On my machine the optimization reduces the draw time for this mesh by 38 percent, which is enormous. This is a special case because the mesh is so large and regular, and Your Milage May Vary, but it does seem like a worthwhile optimization for large meshes. I've submitted my code to the OSG project.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-27726921091239112682010-02-23T06:19:00.000-08:002010-02-23T06:45:44.836-08:00more VirtualPlanetBuilder funLet's add some other imagery to our Mt. Washington terrain. The <a href="http://online.wr.usgs.gov/ngpo/doq/">digital orthophoto quadrangles</a> published by the USGS are a decent source of free aerial photographs of the U.S. These images are rectified, which means that any effects from the tilt of the camera and relief of the terrain have been removed; just the thing for draping over a DEM. Unfortunately they are in black-and-white, but the resolution is 1 meter, which isn't bad. The image files are georeferenced, very convenient for building a mosaic with VirtualPlanetBuilder because we don't need to specify any additional projection or position parameters with each image. Generally one topographic map quad is divided into 4 DOQs. This makes for a marathon download session at the USGS <a href="http://edcsns17.cr.usgs.gov/EarthExplorer/">website</a>; maybe someone knows a better way?<br /><br />It's straightforward to build a new database using an <code>osgdem</code> command similar to that used to build the map overlay database. One complication is that we now have 24 image files to deal with, instead of 5, and I don't feel like typing all the names on the command line. The downloaded files from the USGS have the form <code>O4407145.NES.837049.tgz</code>, and their only contents seems to be another tar file. Extracting that, we get the TIFF file and a header file. I arranged these all in directories; for example, the path to one TIFF file is <code>O4407145.NES/O4407145.NES.837049.tif</code>. We generate the command line with a little Unix magic:<br /><code><br />ls O*.*/*.tif | sed -e's;.*;-t &;' \<br />| xargs osgdem --TERRAIN --geocentric -d elev-meters.tif \<br />-l 8 -o wash-photo.ive<br /></code><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipAaqEPKQ3X_k4csEq-2mawx7_o4mJwcp5dJYKO_f-72QyVhOUZdQB3N9QpE5pUvKAW6ECovu2I2_Yo5rh-arBxolm3DQZKpDU9UoYvhSoUnawdG5U82USCJoqAxN6sBv89DTX/s1600-h/orthophoto.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 200px; height: 125px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipAaqEPKQ3X_k4csEq-2mawx7_o4mJwcp5dJYKO_f-72QyVhOUZdQB3N9QpE5pUvKAW6ECovu2I2_Yo5rh-arBxolm3DQZKpDU9UoYvhSoUnawdG5U82USCJoqAxN6sBv89DTX/s200/orthophoto.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5441445072158905634" /></a>We have a nice image when we look at the scene with <code>osgviewer</code>, although the gray imagery would benefit from colors and more contrast. But what would be very cool is display the topographic map tiles and the orthophotos on the same scene and transition between the two. VirtualPlanetBuilder lets you specify that images belong to different layers, and the OSG example program <code>osgmultitexturecontrol</code> supports transitioning between layers based on elevation above terrain. In order to build the new database, we need to specify a "--layer 1" argument before each orthophoto:<br /><code><br />ls O*.*/*.tif | sed -e's;.*;--layer 1 -t &;' |\<br />xargs osgdem --TERRAIN --geocentric -d elev-meters.tif \<br />-t carter-dome-trim.tif -t crawford-trim.tif -t jackson-trim.tif\<br /> -t stairs-trim.tif -t k44071c3/k44071c3-trim.tif \<br />-l 8 -o wash-layer.ive<br /> </code><br />Phew! Unfortunately, we don't see the map layer at all with the <code>osgmultitexturecontrol</code> program from the OSG distribution. A look at the code reveals the problem: the elevation at which the transition occurs is hard-wired at 1000 kilometers. That's fine for viewing a whole-earth database from space, but doesn't do us any good. I hacked in an elevation transition argument, and got some better results. We start with the map view:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDLI5MoAZ3nmpwCvNsJAHx-bZCP8jjLsMlF7e47OGT4tK9nV545Nu9jq02YHwNsb3eO7WRv33nIn7Xg481qls3D8qumWZgtEYZd7VQlW2TvJMJoCDxnL7RtJnCeGzcpiFrF7pb/s1600-h/screen_shot_0_0.jpg"><img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDLI5MoAZ3nmpwCvNsJAHx-bZCP8jjLsMlF7e47OGT4tK9nV545Nu9jq02YHwNsb3eO7WRv33nIn7Xg481qls3D8qumWZgtEYZd7VQlW2TvJMJoCDxnL7RtJnCeGzcpiFrF7pb/s320/screen_shot_0_0.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5441445418031415410" /></a><br />and as we zoom in, we transition from this:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTmPKJiIo2GWiE-13bBGnirB_t5KtInhfd3abDja1sbnglIdysXm9-Yyd09oaf6XV9aZ3Lu7k6KxNayLh88Yh4WSyubwFz-NuwUBhQjt_Eh46yIKAAFEZyuLoNP5qDklJ2jHRM/s1600-h/screen_shot_0_1.jpg"><img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTmPKJiIo2GWiE-13bBGnirB_t5KtInhfd3abDja1sbnglIdysXm9-Yyd09oaf6XV9aZ3Lu7k6KxNayLh88Yh4WSyubwFz-NuwUBhQjt_Eh46yIKAAFEZyuLoNP5qDklJ2jHRM/s320/screen_shot_0_1.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5441445652805002994" /></a><br />to this:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg62PFylwbFxObDfA0zaXzti-6bsA6SxAEQxjSG6zKHEiFJGtpOEzC664jfNJRtmk-2r1eV_pchPgcLu5Pkv4CjfIVUlyDmJs8-iDuJ7VUAzzJ13CAXB82G7_pPjT82Dd9zYrhK/s1600-h/screen_shot_0_2.jpg"><img style="cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg62PFylwbFxObDfA0zaXzti-6bsA6SxAEQxjSG6zKHEiFJGtpOEzC664jfNJRtmk-2r1eV_pchPgcLu5Pkv4CjfIVUlyDmJs8-iDuJ7VUAzzJ13CAXB82G7_pPjT82Dd9zYrhK/s320/screen_shot_0_2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5441445872653511538" /></a><br />Pretty cool. It would be nice to spruce the photos up a bit, perhaps by using the approach of <a href="http://web.archive.org/web/20080414041054/http://www.oryx.com/ams/landsat.html">merging color from lower resolution LANDSAT imagery</a> with these high resolution DOQs.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-6055123889350505662010-02-02T06:31:00.001-08:002010-02-02T09:46:06.769-08:00Fun with VirtualPlanetBuilderI've been playing with the <a href="http://www.openscenegraph.org/projects/VirtualPlanetBuilder">VirtualPlanetBuilder</a> tool for <a href="www.openscenegraph.org">Open Scene Graph</a> (OSG). This flexible program builds paged terrain databases with automatic level of detail that can be loaded into the standard <code>osgviewer</code> program or any other program that uses the standard OSG loaders. It takes heightfield data and imagery as input, and it accepts a variety of formats because it uses the <a href="www.gdal.org">GDAL</a> geospatial data library. If the input data is georeferenced, the excercise of building a terrain database is almost too easy.<br /><br />While VirtualPlanetBuilder can be used to build terrabyte-sized whole-earth databases, my own ambitions are so far more modest: build a small patch of terrain, drape some terrain over it, and fly around. Inspired by the <a href="http://www.geomart.com/products/raisedrelief/nationalparks.htm">vinyl relief maps</a> that my parents mounted on the wall of our apartment when I was growing up, I decided that it would be fun to drape topographic maps over the terrain. I made a little geocentric database of the Mt. Washington area. I found this <a href="http://www.emrl.byu.edu/gsda/data_dem_obtain.html"> useful set of links</a> to sources of digital elevation data.<br /><br />For the DEM data, I went with <a href="http://www.webgis.com/terr_us75m.html">webGIS</a>, which provides DEMs based on USGS quadrangles. This turned out to not be terribly convenient, as there's state and county map interface but nothing that graphically shows the layout of the quadrangles. I flipped back-and-forth to the USGS web site and, armed with my knowledge of the White Mountains, downloaded the DEM data for the Mount Washington, Carter Dome, Crawford Notch, Stairs Mountain, Jackson, and Mount Dartmouth quadrangles. There's probably a more automated way to do this.<br /><br />I downloaded the images of the map quadrangles using the <a href="http://www.usgsquads.com/mapfinder.html#MapFinder_for_Google_Earth"> usgsquads.com Goggle Earth interface</a>, but this was a bit broken too. The map that contains Mt. Washington itself is 7.5 x 15 minutes, and the download link to it was broken. I went to the <a href="http://edcsns17.cr.usgs.gov/EarthExplorer/">USGS EarthExplorer</a> to grab that map. I would have been better off getting all the map images there. The maps at usgsquads.com seem to be pretty old and their elevations are in feet, whereas the map from USGS was in meters. Oh well, it still looks nice.<br /><br />Because the <code>.dem</code> height data files and the TIFF map files are geolocated, it is quite straight forward, after a little preparation, to feed them to VirtualPlanetBuilder's <code>osgdem</code> program and get a data base. But my first effort had a problem; the maps have borders, and they show up on the terrain!<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGEGvdV0v0EXGmRL6RRNr-Wl2Rmj9zS66R-yyHoYhCJbBjNyTLGhzqRkh4Ir68QcOJ7Wl9BA27kfAOjHlBUVdAvyWTehsOEufaczLCjDqLlwps22BILW4EHVy-a5NPf814u8OF/s1600-h/wash-shot.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGEGvdV0v0EXGmRL6RRNr-Wl2Rmj9zS66R-yyHoYhCJbBjNyTLGhzqRkh4Ir68QcOJ7Wl9BA27kfAOjHlBUVdAvyWTehsOEufaczLCjDqLlwps22BILW4EHVy-a5NPf814u8OF/s320/wash-shot.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5433694437716119954" /></a><br />Whoops. Because the map images are projected in UTM, the latitude and longitude boundaries of the maps are not horizontal and vertical in the image, so it would be awkward to trim the maps in a program like the Gimp.<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQoTqR0G4LmFcq2wAbFzaJZyuT-BDfFcAsQiSnYs26mxXZ89hvCsJf7vddFShp5dAE0Mc25qHnJ7-HoQbTBa3H3f2jUlRY8rQSECfttK_l7tNsDLNT5KN6J2Fdb67rK0eENA1a/s1600-h/carter-dome.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 151px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQoTqR0G4LmFcq2wAbFzaJZyuT-BDfFcAsQiSnYs26mxXZ89hvCsJf7vddFShp5dAE0Mc25qHnJ7-HoQbTBa3H3f2jUlRY8rQSECfttK_l7tNsDLNT5KN6J2Fdb67rK0eENA1a/s200/carter-dome.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5433700502572327810" /></a><br />Fortunately <code>osgdem</code> needs its data in a geographic coordinate system and reprojects if necessary, so we can do the reprojection ourselves and trim the borders using a GDALutility. <br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAXKp3kbpOPDYutEyT0GU-V1kLdg_Mt44Vdw4Y3juHaPWNVC9VJXZsIH5kgwWqwRrkILLhCW_6OlNbEtz_2nmjLutDZv8KNVhpK7UemcRpNOJift2MK7QDwJ2BjdjC_bwWDoXB/s1600-h/wash2-shot.jpg"><img style="float:center; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAXKp3kbpOPDYutEyT0GU-V1kLdg_Mt44Vdw4Y3juHaPWNVC9VJXZsIH5kgwWqwRrkILLhCW_6OlNbEtz_2nmjLutDZv8KNVhpK7UemcRpNOJift2MK7QDwJ2BjdjC_bwWDoXB/s320/wash2-shot.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5433694914670230754" /></a><br />That looks better, but the relief of the mountains seems exaggerated, just like those vinyl relief maps. A little snooping with <code>gdalinfo</code> shows that the elevation data is in feet, but <code>osgdem</code> builds a geocentric database using meters, and that info got lost somewhere along the way. Fortunately it's easy to fix this up with a GDAL tool.<br /><br />Here's the complete recipe. First, many of the .dem files have the same name, so I renamed them in the directories where I unpacked them:<br /><code><br />for i in *.zip; do<br /> mkdir ${i%.zip}<br /> mv $i ${i%.zip}<br />done<br /></code><br /><br />Then I combined them into one TIFF and built overviews: <br /><code><br />gdalwarp 16*/*dem elev.tif<br />gdaladdo -r average elev.tif 2 4 8 16 32<br /></code><br />The overviews are probably not necessary for such a tiny database, but it's recommended anyway. We have to fix the feet / meters problem:<br /><code><br />gdal_translate -scale 0 1.0 0 0.3048 elev.tif elev-meters.tif<br /></code><br /><br />Next, fix up the map borders. The maps are encoded using index color, which doesn't doesn't work well with fancy interpolation methods, so expand them into RGB:<br /><code><br />for i in carter-dome.tif jackson.tif stairs.tif k44071c3/k44071c3.tif ; do<br /> gdal_translate -expand rgb $i ${i%.tif}-rgb.tif<br /> done<br /></code><br />and then use <code>gdalwarp</code> to reproject and trim. For example:<br /><code><br />gdalwarp -t_srs "WGS84" -te -71.25 44.25 -71.125 44.375 \<br /> -r cubicspline carter-dome.tif carter-dome-trim.tif<br /></code><br />Build overviews:<br /><code><br />for i in carter-dome-trim.tif crawford-trim.tif jackson-trim.tif \<br />stairs-trim.tif k44071c3/k44071c3-trim.tif ;do<br />gdaladdo -r average $i 2 4 8 16 32;<br />done<br /></code><br />Finally, run <code>osgdem</code>:<br /><code><br />osgdem --TERRAIN --geocentric -d elev-meters.tif -t carter-dome-trim.tif \<br />-t crawford-trim.tif -t jackson-trim.tif -t stairs-trim.tif \<br />-t k44071c3/k44071c3-trim.tif -l 8 -o wash.ive<br /><code><br />and have fun!<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2R6xv3TBtRBnu6sLf-nO6Og8OTcvh_pE49JjgzVgB0kGxk9WvcXD6HmEtPxuwWdEuhGSQRIN3i0mGyMWoMsyxV8z7EKHVF-86H28VXn5sriFAN_8xgAOn285nuYHQDRsOKOfo/s1600-h/wash3-shot.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2R6xv3TBtRBnu6sLf-nO6Og8OTcvh_pE49JjgzVgB0kGxk9WvcXD6HmEtPxuwWdEuhGSQRIN3i0mGyMWoMsyxV8z7EKHVF-86H28VXn5sriFAN_8xgAOn285nuYHQDRsOKOfo/s320/wash3-shot.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5433695290032056114" /></a>Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com1tag:blogger.com,1999:blog-21382508.post-11608872548298365622007-12-15T01:07:00.000-08:002007-12-15T01:23:10.245-08:00Dead blog?I suppose a year-and-a-half without an update qualifies as a dead blog. I've even dropped off the blog roll at <a href="http://planet.lisp.org/">Planet Lisp</a> due to inactivity. Sniff.<br /><br />Anyway, I've been busy in that time. I've joined <a href="http://redhat.com">RedHat</a> where I'm working on the <a href="http://sourceware.org/frysk/">Frysk</a> debugger/system profiler tool. It's all written in Java, which is an interesting experience for this old Lisper. We're using compiled Java via gcj, which gives you the worst of both worlds: you're stuck writing in Java, but you don't have the rapid turn-around advantages of a byte-coded language. Oh well. Compilation is getting faster as gcj adapts ecj and other Java compiler technology. Otherwise the project is a lot of fun and has to deal with many thorny, low-level issues. Smart colleagues are a requirement for a good job experience, and RedHat sure has 'em.<br /><br />I've been indulging my old passion for real-time computer projects in a big way. I've made some contributions to <a href="http://www.openscenegraph.org">Open Scene Graph</a>, and I have become a committer on the <a href="http://flightgear.org">FlightGear</a> flight simulator project. Why waste time flying around in Microsoft Flight Simulator when you could be coding?Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com0tag:blogger.com,1999:blog-21382508.post-1146651081503153722006-05-03T02:56:00.000-07:002006-05-03T05:21:52.426-07:00Macro Madness<p>The macros -- and madness -- I'm referring to today have little to do with Lisp, except as a cautionary tale. No, I'm talking about TeX and LaTeX, the other major language famous for its macros. I format my <a href="http://www.bricoworks.com/moore/res.pdf">English résumé</a> and <a href="http://www.bricoworks.com/moore/cv.pdf">French C.V.</a> using a motley collection of LaTeX packages that I've found over the years. The English version uses resume.sty, originally by Stephen Gildea, and dates from 1988! The French one uses <a href="http://www.lrde.epita.fr/~didier/">Didier Varna's</a> <a href="http://www.lrde.epita.fr/~didier/software.php">CurVe</a>package which, I've found, does a good job of outputting a C.V. in a French / European style. His C.V. is a lot sexier than mine, but one has to start somewhere.<br /><p>The style I adapted (i.e., copied) from Didier puts the years of an activity in the margin of an entry, bold faces the job title and company, puts the city in normal type in parentheses, and starts right in with the description. A consulting company asked me for my C.V. in a different format: months and years of dates (not in the margin), company and city in bold, job title on the next line, followed by the job description in bullet points. For this long-time TeX amateur it seems vastly preferable to conditionalize this somehow rather than keeping two different version of a document around. In terms of CurVe, I need the prologue of each entry to expand differently in each version and do something about establishing a list environment for the consulting version. Given this input from my wrapper macro:<br /> <code><br /> \Entry{août 1999}{août 2001}{Software Design Engineer}{Amazon.com}{Seattle<br /> WA, États-Unis}{blah blah} <br /> </code><br /> I want:<br /> <code><br /> \entry[1999 -- 2001]{\textbf{Software Design Engineer, Amazon.com}<br /> (Seattle WA, États-Unis)}{blah blah}<br /> </code><br /> in the regular version and:<br /> <code><br /> \entry{\textbf{août 1999 -- août 2001 Amazon.com, Seattle WA, États-Unis}\par%<br /> \textbf{Software Design Engineer:} blah blah}<br /> </code><br /> in the consulting version. This doesn't seem too bad, but there are two big complications:<br /> <ul><br /> <li>If the years of the date are the same, I only want one year in the margin of the regular version;<br /> <li>I want to be able to write <code>\now</code> instead of a date and get "présent".<br /> </ul><br /> Furthermore, I wanted to save some typing and write dates like "août 2001" and not as parameters to a macro like <code>\date{août}{2001}</code> or, worse, as separate parameters to the <code>\Entry</code> macro. This is one of those little decisions that, in retrospect, are incredibly stupid and lead to disaster or, at best, a lot more work, but otherwise I wouldn't have anything to write about today.<br /><p>Like good Lisp programmers we'll attack this in a top-down and bottom-up manner at the same time. Assuming an <code>\ifcvstyle</code> conditional that chooses between the two styles, the LaTeX code I wrote above suggests these definitions: <br /> <code><br />\ifcvstyle%<br />\newcommand{\Entry}[6]{%<br /> \entry[\@years{#1}{#2}]{\textbf{#3, #4} (#5) #6}}%<br />\def\@@cvdate#1 #2\relax{#2}<br />\else%<br />\newcommand{\Entry}[6]{\entry{\textbf{#1 -- #2 #4, #5}\par\textbf{#3:} #6}}<br />\def\@@cvdate#1 #2\relax{#1 #2}<br />\fi<br />\newcommand\@years[2]{\ifthenelse{\equal{\cvdate{#1}}{\cvdate{#2}}}{%<br /> \cvdate{#1}}{%<br /> \cvdate{#1} -- \cvdate{#2}}}<br />\newcommand\cvdate[1]{\@cvdate#1\relax}<br /></code><br /><code>\ifthenelse</code> is LaTeX's all-purpose conditional macro; here we use it to compare two strings because plain TeX can't do that itself. OK, this isn't too bad; we can choose the basic form of the parameters to <code>\entry</code> and pick apart the parts of the date using TeX's powerful if bizarre macro parsing capabilities. But what about <code>\@date</code>, which we haven't defined yet? That submacro checks if the date argument is equal to <code>\now</code> and either returns that or proceeds with the date parsing.<br /><p>My first try at <code>\@date</code> was something like:<br /> <code><br /> \newcommand\@cvdate[1]{\ifthenelse{\equal{#1}{\now}}{#1}{\@@cvdate#1}}%<br /> </code><br /> The intent is to look at the first "token" (basically, character or control sequence beginning with backslash); if it is <code>\now</code>, just use that, otherwise put the token back and let <code>\@@cvdate</code> go to town. But this exploded in some cryptic way. We have to enter the shady world of TeX "expandability" to understand why.<br /><p>It's somewhat obvious that TeX macro definitions are simply templates into which parameters are substituted, although of course these templates can contain arbitrarily complicated code. In the course of this template substitution, called "expansion" (duh), some evaluation can take place; for example, primitive conditionals such as our <code>\ifcvstyle</code> can be evaluated and expanded. But many, many things in TeX can not be, such as definitions made with <code>\def</code>, and assignments to registers, things that complex macros invariably do. A macro is said to be expandable if it produces its intended result only through expansion. Many macros require their parameters to be expandable because they pick apart the results through various clever tricks. So, back to <code>\ifthenelse</code>: it is an extremely complicated macro that is not expandable, but the arguments passed to the predicate in its test part must be! Our <code>\@cvsdate</code> is used in <code>\@years</code> in the test of an <code>\ifthenelse</code> and therefore needs to be expandable. Oh well, can't use <code>\ifthenelse</code>.<br /><p>TeX's primitive conditionals are expandable, so let's try again:<br /> <code><br /> \def\@cvdate#1{\ifx#1\now\now\else\@@cvdate#1\fi} <br /> </code><br /> <code>\ifx</code> compares characters or tokens and expands them, so it will work fine. This seems like it should work, but it doesn't. Aargh! After using TeX's <code>\tracingmacros</code> feature, which, compared to Lisp's <code>macroexpand</code>, sucks, it becomes clear that the <code>\fi</code> token that should end the conditional is sucked into <code>\@@cvdate</code> as part of the parameters. WTF? It turns out that when TeX expands a conditional it doesn't just replace it with the tokens in the appropriate branch and proceed; instead it skips the unsuccessful branch and starts expanding the taken branch, eventually noticing the ending <code>\else</code> or <code>\fi</code> token and discarding it. But it can't notice this token at the time it is gathering tokens for the argument of a macro, because it ignores the meaning of most tokens at that time... are we totally screwed?<br /><p>No, there's a TeX idiom for handling this situation, using the somewhat bizarre <code>\expandafter</code> primitive. <code>\expandafter</code> reads one token without expanding it, then reads the next and expands it (which could consume other tokens if it takes arguments), then puts the first token in front of that expansion and carries on. This sounds promising: we want TeX to find the end if the conditional without beginning the expansion of <code>\@@cvdate</code>. Here's the final version of <code>\@cvdate</code>:<br /> <code><br /> \def\@cvdate#1{\ifx#1\now\now\else\expandafter\@@cvdate\expandafter#1\fi}<br /> </code><br /> Note the double use of <code>\expandafter</code>. We have two tokens to save up and put after the end of the <code>\ifx</code>; you can chain together uses of <code>\expandafter</code> to save up multiple tokens like this.<br /><p>To Lisp hackers this is absolute brain damage. We could just gloat about the superiority of our macro system, but that would miss the point. The TeX macro language is designed to be usable by authors, not necessarily technical, with a syntax that seems "natural." That's why TeX supports complex parsing of arguments, as in our <code>\@@cvdate</code> macro. Since most of the world now uses LaTeX that has a much more regular macro syntax by convention this power is now wasted on end users, but at least the thought is there. Furthermore, there is a balance between writing simple substitution abbreviations and macros, which is easy in TeX, and more complicated packages that are likely to be written by motivated hackers that will invest the time to learn all the obscure incantations. And some novice macro writers will inevitably be sucked into the second category... Finally, it must be remembered that TeX was developed 25 years ago on machines that were incredibly puny by today's standards, so perhaps the strict separation between expansion and evaluation made sense. And, the objective is to put glyphs on paper, not write cool programs: yet another design constraint.<br /><p>I don't know if ultimately I saved any time with all this macrology, but I did learn something about TeX and macros in general. If you were going to write a Lispy typesetting program with an expansion mechanism that didn't suck, what would you do?Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com2tag:blogger.com,1999:blog-21382508.post-1140906564027471692006-02-25T13:08:00.000-08:002006-02-25T14:29:25.753-08:00Names Aren't ForeverDan Barlow <a href="http://ww.telent.net/diary/2006/2/#24.2759">refers</a> to an <a href="http://cybertiggyr.com/gene/80-closminded/">article</a> I wrote once on naming CLOS slot accessors, so I had better respond. Dan notes that choosing the name of a slot accessor or generic function is somewhat commiting because the name is used more globally than in other languages like e.g., Perl where a "slot name" is quite local. Xach <a href="http://xach.livejournal.com/71341.html">responds</a> that if you don't like a name, you can always change it later. I basically agree with this, but I want to point out that in large programs it may be difficult with text-based search-and-replace to make sure you're changing all -- and only -- what you need. Here our Java and Smalltalk friends are ahead with all the refactoring bells and whistles that come with <a href="www.eclipse.org">integrated environments</a> these days. <a href="http://common-lisp.net/project/slime/">Slime</a>, the preferred environment for Lisp programming in GNU Emacs, doesn't even have a notion of all the files in a system. It does do a pretty good job of tracking the home package of symbols in a Lisp source file, based on the <code>in-package</code> forms in the file. It would be a cool hack to write a <code>rename-symbol-in-system</code> elisp function that visits all the files in an <a href="http://www.cliki.net/asdf">ASDF</a> system, searches for a symbol and renames it if it's the symbol we care about i.e., in the right package. I call on my minions to make it so!Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com4tag:blogger.com,1999:blog-21382508.post-1138798608872825252006-02-01T03:08:00.000-08:002006-02-01T07:18:17.416-08:00Dark TeapotsOnce I had a Common Lisp / OpenGL interface working in <a href="http://common-lisp.net/project/glouton">glouton</a>, I of course wanted to render the classic computer graphics model, the <a href="http://www.sjbaker.org/teapot/">Utah Teapot</a>. Glut has a glutSolidTeapot function, so I slavishly translated the C code from <a href="http://www.mesa3d.org/">Mesa</a>, which defines a big array of Bezier surfaces and uses OpenGL evaluator functions to render them, into Lisp and let 'er rip.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.bricoworks.com/moore/dark.png"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 378px;" src="http://www.bricoworks.com/moore/dark.png" border="0" alt="" /></a>And got this interesting image. You can make out the murky outline of teapot if you squint hard. What's going wrong? With new teapot code, a new <a href="http://common-lisp.net/project/cffi">foreign function interface</a> for the OpenGL bindings, running in Lisp, rather a lot could be going wrong. And I spent an embarrassingly long time chasing down various blind alleys until I did what I should have done first: read the man page for <a href="http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/glutSolidTeapot.3.html">glutSolidTeapot</a> (in my defense, I was on vacation without a broad-band connection, so googling wasn't my first instinct). Hey look, there's a big "Bugs" section!.<br /><br />It turns out that we're looking at the inside surfaces of the teapot. Because their surface normals point away from the light source behind the viewer's head they are very dark. But those surfaces are supposed to be culled, and furthermore, the front surfaces of the teapot should not be! OpenGL can use the order of a polygon's vertices to decide if a polygon is facing away from the eye point and can be discarded. By default the order is assumed to be counterclockwise. If we turn off back-face culling we get a normal image, but that's not a happy state of affairs. In order to display the Glut teapot, according to the man page, you must change the vertex order to clockwise with <code>(glFrontFace GL_CW)</code> and then everything's happy. Fair enough, but why?<br /><br />There's an unfortunate interaction between the OpenGL functions for evaluating Bezier meshes and the "quad strip" primitives that they use to display the approximation of the mesh on the screen. The quad strip looks like this:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.bricoworks.com/moore/quad-strip.png"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 408px;" src="http://www.bricoworks.com/moore/quad-strip.png" border="0" alt="" /></a>That is, the vertices v0, v1, v2, v3, v4, ... are fed to OpenGL using glVertexf and the quads are drawn using the vertex order given by the arrow, which in this image is counterclockwise, making these quads front-facing. Now, the mesh produced by OpenGL for a Bezier surface might look like this (sorry for the lame diagram!):<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.bricoworks.com/moore/mesh.png"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 223px;" src="http://www.bricoworks.com/moore/mesh.png" border="0" alt="" /></a> u and v are the parameters of the mesh and increase in the directions of the arrows. The glEvalMesh2 function proceeds in the u direction of the mesh creating quad strips. That is, if we start at u = 0 v = 0 (do I need MathML here?), then the first few coordinates of the first quad strip will be (0,0), (0,dv),(du,0),(du, dv),(2du,0),(2du,dv),... and then another strip will be started at (0,dv). But look again at the diagram of quad strips above; this vertex order produces quads that are facing away from the viewer! We can't reorder the points and put the origin of the mesh parameters at another corner of the mesh because the same thing can happen. We could try to swap u and v, but that causes another, worse problem: we're relying on the evaluator to generate surface normals for us, and the normal direction is defined by the cross product of du and dv on the mesh. The right-hand rule rules here; if we swap u and v, the normals will point away from us, which is also not good. Are we doomed to live with this cheesy clockwise order?<br /><br />Well, no. Actually we don't have to evaluate the mesh from 0 to 1 in u in v: the glMap2f function takes arguments for the start and end values of u and v. If we evaluate v from 1 to 0: <br /><pre><br />(loop<br /> with u-order = (u-order patch)<br /> and v-order = (v-order patch)<br /> for patch-list in (patches patch)<br /> do (loop<br /> for patch in patch-list<br /> do (glMap2f GL_MAP2_VERTEX_3 0.0 1.0 3 u-order 1.0 0.0 12 v-order<br /> patch)<br /> (glEvalMesh2 type 0 grid 0 grid)))<br /></pre><br />then the vertices in the quad strips define quads with the proper orientation:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.bricoworks.com/moore/teapot.png"><img style="margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 350px;" src="http://www.bricoworks.com/moore/teapot.png" border="0" alt="" /></a><br /><br />Yay! We have our teapot.Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com1tag:blogger.com,1999:blog-21382508.post-1138016438825249002006-01-23T03:20:00.000-08:002006-01-23T05:07:31.283-08:00Munching on OpenGL in Common LispFirst post! I wonder if those are the most common words in the first post to a blog. I'm going to skip the introductions and instead describe something I'm working on. Well, more like "hacking on" in the best sense of those words.<br /><br />I've written a package called "glouton" (which means "glutton" in French; can't resist including "g" and "l" with a little cross-language action!) to do some OpenGL programming in Common Lisp. Now, if you believe what you read on comp.lang.lisp this is trivial, many bindings already exist, you can write your own bindings in an hour, etc. Whatever. I know about the <a href="http://www.ii.uib.no/%7Eknute/lisp/lisp.html">"nameless bindings"</a><br />of Knut Arild Erstad, but they don't have a free license. I looked at SDL, concluded (possibly in error) that the Lisp OpenGL bindings weren't very complete, and moved on. But for me, the deal breaker in existing OpenGL bindings is that they don't work with <a href="http://www.cliki.net/CLX">CLX </a>, the defacto X Windows library for Common Lisp. CLX doesn't use the C Xlib library at all; it opens a socket and makes protocol requests directly to the X server. OpenGL and GLX, on the other hand, use a combination of exchanges with the server via Xlib's connection and, in the sexiest implementations, direct manipulation of the graphics hardware, called "direct rendering." I'd really like to support direct rendering while still using CLX event handling and X requests if at all possible.<br /><br />It turns out that a hack is possible. The X server returns integer IDs for all interesting resources to its clients; these IDs are valid across different connections to the X server, or even in different clients. So we can create two connections to the server, one through CLX and one using Xlib, create the window using one or the other, create a GLX context and make in current in Xlib, then set the event mask in CLX and get events for it in the CLX-based event loop. Some synchronization is necessary between the two connections, particularly after a <span style="font-family:courier new;">:configure-notify</span> event; it seems like you need to call XPending() or something on the Xlib connection in order to notify the GLX client side of a reconfiguration of the frame buffer size. This was all more-or-less working on a Macintosh PowerBook, in my first try, currently <a href="http://common-lisp.net/project/glouton/">here.</a><br />And then I bought a laptop with an Nvidia graphics card, and things stopped working...<br /><br />I was creating the window in CLX, which was quite easy, but the results I was now seeing were very flakey: sometimes an image would appear, sometimes it wouldn't sometimes it appear only after a few mouse clicks or resizes. I googled around to no great effect, finally finding a clue that all GLX objects have to exist in the same address space. Well, that doesn't really apply here, but I changed things around to create the window in Xlib (more involved), then look up the window in CLX and set an event mask for it to get events. TaDa! There'll be some pretty pictures up here in a later post.<br /><br />At some point I'd like to dig into the sources for GLX and the drivers to see what's going on for real, but that's a project for another day. I hope this hack is portable and will continue to work!<br /><br />Incidently, I first saw the two connection trick in code Gilbert Baumann wrote to interface <a href="http://common-lisp.net/project/mcclim/">McCLIM</a> to <a href="http://cairographics.org/introduction">Cairo.</a> Mad props also to <a href="http://common-lisp.net/project/cffi/">cffi,</a> which is a really solid portable foreign function interface package.<br /><a href="http://common-lisp.net/project/glouton/"><span class="" style="display: block;" id="formatbar_CreateLink" title="Link" onmouseover="ButtonHoverOn(this);" onmouseout="ButtonHoverOff(this);" onmouseup="" onmousedown="CheckFormatting(event);FormatbarButton('richeditorframe', this, 8);ButtonMouseDown(this);"></span></a>Tim Moorehttp://www.blogger.com/profile/09629429704217731021noreply@blogger.com5