<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-783893035873019493</id><updated>2012-02-25T22:23:50.168+02:00</updated><category term='daz'/><category term='rendering'/><category term='elea'/><category term='texturewerke'/><category term='poser'/><category term='chibibel'/><category term='programming'/><category term='tutorial'/><category term='importing'/><category term='openGL'/><category term='illusion'/><category term='sara'/><category term='geometry'/><category term='sbz'/><category term='sehle'/><category term='texture'/><category term='animation'/><category term='gimp'/><category term='mathematics'/><category term='morphing'/><category term='code'/><category term='release'/><category term='model'/><category term='shinya'/><category term='pov-ray'/><category term='khayyam'/><title type='text'>Khayyam</title><subtitle type='html'>Experimentation with 3D posing and animation</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>38</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-2277010787162250286</id><published>2012-02-13T01:55:00.000+02:00</published><updated>2012-02-13T02:04:30.962+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='shinya'/><category scheme='http://www.blogger.com/atom/ns#' term='sehle'/><title type='text'>Shinya - the game</title><content type='html'>I have made too big progress with Shinya to not share it at last...&lt;br /&gt;&lt;br /&gt;It uses the same rendering engine as Khayyam (Sehle library). Most new (and unreleased) features in Khayyam are actually implemented so it can be used as scene builder for Shinya. Among other things there are now reflections, occlusion queries, volumetric lights and darkness, instanced vegetation, layered terrain and so on...&lt;br /&gt;No - I have not forgotten the new Khayyam release. The main problem holding me back is that there should be at least preliminary support for new materials for POVRay exporter. And things just progress too fast...&lt;br /&gt;&lt;br /&gt;Shinya playable tech-demo is nearly complete. The biggest roadblock at moment is fixing remaining shader bugs for ATI GPU-s (this is the unfortunate consequence of writing engine in OpenGL...)&lt;br /&gt;&lt;br /&gt;OK, I know - pics or it did not happen...&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-a3_DD1pxXo4/TzhHaW4NnCI/AAAAAAAAFXQ/wkT9_5OA8h8/s1600/shinya-ss-01.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://1.bp.blogspot.com/-a3_DD1pxXo4/TzhHaW4NnCI/AAAAAAAAFXQ/wkT9_5OA8h8/s400/shinya-ss-01.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara looking down to Shinya world&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Shinya will be adventure game, similar in style to Quest for Glory series. It takes place in (alternate and fictional) medieval times somewhere in central Europe.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-x64S6kNNgWw/TzhIq_6xugI/AAAAAAAAFXk/TXvoYAg1AgM/s1600/shinya-ss-02.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://3.bp.blogspot.com/-x64S6kNNgWw/TzhIq_6xugI/AAAAAAAAFXk/TXvoYAg1AgM/s400/shinya-ss-02.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;There is a bit more vegetation in some areas&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The main heroine is Sara - a bit complicated 12 years old girl, who is recently moved to the area with her aunt (she is orphan, as expected).&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-aPK7H1c3Zbs/TzhKKIQsMiI/AAAAAAAAFXw/oWd3nwS5G1w/s1600/shinya-ss-03.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://3.bp.blogspot.com/-aPK7H1c3Zbs/TzhKKIQsMiI/AAAAAAAAFXw/oWd3nwS5G1w/s400/shinya-ss-03.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Another human being!&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;She will involuntarily dragged into some intrigues, conflicts and mysteries that have lately developed in this otherwise tranquil place. Or maybe not so involuntarily - se is, after all, a bit complicated damsel...&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-ynC5oalYI3E/TzhLaTkDxXI/AAAAAAAAFYA/NA4mN7XtO88/s1600/shinya-ss-04.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://3.bp.blogspot.com/-ynC5oalYI3E/TzhLaTkDxXI/AAAAAAAAFYA/NA4mN7XtO88/s400/shinya-ss-04.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A mill at the main river &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Modeling in Blender has been the other big time-consumer other than programming. I am slowly starting to get a grasp of this program - not a pro yet, of course.&lt;br /&gt;The most complex model so far is cathedral - LOD 0 is about 190 000 triangles (LOD 1 about 2500). I develop Shinya mostly on laptop with NVidia GT540M GPU - and thus hope, that for the time it will be released it runs smoothy on most desktop computers at least.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-LG-gkpBo51s/TzhNdUNZ5-I/AAAAAAAAFYM/x2_w6Zkd1U4/s1600/shinya-ss-05.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://3.bp.blogspot.com/-LG-gkpBo51s/TzhNdUNZ5-I/AAAAAAAAFYM/x2_w6Zkd1U4/s400/shinya-ss-05.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Inside cathedral&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The world of Shinya is 4x4 km freely roamable terrain. It has continuous day/night cycles - but you cannot stay awake indefinitely, even by using stamina potions. Thus NPC-s will have certain time to arrange things without player peeking over their shoulders.&lt;br /&gt;&lt;br /&gt;Ah, almost forgot - Shinya it is written &lt;span style="font-size: large;"&gt;深夜&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-2277010787162250286?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/2277010787162250286/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2012/02/shinya-game.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2277010787162250286'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2277010787162250286'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2012/02/shinya-game.html' title='Shinya - the game'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-a3_DD1pxXo4/TzhHaW4NnCI/AAAAAAAAFXQ/wkT9_5OA8h8/s72-c/shinya-ss-01.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-5919954768439931691</id><published>2012-01-17T01:22:00.000+02:00</published><updated>2012-01-17T01:22:44.375+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='texture'/><category scheme='http://www.blogger.com/atom/ns#' term='shinya'/><category scheme='http://www.blogger.com/atom/ns#' term='model'/><category scheme='http://www.blogger.com/atom/ns#' term='sara'/><title type='text'>Models and textures</title><content type='html'>I have been busy with modeling and texturing for Shinya - and rewriting parts of Khayyam/Sehle to support certain must-be features like vegetation instancing and geometry LODs. Some (semi)finished things are available for download - more will come. Although certain parts will probably remain secret fro now...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Models&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-F6BoRQJTYoc/TUKsUOPWjRI/AAAAAAAAE4o/5NzgHZoXHrg/s1600/preview.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="http://1.bp.blogspot.com/-F6BoRQJTYoc/TUKsUOPWjRI/AAAAAAAAE4o/5NzgHZoXHrg/s400/preview.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara the tween girl (Blender render)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Textured and rigged, about 6000 polygons. Blender source file and some exported formats can be downloaded from &lt;a href="http://www.turbosquid.com/3d-models/tween-girl-blend-free/583223"&gt;TurboSquid&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-Gh4vH1TEiGc/TxSpm8MZNPI/AAAAAAAAFWY/yc8p6ZHnISs/s1600/QuercusA.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/-Gh4vH1TEiGc/TxSpm8MZNPI/AAAAAAAAFWY/yc8p6ZHnISs/s400/QuercusA.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Oak tree (Blender render)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Oak tree with 3 LOD levels.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;LOD0: 4407 vertices, 2791 faces&lt;/li&gt;&lt;li&gt;LOD1: 645 vertices, 442 faces&lt;/li&gt;&lt;li&gt;LOD2: 21 vertices, 7 faces&lt;/li&gt;&lt;li&gt;Collision (only trunk): 24 vertices, 16 faces&lt;/li&gt;&lt;/ul&gt;Blender source file can be downloaded from &lt;a href="http://opengameart.org/content/oak-tree"&gt;OpenGameArt&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;object width="320" height="266" class="BLOG_video_class" id="BLOG_video-a0441b96e7d2c08" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;&lt;param name="movie" value="http://www.youtube.com/get_player"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="flashvars" value="flvurl=http://v18.nonxt7.googlevideo.com/videoplayback?id%3D0a0441b96e7d2c08%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1332715306%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D59EE107ED649A86434152668C3F40FA92D300994.23CB5D5F17E889981305D6DFA5CCF283C0608D31%26key%3Dck1&amp;amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Da0441b96e7d2c08%26offsetms%3D5000%26itag%3Dw160%26sigh%3DNze-ydMR6_yerDuDBZPwIAM41fc&amp;amp;autoplay=0&amp;amp;ps=blogger"&gt;&lt;embed src="http://www.youtube.com/get_player" type="application/x-shockwave-flash"width="320" height="266" bgcolor="#FFFFFF"flashvars="flvurl=http://v18.nonxt7.googlevideo.com/videoplayback?id%3D0a0441b96e7d2c08%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1332715306%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D59EE107ED649A86434152668C3F40FA92D300994.23CB5D5F17E889981305D6DFA5CCF283C0608D31%26key%3Dck1&amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Da0441b96e7d2c08%26offsetms%3D5000%26itag%3Dw160%26sigh%3DNze-ydMR6_yerDuDBZPwIAM41fc&amp;autoplay=0&amp;ps=blogger"allowFullScreen="true" /&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;Unfinished medieval house. About 10000 polygons, needs some agressive LODing.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-ffGF5uNXw2s/TxSuRTnL48I/AAAAAAAAFWk/7VIwlteR32I/s1600/Cathedral.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/-ffGF5uNXw2s/TxSuRTnL48I/AAAAAAAAFWk/7VIwlteR32I/s400/Cathedral.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Cathedral interior (Blender render)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Unfinished cathedral. About 55000 polygons - half of those are window frames. Needs also some agressive LODing to be usable in game.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Textures&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-8xkOfDwD9pg/TxSvwCT-MsI/AAAAAAAAFWw/IjhFegQkdNc/s1600/Ground_6693_1024.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="http://1.bp.blogspot.com/-8xkOfDwD9pg/TxSvwCT-MsI/AAAAAAAAFWw/IjhFegQkdNc/s400/Ground_6693_1024.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Seamless ground texture - dead leaves&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Small collection of plant and ground textures is available from my &lt;a href="http://lauris71.deviantart.com/gallery/"&gt;DeviantArt&lt;/a&gt; page.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://opengameart.org/sites/default/files/Cutout-plants-1.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="http://opengameart.org/sites/default/files/Cutout-plants-1.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Cut-out plants&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;A collection of cut-out plants at &lt;a href="http://opengameart.org/content/cutout-plant-textures-1"&gt;OpenGameArt&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-5919954768439931691?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/5919954768439931691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2012/01/models-and-textures.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5919954768439931691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5919954768439931691'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2012/01/models-and-textures.html' title='Models and textures'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-F6BoRQJTYoc/TUKsUOPWjRI/AAAAAAAAE4o/5NzgHZoXHrg/s72-c/preview.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-8529503048547143348</id><published>2011-12-17T19:42:00.000+02:00</published><updated>2011-12-29T23:13:08.830+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gimp'/><category scheme='http://www.blogger.com/atom/ns#' term='texture'/><category scheme='http://www.blogger.com/atom/ns#' term='texturewerke'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><title type='text'>Matching image colors with Texturewerke</title><content type='html'>In the &lt;a href="http://khayyam.kaplinski.com/2011/12/texturewerke-01.html"&gt;last post&lt;/a&gt; I demonstrated how to use TextureWerke for masked polynome high-pass filtering. In addition to that, TextureWerke 0.1 has another feature - matching colors between two (masked) images.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Workflow&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's start with two images of stone wall we want to merge into one texture:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-NBSMupp-lKg/TuzOtgqIOjI/AAAAAAAAFVk/efuuSVs0Qus/s1600/texturewerke-6.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="343" src="http://3.bp.blogspot.com/-NBSMupp-lKg/TuzOtgqIOjI/AAAAAAAAFVk/efuuSVs0Qus/s400/texturewerke-6.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;First image - a plain wall&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-mW8EhkhC3OE/TuzOupPlRqI/AAAAAAAAFVs/N3jvTYtVb9w/s1600/texturewerke-7.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="343" src="http://1.bp.blogspot.com/-mW8EhkhC3OE/TuzOupPlRqI/AAAAAAAAFVs/N3jvTYtVb9w/s400/texturewerke-7.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Second image - a corner made of bricks&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;As you can see (among other things) the images have a bit different colors - the first one has more contrast and red. The bright background of the second picture influenced camera color adjustment logic so that the wall is more gray and has less contrast.&lt;br /&gt;We will correct this automatically. First we move both photos to the same image (not necessary, but easier to compare) and add masks. We mask out all details that we do not want to influence final color - i.e. roof, shaded areas, brick structures, grass and background.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-D_Q3OTooPKA/TuzOwrw1h-I/AAAAAAAAFV8/_3U0NCN03fA/s1600/texturewerke-9.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="265" src="http://2.bp.blogspot.com/-D_Q3OTooPKA/TuzOwrw1h-I/AAAAAAAAFV8/_3U0NCN03fA/s400/texturewerke-9.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Masked images on top to each other.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Next we select the masked layer whose colors we want to change (Layer 2), invoke TextureWerke and choose color matching mode.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-UpGvfvp1mhg/TuzOyFCpwmI/AAAAAAAAFWI/aSXPF7DowvY/s1600/texturewerke-color.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-UpGvfvp1mhg/TuzOyFCpwmI/AAAAAAAAFWI/aSXPF7DowvY/s320/texturewerke-color.jpg" width="216" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;TextureWerke dialog window in color mode&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Template is the (masked) layer whose color profile we want to emulate in selected layer.&lt;br /&gt;Blurring reduces the noisiness of distribution curves and thus helps to overcome artefacts resulting from different sharpness of images. On the other hand it may mask out certain differences in color distribution and thus reduce the matching quality. You can always try and use what gives the best results.&lt;br /&gt;And at last we apply the filter.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-bbXp54cDeJQ/TuzOxokgXnI/AAAAAAAAFWE/_B66U-pxxWE/s1600/texturewerke-10.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="343" src="http://3.bp.blogspot.com/-bbXp54cDeJQ/TuzOxokgXnI/AAAAAAAAFWE/_B66U-pxxWE/s400/texturewerke-10.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Image with adjusted colors&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;As you can see, the overall color and contrast of the (stone part of) wall matches with template image. On the other hand the areas that were masked out (bricks, backround...) are now completely off. But probably we did not want to use these anyways - except bricks maybe - but these can easily be copied and pasted from the original, and adjusted separately.&lt;br /&gt;&lt;br /&gt;FYI: Internally the algorithm works by comparing pairwise the cumulative distributions of each image channel (R,G,B,A) and building a transfer function that translates the values with one distribution to the values with another (template) distribution.&lt;br /&gt;&lt;br /&gt;And that's all. Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-8529503048547143348?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/8529503048547143348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/12/matching-image-colors-with-texturewerke.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8529503048547143348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8529503048547143348'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/12/matching-image-colors-with-texturewerke.html' title='Matching image colors with Texturewerke'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-NBSMupp-lKg/TuzOtgqIOjI/AAAAAAAAFVk/efuuSVs0Qus/s72-c/texturewerke-6.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-1597318293120422482</id><published>2011-12-08T01:07:00.001+02:00</published><updated>2011-12-18T12:05:33.626+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gimp'/><category scheme='http://www.blogger.com/atom/ns#' term='texture'/><category scheme='http://www.blogger.com/atom/ns#' term='texturewerke'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><title type='text'>Texturewerke 0.1</title><content type='html'>While doing textures for Shinya (the game with Khayyam/Sehle engine) I got frustrated with GIMP built-in tools and plug-ins available for texturing and decided to write my own tool Texturewerke. The initial version is now available for download from SourceForge:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://sourceforge.net/projects/floyd/files/texturewerke/"&gt;Tarball and win32 precompiled binary&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;At moment it can do two things:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Adjust the colors of one (masked) image so it matches as well as possible with another. Thus you do not have to manually adjust color curves to merge several photographs into the same texture.&lt;/li&gt;&lt;li&gt;Filter out low frequencies from image (while keeping the average intact). This can be used both for the creation of seamless textures and for removing shade gradient from uniform surfaces (like walls, doors, whiteboards...) Highpass filter supports masking (in polynome mode), so you can mask out those details whose contribution you want to ignore (like pictures on wall).&lt;/li&gt;&lt;/ol&gt;Below is one possible usage scenario of Texturewerke highpass filter.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Polynomic highpass filter with masking&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We want to use the following image as a texture. Unfortunately the background color is non-uniform and adjusting it by hand is boring work &lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-WN1x_IWxrWE/Tt_1NK8krJI/AAAAAAAAFUk/-kIlZja73ms/s1600/IMGP0883.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-WN1x_IWxrWE/Tt_1NK8krJI/AAAAAAAAFUk/-kIlZja73ms/s320/IMGP0883.JPG" width="240" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The original image - notice the non-uniform shading of the wall&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;We select the are that we want to have uniform color (the wall). It does not have to be precise - the filter samples few thousand points from target area and thus small regions of wrong color do not disturb the result much.&lt;br /&gt;In given case I used magic want to select white and then added and subtracted few rectangles.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-b9h-1G8ZfRs/Tt_3oe4d6cI/AAAAAAAAFU4/BAWQeuL7dQc/s1600/texturewerke-2.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://2.bp.blogspot.com/-b9h-1G8ZfRs/Tt_3oe4d6cI/AAAAAAAAFU4/BAWQeuL7dQc/s320/texturewerke-2.jpg" width="220" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Target region selected&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Now we turn target region into mask, bu choosing "&lt;b&gt;Add Layer Mask&lt;/b&gt;" from &lt;b&gt;Layer &lt;/b&gt;menu.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-CXU2FWG8Lrk/Tt_4BgYLeeI/AAAAAAAAFVA/nnLv5b99hDs/s1600/texturewerke-3.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="311" src="http://4.bp.blogspot.com/-CXU2FWG8Lrk/Tt_4BgYLeeI/AAAAAAAAFVA/nnLv5b99hDs/s320/texturewerke-3.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Target region turned into mask&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Next we invoke Texturewerke plugin and select "&lt;b&gt;Lowpass&lt;/b&gt;" filter and "&lt;b&gt;Polynome&lt;/b&gt;" mode. &lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-tv9OOTDy3vs/Tt_4SGmOVrI/AAAAAAAAFVI/QIDGZvqjkKU/s1600/texturewerke-polynome.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-tv9OOTDy3vs/Tt_4SGmOVrI/AAAAAAAAFVI/QIDGZvqjkKU/s320/texturewerke-polynome.jpg" width="217" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The Texturewerke dialog window&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;It blurs image (internally) before calculating polynome approximation. The optimal size of kernel depends on image and the number of samples used. As general rule - the more samples it uses, the smaller can be the kernel.&lt;br /&gt;Next we apply filter.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-JVcTtpmrh0I/Tt_5Fm4HXLI/AAAAAAAAFVU/_90Ang1Cdes/s1600/texturewerke-4.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-JVcTtpmrh0I/Tt_5Fm4HXLI/AAAAAAAAFVU/_90Ang1Cdes/s320/texturewerke-4.jpg" width="220" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Masked image after applying polynome filter&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;As you can see, the unmasked area has almost uniform color/bightness now.&lt;br /&gt;As the last thing, we delete (or turn off layer mask).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-HdNs-PBuV7g/Tt_1q2eDykI/AAAAAAAAFUs/DzAVnOhrKEo/s1600/IMGP0883-adjusted.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-HdNs-PBuV7g/Tt_1q2eDykI/AAAAAAAAFUs/DzAVnOhrKEo/s320/IMGP0883-adjusted.jpg" width="240" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The final image - wall color is now uniform&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&amp;nbsp;And the final image has now nice uniform wall color.&lt;br /&gt;&lt;br /&gt;There are few things to notice:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Polynome mode tries to approximate the average color of the image by two-dimensional polynome (up to 4th degree). The approximation process guarantees the correct behavior of the polynome only inside target region - the higher is its degree the faster it goes "wild" outside. Thus if you mask out some edges of the image the results probably will not look nice for anything above quadratic (2th order).&lt;/li&gt;&lt;li&gt;The unmasked area has to be at least 10% of original image&lt;/li&gt;&lt;li&gt; Samples are drawn randomly from unmasked area.&lt;/li&gt;&lt;li&gt;There is another highpass filter mode "blur", that subtracts blurred (lowpass) version of the same image. It does not support masking - but it may be more useful for the generation of highly uniform images (like grass and sand textures).&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Have fun!&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-1597318293120422482?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/1597318293120422482/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/12/texturewerke-01.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/1597318293120422482'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/1597318293120422482'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/12/texturewerke-01.html' title='Texturewerke 0.1'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-WN1x_IWxrWE/Tt_1NK8krJI/AAAAAAAAFUk/-kIlZja73ms/s72-c/IMGP0883.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-8057746658547120995</id><published>2011-10-04T21:42:00.000+03:00</published><updated>2011-10-04T21:42:53.909+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='mathematics'/><category scheme='http://www.blogger.com/atom/ns#' term='openGL'/><category scheme='http://www.blogger.com/atom/ns#' term='sehle'/><title type='text'>Reflective water with GLSL, Part II</title><content type='html'>In the&amp;nbsp;&lt;a href="http://khayyam.kaplinski.com/2011/09/reflective-water-with-glsl-part-i.html"&gt;first part&lt;/a&gt;&amp;nbsp;I explained how to implement a basic reflective surface. But as we could see, it was very poor approximation of true water, looking more like mirror lying on the ground. &amp;nbsp;First because real water is almost never completely still. But it is also not perfect mirror - depending on viewing angle we can see more or less into the water.&lt;br /&gt;Before going to implementing ripples I'll show how to adjust water shaders so the angle-dependency of reflection strength is taken into account. And being already there, how to fake the light attenuation/scattering in water so it is not crystal-clear but has more realistic color.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;1. The relation of reflectivity and viewing angle&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If you imagine looking at water body from different angles it should be obvious, that the lower is viewing angle the more light it reflects. Sea may look almost like perfect mirror during tranquil sunset - but if you are looking daytime from the top of a cliff, you can see the blueish-greenish color of water and only a little reflection.&lt;br /&gt;&lt;br /&gt;The reflectivity of water comes from the difference in refractive indexes of air and water. As the speed of light is different in these mediums, some light is reflected and the light entering water changes slightly its direction - the latter is called refraction. Refraction is another phenomenon that can add realism, but we will not try to simulate it here.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Q1rQHsPmJk4/TosORNTuTqI/AAAAAAAAFR0/sO4VP3EuV_s/s1600/reflections+4.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="308" src="http://4.bp.blogspot.com/-Q1rQHsPmJk4/TosORNTuTqI/AAAAAAAAFR0/sO4VP3EuV_s/s320/reflections+4.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A schematic diagram of reflection and refraction&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Mathematically the amount of light reflected from the surface of water is described by Fresnel equations:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-GrqeLRgxMVo/TosPl08mUfI/AAAAAAAAFR8/15mDPPBeM8I/s1600/fresnel.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="160" src="http://1.bp.blogspot.com/-GrqeLRgxMVo/TosPl08mUfI/AAAAAAAAFR8/15mDPPBeM8I/s400/fresnel.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Fresnel reflection equations (source Wikipedia)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;R&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;s&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; and &lt;i&gt;&lt;b&gt;R&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;t&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; are the reflectance values of vertically and horizontally polarized light.&lt;br /&gt;&lt;i&gt;&lt;b&gt;θ&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;i&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; and &lt;i&gt;&lt;b&gt;θ&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;t&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; are the angles between the surface normal and incident and refracted rays.&lt;br /&gt;&lt;div&gt;&lt;b&gt;&lt;i&gt;n&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;1&lt;/span&gt;&lt;/i&gt;&lt;/b&gt; and &lt;b&gt;&lt;i&gt;n&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;2&lt;/span&gt;&lt;/i&gt;&lt;/b&gt; are the refractive indices of two media - in our case air and water. The relevant values are:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;i&gt;n&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;1&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;i&gt;&amp;nbsp;= 1.000277 ≈ 1&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;i&gt;n&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;2&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;i&gt;&amp;nbsp;= 1.3330&lt;/i&gt;&lt;br /&gt;&lt;div&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="font-style: normal;"&gt;We do not need&amp;nbsp;&lt;i&gt;&lt;b&gt;θ&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;t&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&amp;nbsp;because it can be derived from&amp;nbsp;&lt;i&gt;&lt;b&gt;θ&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;i&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&amp;nbsp;and refractive indices using Snell's law - look at the rightmost part of equations.&lt;/div&gt;&lt;div style="font-style: normal;"&gt;The reflectance values are different for differently polarized light. This explains the magic behind anti-glare sunglasses and optical filters - they cut off vertically polarized light, that is much more strongly reflected.&lt;/div&gt;&lt;div style="font-style: normal;"&gt;It is also interesting to know that skylight is in fact somewhat polarized. But for our simulation we ignore this and treat all light as the uniform mix of both polarizations. In that case, the total reflectance can be simply calculated as:&lt;/div&gt;&lt;div style="font-style: normal;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;b&gt;R&lt;/b&gt; = (&lt;b&gt;R&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;s&lt;/span&gt;&lt;/b&gt;&amp;nbsp;+&amp;nbsp;&lt;b&gt;R&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;t&lt;/span&gt;&lt;/b&gt;) / 2&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;/i&gt;&lt;/div&gt;&lt;div style="font-style: normal;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="font-style: normal;"&gt;The full Fresnel equation above is a bit too complex for our shaders. It is physically correct - but our goal is natural or good-looking scene and we are ready to sacrify a little here ad there to save shader instructions for other things.&lt;/div&gt;&lt;div style="font-style: normal;"&gt;There is quite simple approximation available. Take a look at the following graph:&lt;/div&gt;&lt;div style="font-style: normal;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="font-style: normal; margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-py1tIOc2gSs/Toi7pK3gShI/AAAAAAAAFRk/lczs7UdGmCs/s1600/fresnel-graph.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="271" src="http://4.bp.blogspot.com/-py1tIOc2gSs/Toi7pK3gShI/AAAAAAAAFRk/lczs7UdGmCs/s400/fresnel-graph.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Fresnel reflectance values Rs, Rp and R (blue, red, yellow) and our approximation (green)&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div style="font-style: normal;"&gt;The green line represents function:&lt;/div&gt;&lt;div style="font-style: normal;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;i&gt;&lt;b&gt;R&lt;/b&gt; = &lt;b&gt;Rmin&lt;/b&gt; + (1 - &lt;b&gt;Rmin&lt;/b&gt;) * (1- cos &lt;b&gt;θ&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;i&lt;/span&gt;&lt;/b&gt;)&lt;sup&gt;5&lt;/sup&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;That can be used as good approximation.&lt;br /&gt;&lt;b&gt;R&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;min&lt;/span&gt;&lt;/b&gt; is 0.02 for real water, but you may want to increase it to something like 0.1 or even more, unless you have very good HDR implementation. The problem is that real sky is &lt;b&gt;really &lt;/b&gt;bright - if you are using dimmed down version of sky, its reflection is not visible at all from high angles.&lt;br /&gt;&lt;br /&gt;That's it. Now we have reflectance value calculated - but we cannot yet update our shaders. Unlike in our previous lesson, where the reflection was all that had to be rendered, we now have to render the water itself too - unless the reflectance is exactly 1.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;2. Rendering water&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Water is normally dense medium and strongly scatters light. The actual formula is quite complex and depends on the amount of different chemical compounds (such as oxygen, humic acids) and various inorganic (such as clay) and organic (like plankton) particles in water. But our goal is not to simulate procedurally the color and turbidity of water, but instead find a meaningful set of descriptive parameters, that will give us good enough approximation.&lt;br /&gt;&lt;br /&gt;Scattering changes the intensity of light (ray) in two ways:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Out-scattering - if ray of light goes through medium, some fraction of light is scattered away from direct path and thus the final amount of light is lower.&lt;/li&gt;&lt;li&gt;In-scattering - as light is scattered to all directions, some light from rays originally going other directions is scattered to the direction of the ray of interest.&lt;/li&gt;&lt;/ul&gt;Because of out-scattering the bottom of deep water body is dark. Because of in-scattering objects inside water seem "tinted" with water color.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-iFsx_EztZII/TotOpC7TTXI/AAAAAAAAFSM/ly3ecLeTaGY/s1600/reflections+6.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="257" src="http://3.bp.blogspot.com/-iFsx_EztZII/TotOpC7TTXI/AAAAAAAAFSM/ly3ecLeTaGY/s400/reflections+6.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Scattering in water. A - in-scattering. B - out-scattering.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;In addition to scattering there is one more phenomenon causing less light to come from water - it is internal reflection. As light hits the bottom of water body or objects inside it and goes upwards from there, some of it is mirrored back into water from the air-water phase surface.&lt;br /&gt;We ignore this at moment and will use shortcut - instead of adding together the light coming from inside water and light from reflection, we draw the latter as semitransparent surface using reflectance as alpha value. Thus the higher is reflectance, the lower is the effect of light from inside water - which is generally correct, because the internal and external reflectance values are correlated.&lt;br /&gt;&lt;br /&gt;How good our water formula can be depends on whether we can read the color and depth values of the bottom of water body or underwater objects in shader or not. Here I present a simple approach, that does not use them, but instead adds the color of water to the reflection image.&lt;br /&gt;&lt;br /&gt;We ignore the exact scattering equations, that involve multiple integrals and instead combine the "tint" of water with the color of reflection (that is semitransparent now) and then render them as semitransparent surface over the already rendered bottom of water. For this I use a very simple formula:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;C&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;tint&lt;/span&gt;&lt;/b&gt; = &lt;b&gt;C&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;water&lt;/span&gt;&lt;/b&gt; * (&lt;b&gt;O&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;min&lt;/span&gt;&lt;/b&gt; + (1 - &lt;b&gt;O&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;min&lt;/span&gt;&lt;/b&gt;) * sqrt (min (&lt;b&gt;thickness&lt;/b&gt; / &lt;b&gt;D&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;opaque&lt;/span&gt;&lt;/b&gt;, 1)))&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;C&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;tint&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&amp;nbsp;is the color that water adds to objects (premultiplied)&lt;br /&gt;&lt;i&gt;&lt;b&gt;C&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;water&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&amp;nbsp;is the color of opaque water column&lt;br /&gt;&lt;i&gt;&lt;b&gt;O&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;min&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&amp;nbsp;is the minimal opacity of water. It should be 0 for realistic simulation, but in reality values 0.1-0.2 give overall better effect.&lt;br /&gt;&lt;i&gt;&lt;b&gt;D&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;opaque&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&amp;nbsp;is the depth of water column, that becomes fully opaque. The reasonable value is 2 m for freshwater bodies - the smaller the better, as it helps to hide the lack of refraction.&lt;br /&gt;&lt;i&gt;&lt;b&gt;thickness&lt;/b&gt;&lt;/i&gt;&amp;nbsp;is the thickness of water in given direction until bottom or some underwater object is hit.&lt;br /&gt;&lt;br /&gt;Calculating thickness is tricky. The technically correct way would be to trace ray in refracted direction until bottom (line &lt;b&gt;AC&lt;/b&gt; in following figure) - but we cannot afford to do that.&lt;br /&gt;If you can use depth buffer, you can ignore refraction and calculate the distance the original ray would cover underwater (line &lt;b&gt;AD&lt;/b&gt;). This overestimates the thickness, but as the effect only becomes noticeable at low viewing angle, where reflection dominates, it should look quite good.&lt;br /&gt;Here I will use even simpler approximation. Just find the depth of water under given point of surface (point &lt;b&gt;B&lt;/b&gt; on following figure), and pretend that water has uniform depth (line &lt;b&gt;AB&lt;sub&gt;1&lt;/sub&gt;&lt;/b&gt;). It underestimates depth at the slopes directed away from viewer and overestimates at slopes directed to viewer, but if the bottom of water is reasonably smooth it is not too bad.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-XuVylF1qgTU/TosTCW9Zc9I/AAAAAAAAFSE/umwXwz2M4qY/s1600/reflections+5.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="257" src="http://1.bp.blogspot.com/-XuVylF1qgTU/TosTCW9Zc9I/AAAAAAAAFSE/umwXwz2M4qY/s400/reflections+5.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A diagram of different possible methods for water thickness calculation&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;How to get the actual water depth under given point?&lt;br /&gt;Recall the previous tutorial. We set the Z coordinate of vertex to 0 (i.e. flatten our object), but kept the full original vertex coordinate in &lt;b&gt;interpolatedVertexDepth&lt;/b&gt;.&lt;br /&gt;Thus if the object being rendered as water is actually the bottom of water body, it will render as flat surface, but we have access to the original Z coordinate of it. In other words - the water depth.&lt;br /&gt;Another approach would be to encode water depth into another vertex attribute. It has some good points - like no need to separate the bottom of water body from other terrain and the possibility to hand-code depth.&lt;br /&gt;&lt;br /&gt;Once we have calculated water thickness with whatever method applicable, we treat the tint color as another semitransparent layer, lying directly beneath the reflection layer. The final color and alpha values can be calculated by standard alpha blending formula:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: Times, 'Times New Roman', serif;"&gt;&lt;i&gt;C = A&lt;sub&gt;reflection&lt;/sub&gt; * C&lt;sub&gt;reflection&lt;/sub&gt; + (1 - A&lt;sub&gt;reflection&lt;/sub&gt;) * C&lt;sub&gt;water&lt;/sub&gt;&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: Times, 'Times New Roman', serif;"&gt;&lt;i&gt;A = A&lt;sub&gt;reflection&lt;/sub&gt; + (1 - A&lt;sub&gt;reflection&lt;/sub&gt;) * A&lt;sub&gt;water&lt;/sub&gt;&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Where C and A are color and alpha values.&lt;br /&gt;If the resulting alpha is below 1, bottom or underwater objects are partially visible.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;3. Shaders&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There is no need to change vertex shader.&lt;br /&gt;&lt;br /&gt;Fragment shader:&lt;br /&gt;&lt;span class="Apple-style-span" style="background-color: white; color: #666666; font-family: 'Trebuchet MS', Trebuchet, Verdana, sans-serif; font-size: 13px; line-height: 18px;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;uniform mat4 o2v_projection_reflection;&lt;br /&gt;uniform sampler2D reflection_sampler;&lt;br /&gt;uniform vec3 eye_object;&lt;br /&gt;uniform float min_opacity, opaque_depth, opaque_color;&lt;br /&gt;&lt;br /&gt;varying vec3 interpolatedVertexObject;&lt;br /&gt;&lt;br /&gt;void main()&lt;br /&gt;{&lt;br /&gt;    // Vertex on water surface&lt;br /&gt;    vec3 surfaceVertex = vec3(interpolatedVertexObject.xy, 0.0);&lt;br /&gt;    // Reflection angle&lt;br /&gt;    vec3 vertex2Eye = normalize (eye_object - surfaceVertex );&lt;br /&gt;    float cosT1 = vertex2Eye.z;&lt;br /&gt;    // Reflectance&lt;br /&gt;    float c = 1.0 - cosT1;&lt;br /&gt;    float R = min_reflectivity + (1.0 - min_reflectivity) * c * c * c * c * c;&lt;br /&gt;&lt;br /&gt;    // Water density&lt;br /&gt;    float depth = -interpolatedVertexObject.z;&lt;br /&gt;    float thickness = depth / max (cosT1, 0.01);&lt;br /&gt;    float dWater = min_opacity + (1.0 - min_opacity) * sqrt (min (thickness / opaque_depth, 1.0));&lt;br /&gt;    // Premultiply&lt;br /&gt;    vec3 waterColor = opaque_color * dWater;&lt;br /&gt;&lt;br /&gt;    vec4 vClipReflection = o2v_projection_reflection * vec4(interpolatedVertexObject , 1.0);&lt;br /&gt;    vec2 vDeviceReflection = vClipReflection.st / vClipReflection.q;&lt;br /&gt;    vec2 vTextureReflection = vec2(0.5, 0.5) + 0.5 * vDeviceReflection;&lt;br /&gt;&lt;br /&gt;    vec4 reflectionTextureColor = texture2D (reflection_sampler, vTextureReflection);&lt;br /&gt;    // Framebuffer reflection can have alpha &amp;gt; 1&lt;br /&gt;    reflectionTextureColor.a = 1.0;&lt;br /&gt;&lt;br /&gt;    // Combine colors&lt;br /&gt;    vec3 color = (1.0 - R) * waterColor + R * reflectionTextureColor.rgb;&lt;br /&gt;    float alpha = R + (1.0 - R) * dWater;&lt;br /&gt;&lt;br /&gt;    gl_FragColor = vec4(color, alpha);}&lt;/pre&gt;&lt;br /&gt;We have added another uniform (in addition to water color and opacity ones) - &lt;b&gt;eye_object&lt;/b&gt;, that is simply camera position relative to water object local coordinate system.&lt;br /&gt;&lt;br /&gt;And real-time image from Shinya:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-2C2vluMOdI4/Tojq29YSr8I/AAAAAAAAFRs/YLeZaKmM1PE/s1600/shinya-water-2.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="295" src="http://1.bp.blogspot.com/-2C2vluMOdI4/Tojq29YSr8I/AAAAAAAAFRs/YLeZaKmM1PE/s400/shinya-water-2.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Simple scene from Shinya with partial reflection and water opacity&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Now it is a bit better than last time - but still artificial and lifeless. Next time I show, how to make it live - i.e. add waves or ripples.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://khayyam.kaplinski.com/2011/09/reflective-water-with-glsl-part-i.html"&gt;Part 1 - reflective surface&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Have fun!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-8057746658547120995?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/8057746658547120995/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/10/reflective-water-with-glsl-part-ii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8057746658547120995'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8057746658547120995'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/10/reflective-water-with-glsl-part-ii.html' title='Reflective water with GLSL, Part II'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-Q1rQHsPmJk4/TosORNTuTqI/AAAAAAAAFR0/sO4VP3EuV_s/s72-c/reflections+4.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-68991470304490751</id><published>2011-09-16T22:43:00.000+03:00</published><updated>2011-10-03T02:10:38.072+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='mathematics'/><category scheme='http://www.blogger.com/atom/ns#' term='openGL'/><category scheme='http://www.blogger.com/atom/ns#' term='sehle'/><title type='text'>Reflective water with GLSL, Part I</title><content type='html'>Being it for physical accuracy or setting mood in game, water and reflections are something that can add lot to your rendering engine. While true reflections can only be done with ray-tracing, one can achieve surprisingly nice approximations by using quite simple scene setup and some GPU programming.&lt;br /&gt;&lt;br /&gt;Good water simulation should have at least the following features:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;True reflection (with correct parallax)&lt;/li&gt;&lt;li&gt; Clipping of underwater objects on reflection image&lt;/li&gt;&lt;li&gt;View angle dependent transparency/reflectivity of water&lt;/li&gt;&lt;li&gt;Ripples and/or waves&lt;/li&gt;&lt;li&gt;Water scattering (i.e. water becoming gradually opaque as depth increases)&lt;/li&gt;&lt;/ul&gt;&amp;nbsp;Some more things, that can make things nicer but are not as visible, are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Refraction&lt;/li&gt;&lt;li&gt;Caustics - i.e. light spots at the bottom of shallow water&lt;/li&gt;&lt;li&gt;Reflected light - i.e. light spots reflected to objects near water&lt;/li&gt;&lt;/ul&gt;At moment I have only implemented features from the first list into Khayyam/Sehle/Shinya code. You can look at &lt;a href="http://khayyam.kaplinski.com/2011/08/glsl-water-reflections.html"&gt;my previous post&lt;/a&gt; for some in-engine images.&lt;br /&gt;Here I will describe the mathematics behind the scenes and give step-by-step guide to writing your own water system/object/rendering pass.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;1.Rendering reflection texture&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Water without reflection looks totally uninteresting - just like any other semitransparent surface. Thus we start from implementing reflection and later go on to other effects.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1.1. Parallax &lt;/b&gt;&lt;br /&gt;Even if you have until now managed to render you scene in single pass, from this point on you need at least two passes (actually at least &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;N+1&lt;/span&gt;&lt;/b&gt;, where &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;N&lt;/span&gt;&lt;/b&gt; is the number of visible reflective surfaces).&lt;br /&gt;&lt;br /&gt;The reason is, that unfortunately we cannot recycle our main scene image for reflections. First because it could make view frustum insanely large (for example - if viewing the water surface from high angle we see only ground and water in our main view, but mostly sky in reflection). And second because of parallax. The reflection is unfortunately not the perfect copy of reflected scene, but copy of the view of the same scene from different viewpoint. The following image illustrates this.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-3_UlH2s2KbI/TnN7idEjIJI/AAAAAAAAFQ4/hL2r8zgKC2w/s1600/reflections.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="http://2.bp.blogspot.com/-3_UlH2s2KbI/TnN7idEjIJI/AAAAAAAAFQ4/hL2r8zgKC2w/s400/reflections.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A diagram explaining the parallax effect on reflected image&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;It means that you need to have rendering to texture set up and working. We will render reflection to texture and later use this texture while rendering the water surface in main scene.&lt;br /&gt;&lt;br /&gt;Thus, to get reflection texture we first have to render our scene from the reflected camera viewpoint&amp;nbsp; &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;P'&lt;/span&gt;&lt;/b&gt; to texture. First we have to find the reflected camera position - or more precisely the reflected view matrix (because we need camera orientation too in addition to the position).&lt;br /&gt;This can be done with the following formula:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;M'&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt; = M&lt;span style="font-size: x-small;"&gt;reflection&lt;/span&gt; * M&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Where &lt;i&gt;&lt;b&gt;M&lt;span style="font-size: x-small;"&gt;reflection&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; is the reflection matrix of mirror surface. It can trivially be calculated from the position of reflection plane:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;              | 1-2Nx2   -2NxNy  -2NxNz  -2NxD |&lt;br /&gt;Mreflection = |  -2NxNy 1-2Ny2   -2NyNz  -2NyD |&lt;br /&gt;              |  -2NxNz  -2NyNz 1-2Nz2   -2NzD |&lt;br /&gt;              |    0       0       0       1   |&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Where (&lt;i&gt;&lt;b&gt;N&lt;span style="font-size: x-small;"&gt;x&lt;/span&gt;,N&lt;span style="font-size: x-small;"&gt;y&lt;/span&gt;,N&lt;span style="font-size: x-small;"&gt;z&lt;/span&gt;,D&lt;/b&gt;&lt;/i&gt;) are the coefficients of plane equation (&lt;i&gt;&lt;b&gt;xN&lt;span style="font-size: x-small;"&gt;x&lt;/span&gt; + yN&lt;span style="font-size: x-small;"&gt;y&lt;/span&gt; + zN&lt;span style="font-size: x-small;"&gt;z&lt;/span&gt; + D = 0&lt;/b&gt;&lt;/i&gt;). Notice, that (&lt;i&gt;&lt;b&gt;N&lt;span style="font-size: x-small;"&gt;x&lt;/span&gt;,N&lt;span style="font-size: x-small;"&gt;y&lt;/span&gt;,N&lt;span style="font-size: x-small;"&gt;z&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;) is also the normal vector of given plane.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;M&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; is the transformation of camera as if it would be "normal" object in scene. To get ModelView matrix you will need the inverse of it&lt;i&gt;&lt;b&gt;.&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1.2. Mirrored geometry &lt;/b&gt;&lt;br /&gt;&lt;div style="margin-bottom: 0cm;"&gt;Actually we cheated a little in the previous image. We rotated the mirrored image 180&lt;span style="font-family: 'Times New Roman', serif;"&gt;º&lt;/span&gt; to make it more similar to the original image, so the effect of parallax can be seen. The actual mirrored image looks like this:&lt;/div&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Bc87MIvKygc/TnN-zGwd2kI/AAAAAAAAFRA/IebKpTnjc_Q/s1600/reflections-2.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="180" src="http://2.bp.blogspot.com/-Bc87MIvKygc/TnN-zGwd2kI/AAAAAAAAFRA/IebKpTnjc_Q/s320/reflections-2.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Different winding order on mirrored image&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Notice, that the winding order of polygons in image is flipped on mirrored image - i.e. the triangle is oriented CCW on original but CW on reflection.&lt;br /&gt;&lt;br /&gt;This may or may not be problem for you. If all your materials are double sided (i.e. you do not do back face culling) or if you can set up rendering pipeline in such a way, that you can change culling direction it is OK. In my case though, I prefer to keep culling always on and have forward-facing always defined as CCW. So something has to be done with the reflected image - or otherwise geometry will not render properly.&lt;br /&gt;&lt;br /&gt;We will exploit the feature that camera is always (at least in most applications) rectangular and centered around view direction. Thus we can just flip camera in &lt;b style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;Y&lt;/b&gt; direction and the winding order will be correct again (it flips reflected image so it looks like (3) on the first picture).&lt;br /&gt;This can be done with one more reflection matrix:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;M''&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt; = M&lt;span style="font-size: x-small;"&gt;reflection&lt;/span&gt; * M&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;i&gt;&lt;b&gt; * M&lt;span style="font-size: x-small;"&gt;flip&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Where &lt;i&gt;&lt;b&gt;M&lt;span style="font-size: x-small;"&gt;flip&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; is simply another reflection matrix that does reflection over &lt;b style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;XZ&lt;/b&gt; plane.&lt;br /&gt;Now if we render mirrored image using &lt;i&gt;&lt;b&gt;M''&lt;span style="font-size: x-small;"&gt;camera &lt;/span&gt;&lt;/b&gt;&lt;/i&gt;as camera matrix, pipeline can be left intact. We, of course, have to save this matrix for later reference, because it is needed to properly map our texture to water object in main render stage.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1.3. Underwater clipping&lt;/b&gt;&lt;br /&gt;Take a look at the following picture:&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-NEk2XMHWqf8/TnOC3-uiArI/AAAAAAAAFRI/FHfKSSkVdhA/s1600/reflections-3.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="240" src="http://2.bp.blogspot.com/-NEk2XMHWqf8/TnOC3-uiArI/AAAAAAAAFRI/FHfKSSkVdhA/s320/reflections-3.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A reflection with underwater object&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;We have added an underwater object &lt;b style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;Q&lt;/b&gt; to our scene. Now it should not appear on reflection, because it does not block the actual reflection rays &lt;b style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;PB'B&lt;/b&gt; and &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;PA'A&lt;/span&gt;&lt;/b&gt;. But we are not doing ray-tracing. We are instead moving camera to mirrored viewpoint &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;P'&lt;/span&gt;&lt;/b&gt; and rendering reflection like normal image. But as you can see, the object &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;Q&lt;/span&gt;&lt;/b&gt; blocks ray &lt;b style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;P'A'A&lt;/b&gt; and thus would show up in our reflection.&lt;br /&gt;&lt;br /&gt;Thus we have to make sure, that nothing that is under the reflection plane (water surface) will show up in mirror rendering. This can be achieved in three different ways:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Use additional clipping plane on GPU. It can be very fast or very slow - depending on card and driver used.&lt;/li&gt;&lt;li&gt;Use oblique projection matrix during reflection rendering. You can read more about it &lt;a href="http://www.terathon.com/code/oblique.html"&gt;here&lt;/a&gt;. This is cool technique, but personally I have never got it to work well enough because it messes up camera far plane.&lt;/li&gt;&lt;li&gt;Clip manually in pixel shaders. It wastes some GPU cycles, but is otherwise easy and foolproof.&lt;/li&gt;&lt;/ol&gt;I went with option (3) because oblique projection matrix did not seem to play well with wide camera angles (far plane moved through infinity creating all kinds of weird effects). The clipping itself is as easy as adding the following code at the beginning of all pixel shaders (or more precisely the ones that are used for reflectable objects):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;uniform vec4 clip_plane;&lt;br /&gt;varying vec3 interpolatedVertexEye;&lt;br /&gt;&lt;br /&gt;void main()&lt;br /&gt;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float clipPos = dot (interpolatedVertexEye, clip_plane.xyz) + clip_plane.w;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (clipPos &amp;lt; 0.0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; discard;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Of course you have to supply your shader with &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;clip_plane&lt;/span&gt;&lt;/b&gt; and calculate &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;interpolatedVertexEye&lt;/span&gt;&lt;/b&gt; in vertex shader (it is simply vertex coordinate in view/eye space: &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;VertexEye = Mmodelview * Vertex&lt;/span&gt;&lt;/b&gt;). If you do not need clipping, simply set &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;clip_plane&lt;/span&gt;&lt;/b&gt; normal (&lt;b style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;xyz&lt;/b&gt;) to zero and all pixels will be rendered.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1.4. Putting it all together&lt;/b&gt;&lt;br /&gt;Before starting the main render pass (being it forward or deferred) do the following:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Create list of all objects that need reflections (and the parameters of all reflection planes). Then for each reflection plane:&lt;/li&gt;&lt;li&gt;Calculate the reflected camera matrix&lt;i&gt;&lt;b&gt;&lt;br /&gt;M''&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt; = M&lt;span style="font-size: x-small;"&gt;reflection&lt;/span&gt; * M&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;i&gt;&lt;b&gt; * M&lt;span style="font-size: x-small;"&gt;flip&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;&lt;li&gt;Set up camera matrices (you can optimize rendering by using clipped projection matrix, but this will not be discussed here).&lt;/li&gt;&lt;li&gt;Set clipping plane to reflection plane&lt;/li&gt;&lt;li&gt;Render full scene&lt;/li&gt;&lt;li&gt;Save the rendered image as texture to be used with reflective object&lt;/li&gt;&lt;/ol&gt;If you are using HDR you should not tone-map reflection texture - unless you want to achieve some very specific effect.&lt;br /&gt;&lt;ol&gt;&lt;/ol&gt;&lt;span style="font-size: large;"&gt;2. Rendering reflective object&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is actually quite easy - provided that you have at hand all necessary parameters. You have still to decide at which render stage to do this. I use transparent stage, as water is basically just one semi-transparent surface in scene, but you can add another pass before or after transparency as well.&lt;br /&gt;&lt;br /&gt;You will need at hand:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Reflected camera matrix &lt;i&gt;&lt;b&gt;M''&lt;span style="font-size: x-small;"&gt;camera&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;&lt;li&gt;Projection matrix you used to render reflection &lt;b&gt;&lt;i&gt;M&lt;span style="font-size: x-small;"&gt;projectionreflection&lt;/span&gt;&lt;/i&gt;&lt;/b&gt; (normally this is the same projection that you use for main camera)&lt;/li&gt;&lt;li&gt;Reflection texture&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;ul&gt;&lt;/ul&gt;&lt;b&gt;2.1. Vertex shader&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;attribute vec3 vertex;&lt;br /&gt;&lt;br /&gt;uniform mat4 o2v_projection;&lt;br /&gt;&lt;br /&gt;varying vec3 interpolatedVertexObject;&lt;br /&gt;&lt;br /&gt;void main()&lt;br /&gt;{&lt;br /&gt;	gl_Position = o2v_projection * vec4(vertex.xy, 0.0, 1.0);&lt;br /&gt;	interpolatedVertexObject = vertex;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We add another constraint here - water surface will be at &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;XY&lt;/span&gt;&lt;/b&gt; plane of the object local coordinate system. It is strictly not necessary if you have the proper reflection plane, but I found it easier that way. Just use &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;XY&lt;/span&gt;&lt;/b&gt; plane as reflection plane and place your object (water body) appropriately.&lt;br /&gt;&lt;br /&gt;Actually this allows us to do another cool trick. We can use the bottom of water body (i.e. river, lake..) as our water object. It will be flattened in shader, but we can use the &lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;Z&lt;/span&gt;&lt;/b&gt; data to determine the depth of water at given point. But more about this in next part.&lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;o2v_projection&lt;/span&gt;&lt;/b&gt; is simply my name for composite matrix &lt;b&gt;Projection * ModelView&lt;/b&gt;. I prefer to name matrices with mnemonic names, describing the coordinate system transformations they do - in given case it is &lt;b&gt;Object To View&lt;/b&gt;, multiplied with &lt;b&gt;Projection&lt;/b&gt;.&lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;interpolatedVertexObject&lt;/span&gt;&lt;/b&gt; is simply vertex coordinate in object local coordinate system - we will need it to do lookup onto reflection texture.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2.2. Fragment shader&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;uniform mat4 o2v_projection_reflection;&lt;br /&gt;uniform sampler2D reflection_sampler;&lt;br /&gt;&lt;br /&gt;varying vec3 interpolatedVertexObject;&lt;br /&gt;&lt;br /&gt;void main()&lt;br /&gt;{&lt;br /&gt;	vec4 vClipReflection = o2v_projection_reflection * vec4(interpolatedVertexObject.xy, 0.0 , 1.0);&lt;br /&gt;	vec2 vDeviceReflection = vClipReflection.st / vClipReflection.q;&lt;br /&gt;	vec2 vTextureReflection = vec2(0.5, 0.5) + 0.5 * vDeviceReflection;&lt;br /&gt;&lt;br /&gt;	vec4 reflectionTextureColor = texture2D (reflection_sampler, vTextureReflection);&lt;br /&gt;&lt;br /&gt;	// Framebuffer reflection can have alpha &amp;gt; 1&lt;br /&gt;	reflectionTextureColor.a = 1.0;&lt;br /&gt;&lt;br /&gt;	gl_FragColor = reflectionTextureColor;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;o2v_projection_reflection&lt;/span&gt;&lt;/b&gt; is the composite matrix &lt;b&gt;Projection * ModelView&lt;/b&gt; as it was used during reflection rendering. I.e:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;i&gt;M&lt;span style="font-size: x-small;"&gt;projectionreflection * &lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;i&gt;&lt;b&gt;(M''&lt;span style="font-size: x-small;"&gt;camera)-1 * Mobject&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Like the name implies, it transforms from the object coordinate system to the clip coordinate system of reflection camera.&lt;br /&gt;&lt;br /&gt;In fragment shader we simply repeat the full transform pipeline during reflection rendering and use final 2D coordinates for texture lookup. For this we need initial, untransformed object vertices - thus they are interpolated from vertex shader (&lt;b&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;interpolatedVertexObject&lt;/span&gt;&lt;/b&gt;).&lt;br /&gt;&lt;br /&gt;I'll set reflection &lt;b&gt;alpha &lt;/b&gt;to &lt;b&gt;1.0&lt;/b&gt; because I use HDR buffers and due to additive blending the final alpha can have some very weird values there.&lt;br /&gt;&lt;br /&gt;And the rendered image:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-bFCjb8rWuYg/TnOgLlR6P8I/AAAAAAAAFRQ/97Aow-r-vSI/s1600/reflections-4.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://3.bp.blogspot.com/-bFCjb8rWuYg/TnOgLlR6P8I/AAAAAAAAFRQ/97Aow-r-vSI/s400/reflections-4.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Simple scene from Shinya showing water as perfect mirror&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Not very realistic?&lt;br /&gt;Up to now we have implemented water as perfect mirror. This is very far from reality (look at the feature list in the first section).&lt;br /&gt;&lt;br /&gt;In the next parts I will show how to add viewing angle based transparency, water color and depth-dependent ripples to your water.&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-68991470304490751?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/68991470304490751/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/09/reflective-water-with-glsl-part-i.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/68991470304490751'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/68991470304490751'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/09/reflective-water-with-glsl-part-i.html' title='Reflective water with GLSL, Part I'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-3_UlH2s2KbI/TnN7idEjIJI/AAAAAAAAFQ4/hL2r8zgKC2w/s72-c/reflections.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-3007235040547328462</id><published>2011-08-29T01:40:00.000+03:00</published><updated>2011-08-29T01:40:01.101+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='sehle'/><title type='text'>GLSL water reflections</title><content type='html'>I have been mostly working on sehle (Khayyam rendering engine) based game framework lately. There have been some new things in Khayyam - that will be the scene/level builder for it - as well. The latest addition is reflective water material.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-A6sOzjBHX9I/Tlq7AV0eQjI/AAAAAAAAFQc/6i3mJeOu7nk/s1600/khayyam-20110828.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://4.bp.blogspot.com/-A6sOzjBHX9I/Tlq7AV0eQjI/AAAAAAAAFQc/6i3mJeOu7nk/s400/khayyam-20110828.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Esk standing on near water - with ripples and reflection&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;You can convert all static objects (OBJ, 3DS and Collada) to water bodies - although to get realistic results, they should be bowl- or plane shaped with surface at Z zero (in object coordinates). Everything is fake, of course - there are no real waves or refraction, but only distorted reflections and transparency. But it still looks reasonably good in my opinion.&lt;br /&gt;&lt;br /&gt;At moment there are following water properties implemented:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Full reflection (everything that can be rendered can be reflected, except other reflective surfaces)&lt;/li&gt;&lt;li&gt;Viewing angle based reflectivity - i.e. if looking directly down, water is mostly transparent, looking along water surface it becomes almost perfect mirror&lt;/li&gt;&lt;li&gt;Depth-based transparency - shallow water is almost transparent, deeper water becomes opaque&lt;/li&gt;&lt;li&gt;Up to 8 simultaneous ripple generators, that can be assigned to "shore" vertices of water body&lt;/li&gt;&lt;/ul&gt;At the image below you can see how the water color changes from almost fully transparent near bank to completely opaque brown at the middle of river. As the viewing angle is high, overall reflectivity is low and the colors of river bottom and water can be seen. Ripples change the apparent angle between viewer and water surface, so although sky is uniform color, ripples still show as they change the reflectivity of surface.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-joZsaOwDQl8/Tlq-zkJrPaI/AAAAAAAAFQo/JATcjSOUG5Y/s1600/khayyam-20110828-1.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://4.bp.blogspot.com/-joZsaOwDQl8/Tlq-zkJrPaI/AAAAAAAAFQo/JATcjSOUG5Y/s400/khayyam-20110828-1.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A river from above showing water and bottom colors&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&amp;nbsp;At the next image the viewing angle is low and thus the surface of water is almost completely reflective.&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-KhUJJ6GHIBk/Tlq-zJwFpPI/AAAAAAAAFQk/rboDOcyeunM/s1600/khayyam-20110828-2.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://2.bp.blogspot.com/-KhUJJ6GHIBk/Tlq-zJwFpPI/AAAAAAAAFQk/rboDOcyeunM/s400/khayyam-20110828-2.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A river from low angle, showing almost perfect mirror&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Ripples are circular and their wavelengths, amplitudes and generation points are updated randomly over time. Thus the actual pattern almost never repeats - although sometimes it is not as nice as on the image above.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;When I find some free time, I'll write the technical details about the actual mathematics in shaders. And hopefully there will be new Khayyam release too.&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-3007235040547328462?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/3007235040547328462/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/08/glsl-water-reflections.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3007235040547328462'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3007235040547328462'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/08/glsl-water-reflections.html' title='GLSL water reflections'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-A6sOzjBHX9I/Tlq7AV0eQjI/AAAAAAAAFQc/6i3mJeOu7nk/s72-c/khayyam-20110828.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-359246242673462110</id><published>2011-07-18T03:54:00.000+03:00</published><updated>2011-07-18T03:54:50.970+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='mathematics'/><category scheme='http://www.blogger.com/atom/ns#' term='openGL'/><category scheme='http://www.blogger.com/atom/ns#' term='sehle'/><title type='text'>Encoding normals for Gbuffer</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-zL51eUsw6hM/TiNhrepmEUI/AAAAAAAAFJk/_BAKjSP-08o/s1600/normal_packing_c.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-zL51eUsw6hM/TiNhrepmEUI/AAAAAAAAFJk/_BAKjSP-08o/s1600/normal_packing_c.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;/div&gt;Deferred shading has nowadays become nearly ubiquitous in real-time rendering  due to the separation of geometry and lighting passes. The general idea  of this technique is to render all important properties (color, normal,  reflectivity and so on) of all visible surfaces to a set of  screen-sized textures. Later rendering passes (lighting) get surface  parameters from those textures and thus complex geometries do not have  to be rendered multiple times.&lt;br /&gt;&lt;br /&gt;One design problem of Gbuffer  implementation is the efficient encoding of surface normals. Normals in  three-dimensional space have three components: &lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;N = Nx,Ny,Nz&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The most intuitive way would be to store those in three texture channels. But this would be wasteful, because we know, that:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;Nx2 + Ny2 + Nz2 = 1&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Gbuffer requires many texture channels for other data, thus we want to cut off as many unnecessary channels as possible to conserve memory and fillrate.&lt;br /&gt;&lt;br /&gt;Another  simple alternative would be to store only two components (Nx and Ny)  and reconstruct third (Nz) in pixel shader. Unfortunately we will lose  the sign of Nz in that process. While in ideal scene there should be no  normals with negative Z (facing away from camera), they happen in real  scenes because of realistic polygon counts.&lt;br /&gt;&lt;br /&gt;There exist several techniques for encoding normals while (at least partially) preserving Z component. Unfortunately no one of those is ideal. Either they are not able to encode full range of Z (various projections) or waste precision and calculation (polar coordinates).&lt;br /&gt;&lt;br /&gt;While implementing foliage shader for Khayyam I needed a way to encode full range of possible normals into two-component vector (that will later be stored in 4-component 8-bit RGBA texture of Gbuffer). The following is the brief introduction of the technique used. It solves all the problems I am aware of - with the price of slightly more shader instructions than simple projection encoders.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Efficient projection of normal space&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We forget for awhile the need of storing the sign of Z component and look, how we can store X and Y components most efficently.&lt;br /&gt;The trivial way is to simply forget Z and store X and Y. In geometric terms it means projecting the hemisphere of normal space to an unit circle on XY plane along Z vector as in following figure:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-qL5m3LCqGao/TiNZTpmQqaI/AAAAAAAAFJU/qbqzmbPEWt8/s1600/normal_packing_a.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="375" src="http://2.bp.blogspot.com/-qL5m3LCqGao/TiNZTpmQqaI/AAAAAAAAFJU/qbqzmbPEWt8/s400/normal_packing_a.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The trivial projection of normal space to XY plane&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The problem with this projection is, that the uniform angular difference between normals near equator gives much smaller difference in X and Y coordinates than near poles. If the precision of normal texture is limited to 16 bits (4x8 bit or 2x16 bit integer buffers) or even less (FP16 buffers) , the loss of precision may become visible. For example, for 16 bit buffers, the minimum representable angle near equator is:&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;acos (1 - 2e-15) = 1.44°&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;Near poles it is much better:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;asin (2e-15) = 0.002°&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Clearly we are wasting precision near poles and introduce errors near equator.&lt;br /&gt;&lt;br /&gt;To get better distribution of precision, we can switch projection from parallel to radial, with the projection center at the point (0,0,-1) as on the following figure:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-uoQ601_v0Jw/TiNZUI1BGDI/AAAAAAAAFJY/3f8xAf4QiIk/s1600/normal_packing_b.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="375" src="http://2.bp.blogspot.com/-uoQ601_v0Jw/TiNZUI1BGDI/AAAAAAAAFJY/3f8xAf4QiIk/s400/normal_packing_b.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The radial projection of normal space&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The distribution of angles is still not uniform but is clearly much better (it differs only by the factor of 2 at poles and equator).&lt;br /&gt;Actually, having the center of projection at (0,0,-sqrt(2)) would result in even better distribution, but I wanted to keep it at (0,0,-1) to simplify calculations a bit.&lt;br /&gt;&lt;br /&gt;The hemisphere is still projected at unit circle at XY plane, although now we cannot project the normals on negative hemisphere directly but have to take the absolute value of Z coordinate before projecting.&lt;br /&gt;&lt;br /&gt;This solves the problem of effecient use of normal texture precision. Now we can go on to encoding the sign of Z into the same buffer.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Encoding the sign of normal Z coordinate&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Our positive hemisphere of normal space got projected to unit circle on XY plane as in following figure:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-zL51eUsw6hM/TiNhrepmEUI/AAAAAAAAFJk/_BAKjSP-08o/s1600/normal_packing_c.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="343" src="http://2.bp.blogspot.com/-zL51eUsw6hM/TiNhrepmEUI/AAAAAAAAFJk/_BAKjSP-08o/s400/normal_packing_c.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The normal space projection to XY plane and compaction schema&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;As we can see, we are actually wasting the numerical space of normal texture. The texture is able to encode values in unit square, but only unit circle is used - the possible values on dashed areas can never happen in our projected normals. We could store normals with negative Z in those areas, but unfortunately there is no easy algorithm to do that.&lt;br /&gt;&lt;br /&gt;Now we do another conversion and pack all the values from unit circle into diamond-shaped area, given by the following formula:&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;|X| + |Y| &amp;lt;= 1&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;This can be done with the following algorithm (let the normal be Nx', Ny' and the projection be Nx'', Ny''):&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;r = sqrt (Nx' * Nx' + Ny' * Ny')&lt;/b&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;D = |Nx'| + |Ny'|&lt;/b&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;Nx" = Nx' * r / D&lt;/b&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;Ny" = Ny' * r / D&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;We lose some precision at&amp;nbsp; the normals close to 45&lt;b&gt;°&lt;/b&gt; angles, but the maximum loss is by the factor of sqrt(2).&lt;br /&gt;&lt;br /&gt;The resulting projection of normal space is shown in following figure:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-XwVyzJeOtvw/TiNZTIUmT0I/AAAAAAAAFJQ/ASMZTrxdmqc/s1600/normal_packing_d.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="343" src="http://1.bp.blogspot.com/-XwVyzJeOtvw/TiNZTIUmT0I/AAAAAAAAFJQ/ASMZTrxdmqc/s400/normal_packing_d.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Compacted normal space and Z flipping directions&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Now the whole projection of the positive hemisphere of normal space takes exactly the half of the size of unit square. The extra space can be used to encode normals with negative Z.&lt;br /&gt;&lt;br /&gt;We do this by mirroring the final normal projections by the closest one of diagonal lines (|x| + |y| = 1).&lt;br /&gt;&lt;br /&gt;Thus we have encoded the full normal space to unit square with minimal loss of precision.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Decoding normals&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To decode normals we have first to find the sign of Z coordinate. This is done with the following formula:&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;Zsign =&amp;nbsp; 1 if (|x| + |y|) &amp;lt;= 1&lt;/b&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;Zsign = -1 if (|x| + |y|) &amp;gt;&amp;nbsp; 1&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;Now, if Z is negative, we have to mirror our encoded normal by the closest&amp;nbsp; diagonal line (|x| + |y| = 1) so the resulting normal is inside the diamond shaped area.&lt;br /&gt;&lt;br /&gt;We unpack normals to unit circle with reverse packing algorithm:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;d = sqrt (Nx" * Nx" + Ny" * Ny")&lt;/b&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;r = |Nx"| + |Ny"|&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;Nx' = Nx" * d / r&lt;/b&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;b&gt;Ny' = Ny" * d / r&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;From normal projection on unit sphere (N') unit sphere we unproject to normal on positive hemisphere (N).&lt;br /&gt;&lt;br /&gt;Finally we set the sign of the Z component of N from previously found Zsign.&lt;br /&gt;&lt;br /&gt;And that is it.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Encoding quality&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I ran simulated tests of this algorithm on CPU with 100 000 000 randomly generated normals, including normals with any 1 or 2 components zero. Encoded vector was written and then read back from 4-component unsigned char buffer to simulate storing of 2-component normal in RGBA8 texture.&lt;br /&gt;&lt;br /&gt;The maximum angular error between original and retrieved normal was&lt;span style="font-family: inherit;"&gt; 0.028&lt;/span&gt;&lt;span style="font-family: inherit;"&gt;°.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;GLSL shader code&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For those interested, here is current GLSL shader code of libsehle implementation. It is a bit heavy, but if your lighting is fill-rate bound, as is mine, you can spare some GPU cycles.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Encoding.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;span style="font-size: small;"&gt;vec2 encodeNormal (vec3 normal)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Project normal positive hemisphere to unit circle&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // We project from point (0,0,-1) to the plane [0,(0,0,-1)]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // den = dot (l.d, p.n)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // t = -(dot (p.n, l.p) + p.d) / den&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vec2 p = normal.xy / (abs (normal.z) + 1.0);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Convert unit circle to square&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // We add epsilon to avoid division by zero&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float d = abs (p.x) + abs (p.y) + EPSILON;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float r = length (p);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vec2 q = p * r / d;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Mirror triangles to outer edge if z is negative&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float z_is_negative = max (-sign (normal.z), 0.0);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vec2 q_sign = sign (q);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; q_sign = sign (q_sign + vec2 (0.5, 0.5));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Reflection&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // qr = q - 2 * n * (dot (q, n) - d) / dot (n, n)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; q -= z_is_negative * (dot (q, q_sign) - 1.0) * q_sign;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return q;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;Decoding.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace; font-size: small;"&gt;vec3 decodeNormal (vec2 encodedNormal)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vec2 p = encodedNormal;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Find z sign&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float zsign = sign (1.0 - abs (p.x) - abs (p.y));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Map outer triangles to center if encoded z is negative&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float z_is_negative = max (-zsign, 0.0);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vec2 p_sign = sign (p);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; p_sign = sign (p_sign + vec2 (0.5, 0.5));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Reflection&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // qr = q - 2 * n * (dot (q, n) - d) / dot (n, n)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; p -= z_is_negative * (dot (p, p_sign) - 1.0) * p_sign;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Convert square to unit circle&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // We add epsilon to avoid division by zero&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float r = abs (p.x) + abs (p.y);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float d = length (p) + EPSILON;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vec2 q = p * r / d;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Deproject unit circle to sphere&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; float den = 2.0 / (dot (q, q) + 1.0);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vec3 v = vec3(den * q, zsign * (den - 1.0));&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return v;&lt;br /&gt;}&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-359246242673462110?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/359246242673462110/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/07/encoding-normals-for-gbuffer.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/359246242673462110'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/359246242673462110'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/07/encoding-normals-for-gbuffer.html' title='Encoding normals for Gbuffer'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-qL5m3LCqGao/TiNZTpmQqaI/AAAAAAAAFJU/qbqzmbPEWt8/s72-c/normal_packing_a.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-5897539994752882812</id><published>2011-06-13T02:47:00.001+03:00</published><updated>2011-09-15T22:19:45.072+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='elea'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='mathematics'/><category scheme='http://www.blogger.com/atom/ns#' term='geometry'/><title type='text'>More about Matrix to Euler angles conversion</title><content type='html'>I previously posted the &lt;a href="http://khayyam.kaplinski.com/2011/02/converting-rotation-matrix-to.html"&gt;algorithm to convert rotation matrix to arbitrarily ordered Euler angles&lt;/a&gt;.&lt;br /&gt;It solved two problems of the trivial conversion method in &lt;a href="http://www.j3d.org/matrix_faq/matrfaq_latest.html"&gt;Matrix and Quaternion FAQ&lt;/a&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Use arbitrarily ordered axes (like ZXY) in addition to XYZ&lt;/li&gt;&lt;li&gt;Remove the discontinuity happening at Ax = +/- 90 degrees&lt;/li&gt;&lt;/ul&gt;Meanwhile I discovered two remaining problems with my implementation:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The algorithm always return the minimum set of absolute Euler angles (closest to {0;0;0}, as defined by the sum of squares of the angles) from the two alternatives. Often it is desirable to get the set of angles that are closest not to identity {0;0;0} but to some predefined rotation {Rx;Ry;Rz}.&lt;/li&gt;&lt;li&gt;When gimbal lock occured, the X rotation (or corresponding rotation if axes were reordered) was set to zero. This may not be the best solution, if we need the Euler angles to be as close to some specific set of values as possible.&lt;/li&gt;&lt;/ul&gt;The problem surfaces if we want to describe animations by Euler angles to allow easy adjustments by IK solvers (and have joint constraints defined) and interpolate between keyframes. In that case we absolutely have to keep the angles of adjacent keyframes as similar as possible - because although the alternate sets of angles make identical matrices, interpolations between them generally do not.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Stable conversio of matrix to Euler angles closest to given set&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The matrices have row-major order, so they have to be transposed for OpenGL.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Base&lt;/span&gt;&lt;/b&gt; is a set of three angles {Bx;By;Bz} that will be used as reference while determining the best set of resulting angles.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;void&lt;br /&gt;Matrix3x4f::getEulerAngles (f32 angles[], const f32 base[]) const&lt;br /&gt;{&lt;br /&gt; float aX, aY, aZ;&lt;br /&gt; float aX2, aY2, aZ2;&lt;br /&gt;&lt;br /&gt; aY = asin (c[2]);&lt;br /&gt; aY2 = (aY &amp;gt;= 0) ? M_PI_F - aY : -M_PI_F - aY;&lt;br /&gt; if (fabs (c[2]) &amp;lt; 0.99999f) {&lt;br /&gt;  &lt;span style="color: blue;"&gt;// Over 0.25 degrees from right angle&lt;/span&gt;&lt;br /&gt;  float C = cos (aY);&lt;br /&gt;  float C2 = cos (aY2);&lt;br /&gt;  float trX = c[10] / C;&lt;br /&gt;  float trY = -c[6] / C;&lt;br /&gt;  aX = atan2 (trY, trX);&lt;br /&gt;  aX2 = atan2 (-c[6] / C2, c[10] / C2);&lt;br /&gt;  trX = c[0] / C;&lt;br /&gt;  trY = -c[1] / C;&lt;br /&gt;  aZ = atan2 (trY, trX);&lt;br /&gt;  aZ2 = atan2 (-c[1] / C2, c[0] / C2);&lt;br /&gt; } else {&lt;br /&gt;  &lt;span style="color: blue;"&gt;// Less than 0.25 degrees from right angle&lt;/span&gt;&lt;br /&gt;  &lt;span style="color: blue;"&gt;// Gimbal lock occured&lt;/span&gt;&lt;br /&gt;  aX = aX2 = base[0];&lt;br /&gt;  &lt;span style="color: blue;"&gt;// Keep X rotation the same as base&lt;/span&gt;&lt;br /&gt;  float Sx = sin (aX);&lt;br /&gt;  float Cx = cos (aX);&lt;br /&gt;  if (fabs (Cx) &amp;gt; fabs (Sx)) {&lt;br /&gt;   float Sz = (c[4] + (Sx/Cx) * c[6]) / (Sx*Sx/Cx + Cx);&lt;br /&gt;   aZ = aZ2 = asin (Sz);&lt;br /&gt;  } else {&lt;br /&gt;   float Sz = (c[6] + (Cx/Sx) * c[4]) / (Cx*Cx/Sx + Sx);&lt;br /&gt;   aZ = aZ2 = asin (Sz);&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; float d1X = clamp_pi (aX - base[0]);&lt;br /&gt; float d1Y = clamp_pi (aY - base[1]);&lt;br /&gt; float d1Z = clamp_pi (aZ - base[2]);&lt;br /&gt; float d2X = clamp_pi (aX2 - base[0]);&lt;br /&gt; float d2Y = clamp_pi (aY2 - base[1]);&lt;br /&gt; float d2Z = clamp_pi (aZ2 - base[2]);&lt;br /&gt;&lt;br /&gt; if ((d1X * d1X + d1Y * d1Y + d1Z * d1Z) &amp;lt; (d2X * d2X + d2Y * d2Y + d2Z * d2Z)) {&lt;br /&gt;  angles[X] = aX;&lt;br /&gt;  angles[Y] = aY;&lt;br /&gt;  angles[Z] = aZ;&lt;br /&gt; } else {&lt;br /&gt;  angles[X] = aX2;&lt;br /&gt;  angles[Y] = aY2;&lt;br /&gt;  angles[Z] = aZ2;&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;If aY is close to +/- 90 degrees (gimbal lock occured) we want to set aX to the base value instead of setting it to zero.&lt;br /&gt;Also we calculate the differences of both alternate sets of angles from base and choose the final set based on the squared sum of those differences.&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Clamp_pi&lt;/span&gt;&lt;/b&gt; simply converts angle to -PI...PI range.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Conversion of matrix to arbitrarily ordered Eular angles&lt;/span&gt; &lt;br /&gt;&lt;br /&gt;The matrices have row-major order, so they have to be transposed for OpenGL.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Order &lt;/span&gt;&lt;/b&gt;is array of three integers, determining the order of rotations ({0;1;2} is XYZ, {2;0;1} is ZXY).&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Base &lt;/span&gt;&lt;/b&gt;values should be ordered according to order.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;void&lt;br /&gt;Matrix3x4f::getEulerAngles (f32 angles[], const int order[], const f32 base[]) const&lt;br /&gt;{&lt;br /&gt; float sign = ((order[1] == (order[0] + 1)) || (order[2] == (order[1] + 1))) ? 1.0f : -1.0f;&lt;br /&gt; &lt;span style="color: blue;"&gt;// Permute matrix to switched axes space&lt;/span&gt;&lt;br /&gt; Elea::Matrix3x4f rs;&lt;br /&gt; for (int row = 0; row &amp;lt; 3; row++) {&lt;br /&gt;  for (int col = 0; col &amp;lt; 3; col++) {&lt;br /&gt;   rs[4 * row + col] = c[4 * order[row] + order[col]];&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; rs[2] *= sign;&lt;br /&gt; rs[6] *= sign;&lt;br /&gt; rs[8] *= sign;&lt;br /&gt; rs[9] *= sign;&lt;br /&gt; &lt;span style="color: blue;"&gt;// New base&lt;/span&gt;&lt;br /&gt; float nbase[3];&lt;br /&gt; for (int i = 0; i &amp;lt; 3; i++) nbase[i] = base[i];&lt;br /&gt; nbase[2] *= sign;&lt;br /&gt; &lt;span style="color: blue;"&gt;// Decompose to Euler angles&lt;/span&gt;&lt;br /&gt; rs.getEulerAngles (angles, nbase);&lt;br /&gt; angles[2] *= sign;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The trick is to permute the rotation matrix in such way, that the XYZ angles of new matrix are properly ordered decomposition angles of the original matrix and then do the trivial decomposition.&lt;br /&gt;Hopefully this time the algorithm is final ;-)&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-5897539994752882812?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/5897539994752882812/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/06/more-about-matrix-to-euler-angles.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5897539994752882812'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5897539994752882812'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/06/more-about-matrix-to-euler-angles.html' title='More about Matrix to Euler angles conversion'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-2328415009543063866</id><published>2011-04-05T01:04:00.001+03:00</published><updated>2011-04-05T01:04:33.720+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><title type='text'>The future of Khayyam</title><content type='html'>Khayyam 3D did not have very clear beginning. Few years ago I was experimenting with 3D rendering and animation under projects codenamed Floyd and SDLMu. The initial impulse was to write an engine and scene editor for my Ant-Attack inspred game.&lt;br /&gt;The unorganized codebase soon grew out of control, so I started from scratch. Or more precisely ported the &lt;a href="http://khayyam.kaplinski.com/2010/10/khayyam-architecture.html"&gt;program structure&lt;/a&gt; from my former project Sodipodi. When choosing name for the new project, I discovered &lt;a href="http://en.wikipedia.org/wiki/Omar_Khayy%C3%A1m"&gt;Omar Khayyám&lt;/a&gt; - a Persian mathematician, who sort of discovered the inherent link between geometry and algebra. And thus the program was born.&lt;br /&gt;&lt;br /&gt;As of now, Khayyam is evolving in several directions based on what I am interested in any given moment.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Scene builder. My game is still in design stage, but at least I have the main character - Sara.&lt;/li&gt;&lt;li&gt;Virtual photography tool. It was partially inspired by &lt;a href="http://www.hongfire.com/"&gt;HongFire&lt;/a&gt; hentai game modding community and partially by Poser and DAZ applications.&lt;/li&gt;&lt;li&gt;Animation editor. This came from the need for some decent animations for Sara.&lt;/li&gt;&lt;li&gt;Model importer and converter. Implemented step-by step as needed.&lt;/li&gt;&lt;li&gt;3D engine. Mostly for learning, but also because I think I have some ideas, how it can be done right.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;As of now (April 2011) the program has at last become sort of usable for some simple tasks, like converting model formats, building POV-Ray scenes and so on. To go further, there should be a somewhat clearer working plan - which I have tried to lay out for myself.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;1. Scene builder&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Khayyam itself is not game engine and never will - the two-level DOM structure is too heavy for gameplay. Instead there will eventually be an engine based on libsehle.&lt;br /&gt;There will be custom object formats. At least an octree-based map node, skeletal figures and static objects. Those can be imported from Blender Collada files, optimized and written to custom format.&lt;br /&gt;Objects need JavaScript bindings. I plan to make JavaScript the scripting language for game engine, so the actions should be programmable and testable in Khayyam.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;2. Posing application&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Poser figure loading and manipulating is still very flaky. This will get better eventually, so expect that in some future date Khayyam will be able to render (with the help of POV-Ray) everything Poser and DAZ studio can. Virtual photography is becoming more and more used, and Linux definitely needs a free application for that. The fist milestones are unlit objects (like backgrounds) and smoothscale channel implementation for full body morphs.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;3. Animation editor&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Animations will be integrated globally into Khayyam document structure, so you can render not only still pictures, but full animation sequences.&lt;br /&gt;I am halfway implementing animation graph and biped walking controller for figures. Eventually you could import animations from other formats (like Biovision BVH), define sequences, merge, split, edit and join them, and build fully controllable animation graph from these. The controller will have IK solvers for fine limb/finger adjustments and will be controllable via JavaScript.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;4. Model converter&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;While several popular models are supported, the actual features vary a lot. There will be more model and animation formats (like MD5 animations) and more features. I also plan to support more game model formats - definitely all future Illusion games, but probably some others too.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;5. 3D engine&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The big missing parts are proper shadow map algorithms, particle effects and post processing.&lt;br /&gt;&lt;br /&gt;Other than that, Kahhaym will have some UI overhauls in future. Tools will have their own dynamic property pages. Some extra tools, like bone and object pickers are needed. And there have to be UI controls for constraint placement. Also I am particularly not happy with Gtk+ default style - which wastes enormous amount of screen space. At least the animation editor needs more compact style&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-eksObIM8VPM/TZo_4bgSgXI/AAAAAAAAFHM/sgSILN5rCpc/s1600/sara-and-elephant.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://4.bp.blogspot.com/-eksObIM8VPM/TZo_4bgSgXI/AAAAAAAAFHM/sgSILN5rCpc/s400/sara-and-elephant.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara and african elephant (DAZ 3D model)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-2328415009543063866?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/2328415009543063866/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/04/future-of-khayyam.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2328415009543063866'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2328415009543063866'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/04/future-of-khayyam.html' title='The future of Khayyam'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-eksObIM8VPM/TZo_4bgSgXI/AAAAAAAAFHM/sgSILN5rCpc/s72-c/sara-and-elephant.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-5653613872607108223</id><published>2011-04-04T00:06:00.000+03:00</published><updated>2011-04-04T00:06:11.643+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='animation'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><title type='text'>How to convert animations in Khayyam</title><content type='html'>Starting from the 20110402 release Khayyam can import, convert and export Biovision BVH animations from one figure/skeleton to another. Following is a mini-tutorial guiding you through the process.&lt;br /&gt;You may take a look to the &lt;a href="http://khayyam.kaplinski.com/2011/03/converting-animations-between-skeletons.html"&gt;overview of pose fitting algorithm&lt;/a&gt; to better understand which joints will be moved and how.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;0. The example problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Say, you have some good animations in BVH format and want to apply these to Poser characters. Unfortunately, skeleton topologies&amp;nbsp; and/or bone orientations do not match, so they are not usable as-is.&lt;br /&gt;Khayyam can actually export BVH files from supported animation types (currently only Illusion game formats). How to do this is outside of this tutorial - but as a hint look at &lt;b&gt;skinAnimation&lt;/b&gt; and &lt;b&gt;sequence&lt;/b&gt; objects in document tree editor.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;1. Setting up target&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Before we can convert animation to target figure, we need it's skeleton imported into Khayyam. Skeleton comes together with model, so at first we have to import Poser cr2 figure.&lt;br /&gt;It is probably good idea to use reduced resolution figures for this process, because animating the whole iteration 4 (V4, M4) models can be quite slow. I will use Chibibel for current example - she has much lower polycount and can show animations without too much frame-skipping.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Va91wOqG4NU/TZiss8bfiSI/AAAAAAAAFGY/ykWdIS2FSVg/s1600/chibi-imported.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://2.bp.blogspot.com/-Va91wOqG4NU/TZiss8bfiSI/AAAAAAAAFGY/ykWdIS2FSVg/s400/chibi-imported.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Chibibel imported to Khayyam&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;At moment Khayyam does not set up joint constraints automatically for cr2 objects, so you have to define at least elbow and knee constraints. Otherwise you risk, that legs or arms may suddenly bend sideways during animation.&lt;br /&gt;Select imported figure, switch to pose mode and select left shinbone.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-YwklhQlbZj0/TZitfky8xYI/AAAAAAAAFGc/MHZGEiRrndI/s1600/chibi-skeleton.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://4.bp.blogspot.com/-YwklhQlbZj0/TZitfky8xYI/AAAAAAAAFGc/MHZGEiRrndI/s400/chibi-skeleton.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Chibibel skeleton with shinbone selected&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The colors of rotation arrows mark the bone-relative axes of rotations (red - X, green - Y, blue Z). Normally knee bends only in one direction - in current case this is green rotation, i.e. Y axis. So you have to disable X and Z rotations.&lt;br /&gt;&lt;br /&gt;Open document tree dialog. It will automatically show property page for selected bone - i.e. left shin.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-cqf1qC-tDik/TZit0RPaOeI/AAAAAAAAFGg/9XfkKTVa9lI/s1600/chibi-bone-constraints.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://4.bp.blogspot.com/-cqf1qC-tDik/TZit0RPaOeI/AAAAAAAAFGg/9XfkKTVa9lI/s400/chibi-bone-constraints.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The property page of left shinbone&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;In &lt;b&gt;constraints&lt;/b&gt; frame select first &lt;strong&gt;X&lt;/strong&gt;, then &lt;strong&gt;Z&lt;/strong&gt; &lt;b&gt;channel&lt;/b&gt; and uncheck the &lt;b&gt;Enabled&lt;/b&gt; box for both. This turns off the rotations around those axes.&lt;br /&gt;On the same page you can set more fine-grained behavior by defining tension parameters for bones. Angles are basically maximal allowed rotations in either direction, rigidity values control, how "difficult" is given rotation. For example, knee joint moves very easily (in allowed range), but twisting shoulder is difficult, so character tries to minimize it. But if the pose is not achievable otherwise, the twisting still can happen - unlike the hard cutoff angles, that cannot be exceeded.&lt;br /&gt;In current example we fortunately do not need to set up tensions.&lt;br /&gt;Repeat the disabling &lt;strong&gt;X&lt;/strong&gt; and &lt;strong&gt;Z&lt;/strong&gt; channel rotations for right shinbone. Then select left forearm and disable &lt;strong&gt;X&lt;/strong&gt; and &lt;strong&gt;Y&lt;/strong&gt; rotations. The same with right forearm.&lt;br /&gt;Now we have completed the minimum setup needed for successful pose merge.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;2. Converting animation&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In document tree select the main figure node. From the property page click on &lt;b&gt;Merge animations...&lt;/b&gt; to open the converter dialog.&lt;br /&gt;The first step is to load source animation or pose. Click &lt;b&gt;Load animation...&lt;/b&gt; and select your BVH file.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-1l29riTh-6g/TZjGnwdP9tI/AAAAAAAAFGo/y4g1464Ir0c/s1600/chibi-merge-animation.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="253" src="http://4.bp.blogspot.com/-1l29riTh-6g/TZjGnwdP9tI/AAAAAAAAFGo/y4g1464Ir0c/s400/chibi-merge-animation.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Running animation applied to Chibibel skeleton&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Once the animation is loaded, Khayyam tries to adjust the destination figure to starting pose. This is iterative process and may take more than one try. So if the pose is not close to the original, you may try to click &lt;b&gt;Adjust pose&lt;/b&gt; few times.&lt;br /&gt;The red armature is the original animation skeleton. The blue one is the skeleton of target figure. These can be toggled on and off to see clearer picture.&lt;br /&gt;The other controls you may tweak are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Scale&lt;/strong&gt; - changes the size of source skeleton. This should be used (together with &lt;strong&gt;ZScale&lt;/strong&gt;) to make the skeletons approximately the same height - otherwise shoulders and hips can not be posed correctly.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;ZScale&lt;/strong&gt; - changes the height of source skeleton. Thus &lt;strong&gt;Scale&lt;/strong&gt; can be used to make the widths of hips and shoulders approximately right, and &lt;strong&gt;ZScale&lt;/strong&gt; then to make the height of character right.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Guess scale&lt;/strong&gt; - Khayyam tries to determine scale from the distances between hips and shoulders. This may or may not work correctly.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;EMax&lt;/strong&gt; - tha maximum error tolerance - if the combined distance between poseable chains (body, limbs...) is less than &lt;strong&gt;EMax&lt;/strong&gt;, iterative pose refinement will stop. Smaller value may give better adjusting but makes the process slower.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;TScale&lt;/strong&gt; - the scaling factor of bone tensions. Usually this can be 1 for default tensions or some smaller value (0.01) if all tensions have been set up in skeleton.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Runs&lt;/strong&gt; - number of distinct runs of IK solver&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Iterations&lt;/strong&gt; - the maximum number of tries in each run&lt;/li&gt;&lt;/ul&gt;Playing with these may give better results. If animation is jerky, increasing the number of runs often helps. If limbs do not track the originals well, lowering &lt;strong&gt;TScale&lt;/strong&gt; may help. Experiment!&lt;br /&gt;Also, you should keep in mind, that while hips and shoulders are placed using the absolute positions, other joints are adjusted by directions. &lt;a href="http://khayyam.kaplinski.com/2011/03/converting-animations-between-skeletons.html"&gt;Previous blog entry&lt;/a&gt; describes the process in more detail.&lt;br /&gt;Khayyam knows the bone names of some common skeletons and can determine the corresponding standard parts automatically. But sometimes it may not know, which standard humanoid bone certain name corresponds to. In that case you have to define bone names manually, using the bone mapper tool. This can be done by clicking &lt;b&gt;Map&lt;/b&gt; buttons in source and destination skeleton frames.&lt;br /&gt;Not all skeletal bones are 1:1 mappable and this is not even needed. What is important is, that standard joints of movable body parts are mapped correctly.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-90cjS4-XaZA/TZjI9kXlOKI/AAAAAAAAFGw/cGrA7yH56go/s1600/bone-mapper.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="295" src="http://4.bp.blogspot.com/-90cjS4-XaZA/TZjI9kXlOKI/AAAAAAAAFGw/cGrA7yH56go/s320/bone-mapper.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Bone mapper dialog for Poser skeleton&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;In the example above, you can see the bone mapper dialog for standard Poser biped skeleton (V4, M4, Chibibel). In the left-hand tree there are all bone names that appear in skeleton file, at right are the corresponding Khayyam standardized body parts. Once set up the mapping can be saved and loaded (actually the last saved mapping will be loaded automatically).&lt;br /&gt;Once the bone mappings are set up (and saved), click &lt;strong&gt;Close&lt;/strong&gt; and the conversion solver will update itself.&lt;br /&gt;You can play the animation with current conversion parameters using play button.&lt;br /&gt;&lt;br /&gt;3. Importing animation to document&lt;br /&gt;&lt;br /&gt;Once you are satisfied with the conversion results, click &lt;strong&gt;Import&lt;/strong&gt;. Khayyam will then generate a tree of animation objects as child nodes of parent Figure node.&lt;br /&gt;Hint: You can set &lt;strong&gt;NRuns&lt;/strong&gt; to bigger value and &lt;strong&gt;EMax&lt;/strong&gt; to lower value for import so the actual conversion will be done with maximal accuracy.&lt;br /&gt;The object of interest for us is a &lt;strong&gt;sequence&lt;/strong&gt; node, that will be created as the last child of &lt;strong&gt;figure&lt;/strong&gt;. It defines the imported animation sequence and can be played from play button on its property page.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-tw6MqLEmt8w/TZjc0Z6CFJI/AAAAAAAAFG4/rSF1xvtdTg8/s1600/chibi-animation-sequence.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://1.bp.blogspot.com/-tw6MqLEmt8w/TZjc0Z6CFJI/AAAAAAAAFG4/rSF1xvtdTg8/s400/chibi-animation-sequence.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The imported animation sequence object&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;If the imported sequence works as intended, you can export it to new BVH file. This new file uses already the proper skeleton of the target figure.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;object width="320" height="266" class="BLOG_video_class" id="BLOG_video-bd048a084c673a76" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;&lt;param name="movie" value="http://www.youtube.com/get_player"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="flashvars" value="flvurl=http://v6.nonxt2.googlevideo.com/videoplayback?id%3Dbd048a084c673a76%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1332715306%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D3C8A201A833B72C00F522F0D67AE7BD478895065.74E718E81CFE654B509ECCE5D75FD477B69C18F5%26key%3Dck1&amp;amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Dbd048a084c673a76%26offsetms%3D5000%26itag%3Dw160%26sigh%3DRxIul0xc-4oGSf5Gg0DFYxU7rhM&amp;amp;autoplay=0&amp;amp;ps=blogger"&gt;&lt;embed src="http://www.youtube.com/get_player" type="application/x-shockwave-flash"width="320" height="266" bgcolor="#FFFFFF"flashvars="flvurl=http://v6.nonxt2.googlevideo.com/videoplayback?id%3Dbd048a084c673a76%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1332715306%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D3C8A201A833B72C00F522F0D67AE7BD478895065.74E718E81CFE654B509ECCE5D75FD477B69C18F5%26key%3Dck1&amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3Dbd048a084c673a76%26offsetms%3D5000%26itag%3Dw160%26sigh%3DRxIul0xc-4oGSf5Gg0DFYxU7rhM&amp;autoplay=0&amp;ps=blogger"allowFullScreen="true" /&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;&amp;nbsp;And that's it. I have many ideas how to make this process smoother, so stay tuned!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-5653613872607108223?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/5653613872607108223/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/04/how-to-convert-animations-in-khayyam.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5653613872607108223'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5653613872607108223'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/04/how-to-convert-animations-in-khayyam.html' title='How to convert animations in Khayyam'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-Va91wOqG4NU/TZiss8bfiSI/AAAAAAAAFGY/ykWdIS2FSVg/s72-c/chibi-imported.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-5004689056838968842</id><published>2011-04-03T01:46:00.000+03:00</published><updated>2011-04-03T01:46:56.908+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><title type='text'>Snapshot release 20110402</title><content type='html'>A new snapshot release 20110310 is available from &lt;a href="https://sourceforge.net/projects/floyd/files/escher/"&gt;SourceForge page&lt;/a&gt; as source tarball and precompiled Win32 binary.&lt;br /&gt;&lt;br /&gt;The most important new feature is refactored biped pose/animation converter:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Both animations (from BVH files) and static poses (from other objects) can be converted&lt;/li&gt;&lt;li&gt;Animations can be saved to sequences (and exported to BVH again)&lt;/li&gt;&lt;li&gt;Almost all body parts of biped are adjusted (body, limbs, head, fingers)&lt;/li&gt;&lt;li&gt;IK solver parameters can be adjusted&lt;/li&gt;&lt;li&gt;Bone definitions can be set by user (and saved for future use)&lt;/li&gt;&lt;/ul&gt;Some problems remain - collar bones do not move, you cannot set T-pose and so on.&lt;br /&gt;The better the constraint system of the destination figure, the better pose matching will be. At minimum you should set knee and elbow constraints to avoid wrong bendings.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;object width="320" height="266" class="BLOG_video_class" id="BLOG_video-8564b472ce144edb" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"&gt;&lt;param name="movie" value="http://www.youtube.com/get_player"&gt;&lt;param name="bgcolor" value="#FFFFFF"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="flashvars" value="flvurl=http://v20.nonxt6.googlevideo.com/videoplayback?id%3D8564b472ce144edb%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1332715306%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D3706496670D6868ABCBE3CAB5834E6FD5DF22825.32621270357B2F66EE6570262D13F98BB0538CFE%26key%3Dck1&amp;amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3D8564b472ce144edb%26offsetms%3D5000%26itag%3Dw160%26sigh%3D1FHJF5SaaT92Mtz1R1c3fSD6c7o&amp;amp;autoplay=0&amp;amp;ps=blogger"&gt;&lt;embed src="http://www.youtube.com/get_player" type="application/x-shockwave-flash"width="320" height="266" bgcolor="#FFFFFF"flashvars="flvurl=http://v20.nonxt6.googlevideo.com/videoplayback?id%3D8564b472ce144edb%26itag%3D5%26app%3Dblogger%26ip%3D0.0.0.0%26ipbits%3D0%26expire%3D1332715306%26sparams%3Did,itag,ip,ipbits,expire%26signature%3D3706496670D6868ABCBE3CAB5834E6FD5DF22825.32621270357B2F66EE6570262D13F98BB0538CFE%26key%3Dck1&amp;iurl=http://video.google.com/ThumbnailServer2?app%3Dblogger%26contentid%3D8564b472ce144edb%26offsetms%3D5000%26itag%3Dw160%26sigh%3D1FHJF5SaaT92Mtz1R1c3fSD6c7o&amp;autoplay=0&amp;ps=blogger"allowFullScreen="true" /&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;A demonstration video of making Sara-chan to run. Red armature is the original BVH animation file, blue is the Sara skeleton. It is not ideal, because the BVH does not have finger poses.&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-5004689056838968842?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/5004689056838968842/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/04/snapshot-release-20110402.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5004689056838968842'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5004689056838968842'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/04/snapshot-release-20110402.html' title='Snapshot release 20110402'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-448281312997307857</id><published>2011-03-26T12:56:00.001+02:00</published><updated>2011-03-26T22:56:18.709+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='importing'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Converting animations between skeletons</title><content type='html'>I have written one more piece about the skeleton merge about half year ago - you can read it &lt;a href="http://khayyam.kaplinski.com/2010/11/how-to-merge-animations.html"&gt;here&lt;/a&gt;. What follows is the a much better version of the original algoritm.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;The problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Say you have some good animations (there are many free BVH sites in web) and want to use these for your own models. Unfortunately - unless you made your models using exactly the same skeleton layout, the bones do not match and copying animations is not trivial problem.&lt;br /&gt;&lt;br /&gt;There are many ways, how skeletons may differ:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Bone names do not match. This is easy to overcome - either build mapping or rename bones.&lt;/li&gt;&lt;li&gt;Rest pose orientations use different systems (like XYZ euler angles vs. ZXY euler angles vs. quaternions). This is also minor problem, as normally you want to convert everything to quaternions or matrices anyways.&lt;/li&gt;&lt;li&gt;Bone lengths do not match. Thus copying animation directly can make movement unnatural.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Rest pose bone positions (directions) do not match. Thus animations can not be immediately copied between skeletons, but have to be transformed to absolute space (object space) before.&lt;/li&gt;&lt;li&gt;Skeleton topologies do not match. This is often a showstopper, because if you do not even have similar bones, there is no way to copy animations.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-Mla4RwOf0qk/TY26BH3nD8I/AAAAAAAAE_4/KIY8KKH1MUM/s1600/skeleton-topology.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="https://lh5.googleusercontent.com/-Mla4RwOf0qk/TY26BH3nD8I/AAAAAAAAE_4/KIY8KKH1MUM/s320/skeleton-topology.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Example of different common skeleton topologies&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The topology mismatch is the hardest problem to overcome. In the example above, if you, say have animation for the right skeleton, the 4 parts of backbone should move the 2 backbone parts and pelvis of the left skeleton. While in theory it should be possible to solve this problem analytically, it is not easy - and has to be done by hand for each combination of skeletons.&lt;br /&gt;Instead of that, using IK solvers gives us easier way to map those skeletons. It is still not universal - the mapping algorithms have to be adjusted to object itself - in current case a humanoid. But normally this is not big problem, because in 99% cases humanoid is precisely what you want to animate.&lt;br /&gt;&lt;br /&gt;In any case you need good IK solver, that can do at least 6-7 bones long chains, adjust both positions and rotations and use both absolute positions and bone directions for targets. Also, the solver has to support comparing up to 4 target points or directions simultaneously. I am currently using &lt;a href="http://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method"&gt;simplex method&lt;/a&gt; in Khayyam (the code is in &lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;helpers/ik.cpp&lt;/span&gt;), but there are other options as well.&lt;br /&gt;Also the skeleton should have bone constraints defined - otherwise you may discover that elbows or knees will be bent to completely wrong direction.&lt;br /&gt;&lt;br /&gt;We start implementing the algorithm by a small observation. Namely - while there are different ways to construct skeletons, the joints have to be still placed to precise spots - otherwise the movement of joints will not be natural. Thus we can use the joints as anchor spots for the algorithm.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;The body&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The first and hardest part is moving the main bodies of skeletons to the same position. First - the root bones may be arranged differently. Second - root bones may be joined with body either below or above pelvis. Third - there may be different number of backbone elements. Fourth - there may or may not be extra bones, like hips or collar bones.&lt;br /&gt;To solve this problem, we can do the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Set 4 IK target points to the corner joints of body - both hips and both shoulders.&lt;/li&gt;&lt;li&gt;Scale the source skeleton so, that the distances between hips and shoulders are approximately the same, as in target skeleton.&lt;/li&gt;&lt;li&gt;Find the topmost common parents of both hips and shoulders Ah and As.&lt;/li&gt;&lt;li&gt;Find the topmost common parent of Ah and As B.&lt;/li&gt;&lt;li&gt;Build rotating IK chain of bones from &amp;nbsp;B to Ah and from B to As, not including B.&lt;/li&gt;&lt;li&gt;Add both the rotations and translations of the rootmost bone R to chain.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-9sexj_tRvvc/TY26JOqnAYI/AAAAAAAAE_8/m7kXPg6bnhI/s1600/skeleton-body.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="https://lh5.googleusercontent.com/-9sexj_tRvvc/TY26JOqnAYI/AAAAAAAAE_8/m7kXPg6bnhI/s320/skeleton-body.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The schematics of body IK solver (green: translate + rotate, red: rotate, blue: not moving)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;After solving this IK chain, the corner joins of body will be approximately aligned to the original. Normally you do not achieve perfect alignment, because the width of shoulders and hips is different in skeletons - but this is not big problem, because the direction of hip-hip and shoulder-shoulder lines should be aligned.&lt;br /&gt;It is important to do this alignment in one step - otherwise the alignment of hips may involve extensive rotations and translations of root bone R - and you cannot later align shoulders at all.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Limbs&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To align limbs, there are two strategies - depending on what we want to achieve.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;We can align the endpoints - if we want to model some specific action - like taking some item from table. In thus case the hand will be in correct place - but because the lengths of arms and forearm probably differ, elbow will be off.&lt;/li&gt;&lt;li&gt;Or we can align the directions of bones - for example if modeling hand movement during walking - in that case both elbow and wrist will be off, but the overall look of hand will be more similar.&lt;/li&gt;&lt;/ul&gt;At the first case it is just ordinary single-target IK solver. Or if you want to keep the elbow/knee direction more similar to original, there should be additional directional target at midpoint.&lt;br /&gt;At the second case, the IK chain has to be solved with two directional targets (&lt;b&gt;shoulder-elbow&lt;/b&gt; + &lt;b&gt;elbow-wrist&lt;/b&gt; or &lt;b&gt;hip-knee&lt;/b&gt; + &lt;b&gt;knee-ankle&lt;/b&gt;) simultaneously. Otherwise the topmost bone may turn it to such direction, that 1 DOF joint at midpoint (elbow or knee) cannot move other bone to the right direction.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Palm&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There are probably different strategies for adjusting palms. In khayyam I did the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Set 4 directional targets from wrist to the topmost joints of fingers&lt;/li&gt;&lt;li&gt;Add the 3 DOF rotations of palm to IK chain&lt;/li&gt;&lt;li&gt;If forearm includes extra rotational bone (like at the skeleton at left), include the axial rotation of it to chain&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;By solving this chain, the positions of finger joints will be approximately aligned to the original.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Fingers&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Fingers can be aligned in the same way as limbs - but here we need three targets instead of two. Also, as the fingertips are normally the end sites, the last target does not coincide with any joint, but has to be placed about 2-3 cm from the last joint along the bone direction.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Head&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Head should be placed by adjusting three positional targets - eyes and skulltop. This can become tricky, because many skeletons may not include eye positions at all.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-7jVA0P0E8qU/TY3A6eZYyHI/AAAAAAAAFAE/LNu3_oXvk0g/s1600/sara-dance-adjusted.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="316" src="https://lh3.googleusercontent.com/-7jVA0P0E8qU/TY3A6eZYyHI/AAAAAAAAFAE/LNu3_oXvk0g/s400/sara-dance-adjusted.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara chan in skeleton merge editor (blue: figure skeleton, red: animation skeleton)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;At the above picture you can see the results of adjustment of Sara-chan skeleton to Illusion Characolle animated skeleton. Although the skeleton topologies and bone lengths differ, the pose is pretty close to the original.&lt;br /&gt;&lt;br /&gt;At moment the skeleton merge works only in &lt;a href="http://sourceforge.net/projects/floyd/"&gt;CVS version&lt;/a&gt; of Khayyam, but expect it in the next release.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-448281312997307857?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/448281312997307857/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/03/converting-animations-between-skeletons.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/448281312997307857'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/448281312997307857'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/03/converting-animations-between-skeletons.html' title='Converting animations between skeletons'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-Mla4RwOf0qk/TY26BH3nD8I/AAAAAAAAE_4/KIY8KKH1MUM/s72-c/skeleton-topology.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-6148836402380583330</id><published>2011-03-16T10:23:00.000+02:00</published><updated>2011-03-16T10:23:10.926+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Khayyam architecture: libthera</title><content type='html'>For a quick overview of all Khayyam subcomponents, look at the &lt;a href="http://khayyam.kaplinski.com/2010/10/khayyam-architecture.html"&gt;introduction&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Overview&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Libthera is the backbone of Khayyam document system. It is very simple DOM-like tree of XML nodes, implementing file IO, transactions (think undo and redo) and property, tree and content mutation events.&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-fTPF24nPpmc/TX_FlNGOr4I/AAAAAAAAE_M/ggR8EpOj1V0/s1600/thera.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="207" src="https://lh6.googleusercontent.com/-fTPF24nPpmc/TX_FlNGOr4I/AAAAAAAAE_M/ggR8EpOj1V0/s400/thera.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The schematic layout of Thera object tree&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;All properties inside libthera tree are simple text strings. Thera nodes do not know anything about the semantics of attributes, content or child nodes. They only serve as the containers of base state for more semantic-aware objects in libmiletos.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Thera::Node&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The basic building block of Thera tree is Thera::Node - an object encapsulating XML node (either element, text or cdata). It has the following properties:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Name (only for element nodes)&amp;nbsp;&lt;/li&gt;&lt;li&gt;Backlink to containing document&amp;nbsp;&lt;/li&gt;&lt;li&gt;One or more attributes (key/value pairs, only for element nodes)&lt;/li&gt;&lt;li&gt;Optional text content&lt;/li&gt;&lt;li&gt;Linked list of children&lt;/li&gt;&lt;li&gt;Linked list of eventlisteners&lt;/li&gt;&lt;/ul&gt;The tree is modified by atomic methods of nodes - content change, attribute change, child insertion, child removal and child reordering. The API is kept as simple as possible - setting attribute to NULL deletes it, using NULL as ref value during child insertion prepends child and so on. The result is &lt;strong&gt;true&lt;/strong&gt; if the action succeeds, &lt;strong&gt;false&lt;/strong&gt; if not.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;bool Node::setAttribute (const char *key, const char *value);&lt;br /&gt;bool Node::setTextContent (const char *newcontent);&lt;br /&gt;bool Node::addChild (Node *child, Node *ref);&lt;br /&gt;bool Node::removeChild (Node *child);&lt;br /&gt;bool Node::relocateChild (Node *child, Node *ref);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;By default all methods above are allowed on all proper nodes (no attribute change or child management on text and cdata nodes). Actions can be vetoed by event listeners. The idea here is, that thera tree is not typed - all element nodes are similar and do not know anything about the semantic meaning of themselves or attributes. To implement semantic checks, another structure has to be added to thera tree, that can interpret the property changes and veto/allow these. In case of Khayyam, that additional layer is &lt;strong&gt;libmiletos&lt;/strong&gt; scene graph library.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Event system&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For each property change (attribute or content change, child insertion and so on) node first invokes query events from linked &lt;strong&gt;EventVector&lt;/strong&gt; list.&lt;br /&gt;&lt;strong&gt;EventVector&lt;/strong&gt; is list of function pointers, which - if not NULL - will be called during mutation events. I did not implement real signalling system for two reasons. First - to keep things simple. Second - normally single &lt;strong&gt;Thera::Node&lt;/strong&gt; is tracked by single &lt;strong&gt;Miletos::Object&lt;/strong&gt; and thus having all listeners in one structure makes perfect sense.&lt;br /&gt;&lt;br /&gt;For example, if one tries to change the attribute of &lt;strong&gt;Thera::Node&lt;/strong&gt;, the following happens:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Old and new attribute are compared. &lt;strong&gt;True&lt;/strong&gt; is returned immediately, if they are identical.&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;span style="color: blue;"&gt;change_attribute&lt;/span&gt;&lt;/b&gt; listeners are called by function pointers from all linked &lt;strong&gt;EventVectors&lt;/strong&gt;. If any of these returns &lt;strong&gt;false&lt;/strong&gt;, attribute is left intact and &lt;strong&gt;false&lt;/strong&gt; returned.&lt;/li&gt;&lt;li&gt;Actual attribute value of &lt;strong&gt;Thera::Node&lt;/strong&gt; is set to new value&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;span style="color: blue;"&gt;attribute_changed&lt;/span&gt;&lt;/b&gt; listeners are called by function pointers from all linked &lt;strong&gt;EventVectors&lt;/strong&gt;. This is post-mutation callback and thus cannot veto the change anymore.&lt;/li&gt;&lt;li&gt;If no &lt;span style="color: blue;"&gt;&lt;strong&gt;attribute_changed&lt;/strong&gt;&lt;/span&gt; event was installed, &lt;b&gt;&lt;span style="color: blue;"&gt;downstream_attribute_changed&lt;/span&gt;&lt;/b&gt; listeners are called from parent node &lt;strong&gt;EventVector&lt;/strong&gt; list.&lt;/li&gt;&lt;li&gt;Containing &lt;strong&gt;Thera::Document&lt;/strong&gt; is notified about attribute change&lt;/li&gt;&lt;/ol&gt;Normally the libmiletos scene graph objects (subclasses of &lt;strong&gt;Miletos::Object&lt;/strong&gt;) listen to &lt;strong&gt;attribute_changed&lt;/strong&gt;, &lt;strong&gt;content_changed&lt;/strong&gt;, &lt;strong&gt;child_inserted&lt;/strong&gt; and other post-mutation events and update their internal state whenever something changes. The exceptions are two special attributes &lt;strong&gt;id&lt;/strong&gt; and &lt;strong&gt;xmlns&lt;/strong&gt;. As each object in scene graph has to have unique id for reference system to work, the uniqueness of the value of &lt;strong&gt;id&lt;/strong&gt; attribute is checked in change_attribute handler and mutation blocked if new value is not unique. All &lt;strong&gt;xmlns&lt;/strong&gt; changes are blocked, as those change the semantics of object, and would require replacing one scene graph node with another one.&lt;br /&gt;&lt;br /&gt;The reason for downstream listeners is to reduce the number of objects, that libmiletos has to implement. For example &lt;strong&gt;&amp;lt;color&amp;gt;&lt;/strong&gt; nodes in Collada tree do not build their of objects (i.e. there is no &lt;strong&gt;Miletos::Collada::Color&lt;/strong&gt; objects) but instead the containing nodes (ambient, diffuse, light nodes and so on) get signalled of color content changes by downstream propagation.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Transactions&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The untyped nature of thera tree makes it ideal place to handle generic editing functions, that have to be consistent for all scene graph nodes - undo, redo, cut, copy, paste, load and save. The invariant in Khayyam document system is, that the properties of &lt;strong&gt;Miletos::Object&lt;/strong&gt; are determined only by the attributes and children of &lt;strong&gt;Thera::Node&lt;/strong&gt;. So restoring the &lt;strong&gt;Thera::Node&lt;/strong&gt; properties to previous value, also restores scene graph to previous state.&lt;br /&gt;&lt;strong&gt;Thera::Node&lt;/strong&gt; has very few properties, so to implement undo and redo, we have to only record 5 different types of mutation events (attribute change, content change, child insertion, child removal and child relocation). This is managed by &lt;strong&gt;Thera::Document&lt;/strong&gt; container.&lt;br /&gt;&lt;br /&gt;For each mutation event, a new record is appended in document undo list.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;For attribute changes keep node location, key and old value&lt;/li&gt;&lt;li&gt;For content changes keep node location and old content&lt;/li&gt;&lt;li&gt;For child insertion keep the location of new node&lt;/li&gt;&lt;li&gt;For child deletion keep the copy of child node and it's previous location&lt;/li&gt;&lt;li&gt;For child relocation keep the old and new locations of child&lt;/li&gt;&lt;/ul&gt;Undo works by rolling back the record, and moving it from the top of undo list to the top of redo list.&lt;br /&gt;&lt;br /&gt;Transaction logging can be turned on and off. Normally it has to be always on for editable document, but in certain cases - like during ensuring unique &lt;strong&gt;id&lt;/strong&gt; attributes to all objects during document creation, it is turned off (&lt;strong&gt;id&lt;/strong&gt; uniqueness is required feature, so this procedure cannot be undone).&lt;br /&gt;In addition to transaction logging, there is transaction collation. If this is turned on, subsequent changes of the same attribute do not create new records, but only update the last record. It is used for tracking continuous numerical attribute changes - like the ones controlled by spinbutton or slider.&lt;br /&gt;&lt;br /&gt;Similarly cut, copy and paste are implemented by storing the clone of copied node(s). During paste, the clone is re-cloned into document tree - and libmiletos objects will be built and updated automatically by event listeners.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Some concerns&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;While the untyped nature of thera tree makes editing functions simple and consistent throughout document, it has one drawback. As objects are often referenced by &lt;strong&gt;id&lt;/strong&gt; values, and &lt;strong&gt;id&lt;/strong&gt; has to be unique in document, copying reference pair does not work as intended. The &lt;strong&gt;id&lt;/strong&gt; of new referee will be changed to some unique value, but the referencing attribute of referer will be not. So the referer will link back to the original object, instead of the new object.&lt;br /&gt;I have some ideas, how to handle it - by keeping a dictionary of &lt;strong&gt;id&lt;/strong&gt; overwrites during libmiletos object building phase, but at moment it has to be adjusted by hand.&lt;br /&gt;&lt;br /&gt;Also, as libthera keeps all values as text, updating parsing these may take more time than is reasonable to spend during interactive editing. To dealt with this, many interactive functions work in two-stage way.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;When input is grabbed (mouse button pressed), the initial state of object is recorded&lt;/li&gt;&lt;li&gt;During editing (normally dragging) libthera is skipped and editing function applied directly to scene graph nodes&lt;/li&gt;&lt;li&gt;When editing is finished, the last state is written to libthera&lt;/li&gt;&lt;li&gt;If editing is canceled, object state is read back from libthera (which has the initial value)&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;Thus interactive functions do not constantly update text attributes, but instead the state of scene nodes directly. As an extra bonus the single editing action (click-drag-release) is recorded as single document transaction, because the libthera values are updated only during release.&lt;br /&gt;&lt;br /&gt;That's all for now. Libthera can be donwloaded as part of khayyam source from &lt;a href="http://sourceforge.net/projects/floyd/"&gt;SourceForge page&lt;/a&gt;. Or the latest version can be fetched from Sodipodi SVN:&lt;br /&gt;&lt;br /&gt;&lt;a href="https://sodipodi.svn.sourceforge.net/svnroot/sodipodi/trunk/thera"&gt;https://sodipodi.svn.sourceforge.net/svnroot/sodipodi/trunk/thera&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-6148836402380583330?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/6148836402380583330/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/03/khayyam-architecture-libthera.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6148836402380583330'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6148836402380583330'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/03/khayyam-architecture-libthera.html' title='Khayyam architecture: libthera'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh6.googleusercontent.com/-fTPF24nPpmc/TX_FlNGOr4I/AAAAAAAAE_M/ggR8EpOj1V0/s72-c/thera.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-557824340864405902</id><published>2011-03-12T20:28:00.000+02:00</published><updated>2011-03-12T20:28:36.896+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='poser'/><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><title type='text'>How to use Poser/DAZ models in Khayyam</title><content type='html'>There is almost endless number of Poser models and props (accessories) available from sites like &lt;a href="http://www.daz3d.com/"&gt;DAZ3D&lt;/a&gt;, &lt;a href="http://www.renderosity.com/"&gt;Renderosity&lt;/a&gt; and &lt;a href="https://www.renderotica.com/"&gt;Renderotica&lt;/a&gt; (adult content). It is possible, although somewhat tricky, to get many of these to work in Khayyam. So I have composed a small tutorial of loading figures, hair, clothes and materials in Khayyam.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Warning&lt;/b&gt;: Khayyam is still in very early stages of development and many models do not work. So better do not purchase expensive models unless you also have Poser/DAZ or know, that you can get them working. But you can always hunt for cheap things, bargains and free stuff (&lt;a href="http://forum.daz3d.com/index.php"&gt;DAZ forums&lt;/a&gt; is a great resource for finding free models). If something does not work, I can usually fix the Khayyam code - but you have to buy given model for me so I can experiment :D&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;1. Loading Cr2 model&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Cr2 (Character Rig) is the skeletally controlled model, that is mostly used for human figures (also animals) and more complex hairstyles.&lt;br /&gt;Select &lt;b&gt;Object -&amp;gt; Import -&amp;gt; Import Poser mesh&lt;/b&gt;&lt;br /&gt;Depending on your library size it may take some time to load all previews. When done, the import dialog will open. If you have not specified Poser library path yet, the library list is empty. Click on Choose Library and select the &lt;b&gt;Runtime&lt;/b&gt; directory of your model installation tree.&lt;br /&gt;Most human figures are under &lt;b&gt;Character&lt;/b&gt; subtree. I use &lt;b&gt;DAZ People -&amp;gt; Victoria 4.2.cr2&lt;/b&gt; as an example for the tutorial.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-jAKDic_PeFo/TXujPRDPegI/AAAAAAAAE9s/KawM2i0CspU/s1600/Dialog-import-poser.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="275" src="https://lh4.googleusercontent.com/-jAKDic_PeFo/TXujPRDPegI/AAAAAAAAE9s/KawM2i0CspU/s400/Dialog-import-poser.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Khayyam Cr2 import dialog with Victoria 4.2 selected&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&amp;nbsp;Click &lt;b&gt;Import&lt;/b&gt; and the the import options dialog will open.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh6.googleusercontent.com/-L-3aNmsHpY8/TXukJF9eD9I/AAAAAAAAE9w/ha03meZeXqI/s1600/Dialog-poser-import-options.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="282" src="https://lh6.googleusercontent.com/-L-3aNmsHpY8/TXukJF9eD9I/AAAAAAAAE9w/ha03meZeXqI/s320/Dialog-poser-import-options.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;If importing main figure you should keep all options at default values, as shown above.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&amp;nbsp;&lt;b&gt;Create skeleton&lt;/b&gt; will build editable skeleton for rigged model. Normally you want to keep this checked.&lt;/li&gt;&lt;li&gt;&amp;nbsp;&lt;b&gt;Import as standalone mesh&lt;/b&gt; will build new toplevel figure. Unless you are adding hair or clothes to existing figure, this should also be checked.&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;Click &lt;b&gt;Import&lt;/b&gt; and the figure will be inserted to main document. You can close the Import Poser dialog or keep it open, as we will use the same dialog later again.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-ULGFh-pUZqw/TWRANsWZ_pI/AAAAAAAAE8Q/N_H-6zAYz_U/s1600/victoria-plain.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="https://lh4.googleusercontent.com/-ULGFh-pUZqw/TWRANsWZ_pI/AAAAAAAAE8Q/N_H-6zAYz_U/s400/victoria-plain.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure imported to main document&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;As you can see, the default look is quite plain. We'll add proper materials soon to make her more attractive.&lt;br /&gt;Now it is time to open the &lt;strong&gt;Document Tree&lt;/strong&gt; dialog, if not already open. Select the figure (click on it, so the selection box turns orange) and choose &lt;strong&gt;Dialogs -&amp;gt; Document Tree&lt;/strong&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-dcw8ApmIUgY/TXum7HrRgqI/AAAAAAAAE94/AsKOvvT5RYU/s1600/Dialog-document-tree.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="https://lh5.googleusercontent.com/-dcw8ApmIUgY/TXum7HrRgqI/AAAAAAAAE94/AsKOvvT5RYU/s400/Dialog-document-tree.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Document Tree dialog with some nodes expanded&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;At the left side is the DOM tree of current document. At the right side are the property pages of the currently selected node. Most fine-tuning of objects is done with property pages.&lt;br /&gt;&lt;br /&gt;All poseable objects have toplevel node &lt;strong&gt;Figure&lt;/strong&gt;. It works as a container of one or more geometries (actual poseable objects), skeleton (a controller of those geometries) and possibly other node types.&lt;br /&gt;Selecting the single geometry under imported figure opens the property page of Cr2 object. Here you can see a tree of actors and associated dials. The base V4 figure has only few morphs, but if you expand the tree and select head actor, you can access the lip sync dials, for example.&lt;br /&gt;&lt;br /&gt;Khayyam build IK Chains automatically from model data - but does not set bone constraints. You can play with IK chains (and FK posing too) by selecting bone edit mode in main window.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;NB!&lt;/b&gt; V4 figure has extra EyeBrow object, that partially covers the face. To turn this off, select the geometry node in document tree, select the middle Geometry property page from the notebook at right, scroll down and turn the &lt;b&gt;visible&lt;/b&gt; checkbox off for &lt;b&gt;1_EyeBrow&lt;/b&gt; material.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;2. Adding clothes&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Clothes have to follow body position, so they are also poseable objects (of type cr2). To import clothing item, keep the main figure selected and open again &lt;strong&gt;Import Poser Dialog&lt;/strong&gt; (or bring it up, if you did not close it). Select proper clothing item (it should appear in preview, if Khayyam can load it) and click &lt;strong&gt;Import&lt;/strong&gt;. Unfortunately the free V4 base does not include clothes, so you have to buy these yourself or search for free ones. I am using free &lt;a href="http://www.bucketload3d.com/3dmodels.html"&gt;Bucketload 3D&lt;/a&gt; Sunny Bikini for current tutorial.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-DJ1Ck8ZC7lY/TXusLUV75OI/AAAAAAAAE-A/cTOUNE_ZKNw/s1600/Dialog-poser-import-options-2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="282" src="https://lh4.googleusercontent.com/-DJ1Ck8ZC7lY/TXusLUV75OI/AAAAAAAAE-A/cTOUNE_ZKNw/s320/Dialog-poser-import-options-2.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;If you had the figure selected, the import options dialog has a bit different set of controls selected. Normally you want to keep the preselected values.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&amp;nbsp;&lt;b&gt;Create skeleton&lt;/b&gt; is unselected, because the figure already has skeleton.&lt;/li&gt;&lt;li&gt;&amp;nbsp;&lt;b&gt;Skin of selected mesh&lt;/b&gt; - it means, that it will be fully controlled by main figure skeleton.&lt;/li&gt;&lt;li&gt;&amp;nbsp;Parent bone and anchor bone are set to the lowest common bone (actor) in the hierarchies of both objects.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Click &lt;b&gt;Import &lt;/b&gt;and clothing item should be added to the figure.&lt;br /&gt;NB! If the clothing item is built for a different base figure (V3, A3...) it is quite possible, that it does not "fit". Sometimes you can adjust it with morphs, but often this does not help. You can remove wrong objects by selecting corresponding geometries in document tree and clicking on trashcan image.&lt;br /&gt;Repeat clothing import for as many items as you need (well - you do not need any, but to keep this tutorial work-safe, I'll add at least bra and panties).&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-6JSHWFpphuw/TXuu8-PAhOI/AAAAAAAAE-I/hK92uLrYRE8/s1600/Khayyam-V4-bikini.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="https://lh3.googleusercontent.com/-6JSHWFpphuw/TXuu8-PAhOI/AAAAAAAAE-I/hK92uLrYRE8/s400/Khayyam-V4-bikini.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;V4 figure with bikinis loaded&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;As you can see, both the main figure and bikini are plain. We'll set material as the next step.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;3. Loading materials&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Predefined materials are either Pose files (pz2) or Material definition files (mc6). It is also possible to set materials by specifying texture files directly, but I'll skip this at moment (the procedure is the same, as in &lt;a href="http://khayyam.kaplinski.com/2010/12/importing-sexy-beach-zero-figures.html"&gt;SBZ tutorial&lt;/a&gt;).&lt;br /&gt;Materials are applied to geometry nodes - not the full figure. So in &lt;strong&gt;Document Tree Dialog&lt;/strong&gt;, select the body geometry. On the property page, click the button &lt;b&gt;Load Material...&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;br /&gt;Materials are usually either under the Pose (older version) or Material (newer) subsection of library tree. You have to select correct material type - the ones for wrong object (clothes vs. body) do not work.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-6tQQx9lHyrs/TXuya4PhL7I/AAAAAAAAE-Q/33ECaMadJnI/s1600/Library-tree-material.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="https://lh4.googleusercontent.com/-6tQQx9lHyrs/TXuya4PhL7I/AAAAAAAAE-Q/33ECaMadJnI/s400/Library-tree-material.jpg" width="292" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Library tree with material file selected&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Click &lt;b&gt;OK&lt;/b&gt;, and material will be imported. Usually the textures are huge, so depending on the speed of you computer and GPU it may take some time.&lt;br /&gt;Material definitions are added as Pose nodes under Geometry nodes. There can be many material definitions - they are parsed from bottom to top - so it is, for example, possible to add makeup on top of body materials. To delete material definition, simply delete corresponding Pose node.&lt;br /&gt;Repeat the same procedure for clothes - first select clothing geometry node and then click &lt;b&gt;Load Material...&lt;/b&gt;&amp;nbsp; (of course you have to select appropriate material file for clothing).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;4. Adding hair&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There are 2 types of hair - poseable (cr2) and prop (hr2).&lt;br /&gt;Adding cr2 hair is exactly the same as adding clothes - select main figure, choose hair object in &lt;strong&gt;Import Poser Dialog&lt;/strong&gt; and import it as skin of selected mesh. Apply material just like for clothes.&lt;br /&gt;Hr2 meshes do not have skeleton, thus they are imported as Poseable elements. Usually short hair is hr2, as it simply follows the movement of the head and does care about the neck and shoulders. But this will be shown in the next tutorial.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-xDwv8ff8BNI/TXu2JHZrYrI/AAAAAAAAE-Y/A2x-0YqqE5U/s1600/Khayyam-V4-bikini-hair.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="https://lh3.googleusercontent.com/-xDwv8ff8BNI/TXu2JHZrYrI/AAAAAAAAE-Y/A2x-0YqqE5U/s400/Khayyam-V4-bikini-hair.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;V4, bikinis and hair with materials applied&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;And this is it.&lt;br /&gt;The next time I'll show, how to load poses, inject morphs and import Hr2 hair. Or you can try this yourself - it is not complicated at all, once you find the right place in document tree :D&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-557824340864405902?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/557824340864405902/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/03/how-to-use-poserdaz-models-in-khayyam.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/557824340864405902'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/557824340864405902'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/03/how-to-use-poserdaz-models-in-khayyam.html' title='How to use Poser/DAZ models in Khayyam'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-jAKDic_PeFo/TXujPRDPegI/AAAAAAAAE9s/KawM2i0CspU/s72-c/Dialog-import-poser.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-5504360344002414776</id><published>2011-03-10T16:52:00.000+02:00</published><updated>2011-03-10T16:52:04.482+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><title type='text'>Snapshot release 20110310</title><content type='html'>A new snapshot release 20110310 is available from &lt;a href="https://sourceforge.net/projects/floyd/"&gt;SourceForge page&lt;/a&gt; as source tarball and precompiled Win32 binary.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Fixed PNG export gamma so Pov-Ray rendered images have similar tone to screen&lt;/li&gt;&lt;li&gt;Directional lights now cast shadows&lt;/li&gt;&lt;li&gt;Fixed Poser cr2 joint order and pose loading&lt;/li&gt;&lt;li&gt;Support for Yuusha models&lt;/li&gt;&lt;li&gt;BVH export allows one to manually specify root bone&lt;/li&gt;&lt;/ul&gt;Also Sara-chan (included as demo model) got a little update - better face topology, cuter and more realistic mouth and thicker hair.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-Tna6U213gSE/TXji5b-5SXI/AAAAAAAAE9c/xR46SSo108U/s1600/sara_1_1.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="400" src="https://lh3.googleusercontent.com/-Tna6U213gSE/TXji5b-5SXI/AAAAAAAAE9c/xR46SSo108U/s400/sara_1_1.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara Tween Girl version 1.1 (Blender render)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Included model has IK chains and constraints, so it can be used as a reference, how to set up these for other figures. Until I'll get time to write tutorial about it.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-XFew-7aP6w8/TXjkthCYtvI/AAAAAAAAE9k/3RvBkSGuTUs/s1600/sara_1_1_blender.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="292" src="https://lh3.googleusercontent.com/-XFew-7aP6w8/TXjkthCYtvI/AAAAAAAAE9k/3RvBkSGuTUs/s400/sara_1_1_blender.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A new face topology of Sara-chan in Blender&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-5504360344002414776?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/5504360344002414776/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/03/snapshot-release-20110310.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5504360344002414776'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5504360344002414776'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/03/snapshot-release-20110310.html' title='Snapshot release 20110310'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh3.googleusercontent.com/-Tna6U213gSE/TXji5b-5SXI/AAAAAAAAE9c/xR46SSo108U/s72-c/sara_1_1.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-7115916947902760078</id><published>2011-03-01T22:56:00.000+02:00</published><updated>2011-03-01T22:56:13.635+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='poser'/><category scheme='http://www.blogger.com/atom/ns#' term='daz'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><title type='text'>Where to find free 3D models?</title><content type='html'>Even if it works for you, Khayyam is only as good as the 3D models you can insert into your scene. Thus I put together a small list of useful and/or interesting sites where you can find free or cheap 3D models. It does not pretend to be some sort of top 8 - there are many-many more websites, so you are free to suggest more.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-_cQNJXN9RzM/TW1ZStOnS0I/AAAAAAAAE9U/ZAn741l32Ng/s1600/amazon-fish.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="https://lh3.googleusercontent.com/-_cQNJXN9RzM/TW1ZStOnS0I/AAAAAAAAE9U/ZAn741l32Ng/s400/amazon-fish.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A discus and neon tetras from Toucan Virtual Museum, rendered with POV-Ray&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The places to go for models (in no particular order).&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;1. &lt;a href="http://www.turbosquid.com/"&gt;TurboSquid&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;This is probably the biggest resource of user-submitted 3D models in Internet, ranging from free or cheap class works to very professional and expensive characters and architectural models.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;2. &lt;a href="http://www.daz3d.com/"&gt;DAZ 3D&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;DAZ is the creator of both the professional 3D applications and the base human characters for Poser/DAZ Studio (Victoria, Michael and various derivatives). They have online store with lots of high quality models, some of them available for free (require registration still), including the starter versions of most of their human characters.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;3. &lt;a href="http://forum.daz3d.com/index.php"&gt;DAZ 3D forums&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;They have special sections for advertising free models. From there you can find links to some free hair and clothes to complement your figures.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;4. &lt;a href="http://www.renderosity.com/"&gt;Renderosity&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;For many this is The Internet Marketplace for 3D art. Huge amount of models for Poser/DAZ including many free ones. The prices seem to be a bit cheaper than in DAZ store. Registration required.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;5. &lt;a href="http://www.toucan.co.jp/indexE.html"&gt;Toucan Virtual Museum&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;A small site that has some very nice free fish, flower and insect models, that are hard to find otherwise.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;6. &lt;a href="http://archive3d.net/"&gt;Archive 3D&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;More than 20 000 architectural models (mostly furniture) available fro download. No registration required.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;7. &lt;a href="http://sketchup.google.com/3dwarehouse/"&gt;Google 3D Warehouse&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;The companion site to Google SketchUp free modeling program. Big and growing selection of free user-created models.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;8 &lt;a href="https://www.renderotica.com/"&gt;Renderotica&lt;/a&gt; (Warning: Adult Content)&lt;/strong&gt;&lt;br /&gt;A must be site, if you want to render some steamy action. No free models, but otherwise good selection of characters, poses and accessories.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I have not tested them all, so I cannot tell, how much Khayyam compatible model formats are there.&lt;br /&gt;&lt;br /&gt;Have fun!&amp;nbsp;And do not forget to support the creators by purchase, donation or advertisement, if you find their models useful for you!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-7115916947902760078?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/7115916947902760078/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/03/where-to-find-free-3d-models.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/7115916947902760078'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/7115916947902760078'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/03/where-to-find-free-3d-models.html' title='Where to find free 3D models?'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh3.googleusercontent.com/-_cQNJXN9RzM/TW1ZStOnS0I/AAAAAAAAE9U/ZAn741l32Ng/s72-c/amazon-fish.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-8503471565805048195</id><published>2011-02-27T02:34:00.000+02:00</published><updated>2011-02-27T02:34:35.769+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='poser'/><category scheme='http://www.blogger.com/atom/ns#' term='morphing'/><category scheme='http://www.blogger.com/atom/ns#' term='daz'/><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><title type='text'>The Girl and full body morphs</title><content type='html'>Have spent last days by writing code for morph injection. While Khayyam has supported both standalone and ERC morphs for some time, they had until now to be specified in the main cr2 file.&lt;br /&gt;&lt;br /&gt;ERC stands for "Enhanced Remote Control" and it is a feature of Poser/DAZ Studio to chain morphs (actually arbitrary channels) together, so changing the value of master dial propagates to different body parts by changing their channel values. Thus, for example, applying FBM (Full Body Morph) of The Girl, the master dial applies corresponding morph to head and at the same time moves eyes to slightly up and wider. Plus many-many things to other body parts.&lt;br /&gt;&lt;br /&gt;The ERC feature is used extensively in DAZ version 4 figures (Victoria, Michael, Aiko, The Girl). Actually Aiko and The Girl do not have separate bodies at all anymore, but are defined as FBMs of Victoria. Most recent clothes have conforming morphs as well, but unlike the morphs of base figures they may be needed to be loaded manually from separate file. Thus the urgent need for Khayyam for applying extra morph files to objects (by poser slang it is called injection).&lt;br /&gt;&lt;br /&gt;The result can be seen at the following screenshot:&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-12SjmOkfFYw/TWmXMSM4ueI/AAAAAAAAE80/SnXCOynOZBY/s1600/thegirl-beach.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="335" src="https://lh3.googleusercontent.com/-12SjmOkfFYw/TWmXMSM4ueI/AAAAAAAAE80/SnXCOynOZBY/s400/thegirl-beach.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The Girl at the beach&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Here you can see &lt;a href="http://www.daz3d.com/i/shop/itemdetails/?item=8390"&gt;The Girl&lt;/a&gt; (&lt;a href="http://www.daz3d.com/i/shop/itemdetails/?item=4783"&gt;Victoria 4&lt;/a&gt; morph, so the body is actually the same as on previous Victoria pictures). Material is the original DAZ Studio "The Girl" texture set. Swimwear is &lt;a href="Bucketload SunnyBikini"&gt;Bucketload Sunny Bikini set&lt;/a&gt;, morphed to The Girl body. Background is beach scene from Sexy Beach 3.&lt;br /&gt;&lt;br /&gt;Not everything works yet - for example The Girl FBM changes the sizes of some body parts like palms, and scaling is not implemented properly in Khayyam. To get the correct scaled hands, skeleton has to be modified (to move fingers along with scaled palm). Plus SmoothScale channel is not used, so the scaling boundaries are sharp.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-8503471565805048195?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/8503471565805048195/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/girl-and-full-body-morphs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8503471565805048195'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8503471565805048195'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/girl-and-full-body-morphs.html' title='The Girl and full body morphs'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh3.googleusercontent.com/-12SjmOkfFYw/TWmXMSM4ueI/AAAAAAAAE80/SnXCOynOZBY/s72-c/thegirl-beach.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-8361279109780130833</id><published>2011-02-23T13:21:00.000+02:00</published><updated>2011-02-23T13:21:31.502+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><title type='text'>New snapshot release 20110222</title><content type='html'>A new snapshot release 20110214 is available from &lt;a href="https://sourceforge.net/projects/floyd/"&gt;Sourceforge page&lt;/a&gt; as both source tarball and precompiled Win32 binary.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Fixed Poser deformation spheres so there are no "vertices going wild" anymore&lt;/li&gt;&lt;li&gt;Materials can be imported as full sets from PZ2 files&lt;/li&gt;&lt;li&gt;Poses can be applied to skeleton prom PZ2 files&lt;/li&gt;&lt;li&gt;Transparency channel is applied automatically to Poser materials&lt;/li&gt;&lt;li&gt;A new code for figure welding. As a result Chibibel is not broken anymore, but most body morphs do not work properly&lt;/li&gt;&lt;/ul&gt;And an obligatory screenshot - this time Chibibel was exercising in morning forest - but she is a bit worried by all you voyeurs.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-H71l6Eq3Tuk/TWTrxo-SQQI/AAAAAAAAE8s/DOgULNJimtw/s1600/chibibel-forest.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://3.bp.blogspot.com/-H71l6Eq3Tuk/TWTrxo-SQQI/AAAAAAAAE8s/DOgULNJimtw/s400/chibibel-forest.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Chibibel in forest demonstrating imported Pz2 pose&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Many things are still missing, but overall the program is taking shape nicely. So give it a try, if you have some unused models lying around and let me know about your mileage.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-8361279109780130833?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/8361279109780130833/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/new-snapshot-release-20110222.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8361279109780130833'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8361279109780130833'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/new-snapshot-release-20110222.html' title='New snapshot release 20110222'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-H71l6Eq3Tuk/TWTrxo-SQQI/AAAAAAAAE8s/DOgULNJimtw/s72-c/chibibel-forest.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-4245547504311752091</id><published>2011-02-23T01:21:00.000+02:00</published><updated>2011-02-23T01:21:09.100+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='poser'/><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><title type='text'>Poses, poses, poses</title><content type='html'>Poser file formats are wonderful (or terrible) patchwork of figures and properties, endlessly extensible through a procedure called pose or morph injection. Basically you will take base figure (or extended one), add custom morphs, poses, materials and accessories (props), call it with fancy name and sell the extensions separately. As a result there is big and lively ecosystem of modelers and artists making living by selling virtual things for virtual worlds.&lt;br /&gt;&lt;br /&gt;I am currently halfway through the implementation of those extension mechanisms for Khayyam. The most important ones - materials and poses mostly work now in CVS version and the results are really worth seeing.&lt;br /&gt;&lt;br /&gt;Normally you start from plain base figure. As an example I use here the most famous one - &lt;a href="http://www.daz3d.com/i/shop/itemdetails/?item=4783"&gt;DAZ Victoria 4&lt;/a&gt; freely available from &lt;a href="http://www.daz3d.com/i/3d_models/"&gt;DAZ webpage&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-ULGFh-pUZqw/TWRANsWZ_pI/AAAAAAAAE8Q/N_H-6zAYz_U/s1600/victoria-plain.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://2.bp.blogspot.com/-ULGFh-pUZqw/TWRANsWZ_pI/AAAAAAAAE8Q/N_H-6zAYz_U/s400/victoria-plain.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Victoria 4 base figure&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Not very pretty - right?&lt;br /&gt;Next let's load a predefined pose to make it a bit more lifelike.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-u-y-WwN3Rs4/TWRAjkCr1VI/AAAAAAAAE8U/Aj08fl-qRLU/s1600/victoria-pose.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://3.bp.blogspot.com/-u-y-WwN3Rs4/TWRAjkCr1VI/AAAAAAAAE8U/Aj08fl-qRLU/s400/victoria-pose.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Victoria 4 posed&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;A bit better, but still ugly.&lt;br /&gt;Now we import custom materials.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-U_T1EOmleVo/TWRBBjyi2lI/AAAAAAAAE8Y/ali10vboX4c/s1600/victoria-material.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://1.bp.blogspot.com/-U_T1EOmleVo/TWRBBjyi2lI/AAAAAAAAE8Y/ali10vboX4c/s400/victoria-material.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Materials, swimwear and hair&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Most texture sets are a bit ... realistic, so I added swimwear as well. Also imported hair. Imported objects are usually immediately skinnable - i.e. they follow the figure pose automatically, as you can see from swimwear.&lt;br /&gt;There is more Khayyam can already do - like apply morphs.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-3cCyxhzn5V0/TWRBz8nN8bI/AAAAAAAAE8c/VXqpyurXrPg/s1600/victoria-closeup.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://1.bp.blogspot.com/-3cCyxhzn5V0/TWRBz8nN8bI/AAAAAAAAE8c/VXqpyurXrPg/s400/victoria-closeup.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Closeup of face with eyes targeted and mouth morphed&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Materials can be applied one on top of another - like eye color or makeup separately from main body texture. Transparent hair is a bit messed up in Khayyam, but will render properly if exported.&lt;br /&gt;And last a quick render of the same figure with POV-Ray (different hair material and pose):&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-GAj7gre0uYs/TWRCdyHjc1I/AAAAAAAAE8g/f-vfkZz9VFQ/s1600/victoria-beach.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://2.bp.blogspot.com/-GAj7gre0uYs/TWRCdyHjc1I/AAAAAAAAE8g/f-vfkZz9VFQ/s400/victoria-beach.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Victoria 4 at beach - renderd with POV-Ray&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;That's all for today. Expect a release soon ;-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-4245547504311752091?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/4245547504311752091/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/poses-poses-poses.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/4245547504311752091'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/4245547504311752091'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/poses-poses-poses.html' title='Poses, poses, poses'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-ULGFh-pUZqw/TWRANsWZ_pI/AAAAAAAAE8Q/N_H-6zAYz_U/s72-c/victoria-plain.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-8670206311578663377</id><published>2011-02-18T00:22:00.002+02:00</published><updated>2011-06-13T02:50:28.960+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='elea'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='mathematics'/><category scheme='http://www.blogger.com/atom/ns#' term='geometry'/><title type='text'>Converting rotation matrix to arbitrarily ordered Euler angles</title><content type='html'>&lt;b&gt;There is &lt;a href="http://khayyam.kaplinski.com/2011/06/more-about-matrix-to-euler-angles.html"&gt;enhanced version of this algorithm available in new post&lt;/a&gt;.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://www.j3d.org/matrix_faq/matrfaq_latest.html"&gt;Matrix and Quaternion FAQ&lt;/a&gt; presents the straightforward method of converting rotation matrix to Euler angles. Still, it has two shortcomings.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;There is discontinuity close to Z rotation of 90 degrees, where X and Y rotations suddenly jump from close to zero to close to 180 degrees.&lt;/li&gt;&lt;li&gt;It only shows the way to get angles in XYZ order (i.e. M = Mx * My * Mz).&lt;/li&gt;&lt;/ul&gt;Arbitrarily ordered angles are extremely useful for skeletal animation to give joints more natural deformations. But the main motivation was actually writing libmiletos container for Poser figures (cr2 file format). These have always bone rotations in twist/bend/bend order, while twist is defined as the rotation of the main axis of the bone. And as Poser figures have different bending algorithms for different rotations, the angles cannot be easily reordered.&lt;br /&gt;So I have written slightly enhanced version for libelea.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Stable decomposition of matrix to Euler angles&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First the original code.&lt;br /&gt;It is derived from expanded form of rotation matrix:&lt;br /&gt;&lt;pre&gt;|  CE      -CF       D   0 |&lt;br /&gt;M = |  BDE+AF  -BDF+AE  -BC  0 |&lt;br /&gt;    | -ADE+BF   ADF+BE   AC  0 |&lt;br /&gt;    |  0        0        0   1 |&lt;br /&gt;&lt;br /&gt;A,B are the cosine and sine of the X-axis rotation axis,&lt;br /&gt;C,D are the cosine and sine of the Y-axis rotation axis,&lt;br /&gt;E,F are the cosine and sine of the Z-axis rotation axis.&lt;br /&gt;&lt;/pre&gt;From it the following algorithm can be derived (using row-major matrix layout, so it has to be transposed for column-major OpenGL matrices):&lt;br /&gt;&lt;pre&gt;angleY = asin (m[2]);&lt;br /&gt;C =  cos (angleY);&lt;br /&gt;if (fabs (C) &amp;gt; 0.005) {&lt;br /&gt;    trx    =  m[10] / C;&lt;br /&gt;    try    = -m[6]  / C;&lt;br /&gt;    angleX = atan2 (try, trx);&lt;br /&gt;    trx    =  m[0] / C;&lt;br /&gt;    try    = -m[1] / C;&lt;br /&gt;    angleZ = atan2 (try, trx);&lt;br /&gt;} else {&lt;br /&gt;    angleX = 0;&lt;br /&gt;    trx    =  m[5];&lt;br /&gt;    try    =  m[4];&lt;br /&gt;    angleZ = atan2 (try, trx);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;The problem is that when angleY goes from slightly below 90 degrees to slightly above 90 degrees, C changes sign and suddenly angles X and Y jump to different quadrant.&lt;br /&gt;&lt;br /&gt;The enhanced version from libelea (also written for row-major order matrices):&lt;br /&gt;&lt;pre&gt;aY = asin (m[2]);&lt;br /&gt;aY2 = M_PI_F - aY;&lt;br /&gt;if (fabs (m[2]) != 1) {&lt;br /&gt;    C = cos (aY);&lt;br /&gt;    C2 = cos (aY2);&lt;br /&gt;    trX = m[10] / C;&lt;br /&gt;    trY = -m[6] / C;&lt;br /&gt;    aX = atan2 (trY, trX);&lt;br /&gt;    aX2 = atan2 (-m[6] / C2, m[10] / C2);&lt;br /&gt;    trX = m[0] / C;&lt;br /&gt;    trY = -m[1] / C;&lt;br /&gt;    aZ = atan2 (trY, trX);&lt;br /&gt;    aZ2 = atan2 (-m[1] / C2, m[0] / C2);&lt;br /&gt;} else {&lt;br /&gt;    aX = aX2 = 0;&lt;br /&gt;    trX = m[5];&lt;br /&gt;    trY = m[4];&lt;br /&gt;    aZ = aZ2 = atan2 (trY, trX);&lt;br /&gt;}&lt;br /&gt;if ((aX * aX + aY * aY + aZ * aZ) &amp;lt; (aX2 * aX2 + aY2 * aY2 + aZ2 * aZ2)) {&lt;br /&gt;    angleX = aX;&lt;br /&gt;    angleY = aY;&lt;br /&gt;    angleZ = aZ;&lt;br /&gt;} else {&lt;br /&gt;    angleX = aX2;&lt;br /&gt;    angleY = aY2;&lt;br /&gt;    angleZ = aZ2;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;The only trick here is to calculate both Y angles satisfying the formula &lt;br /&gt;&lt;pre&gt;sin (aY) = c[2]&lt;br /&gt;&lt;/pre&gt;and subsequently two versions of other angles too. Then the actual set of angles is chosen such, that the squared sum of all angles is lesser. This behaves extremely well as far as I have tested it.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Decomposition of matrix to arbitrarily ordered Euler angles&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The code from libelea (matrices are again row-major order).&lt;br /&gt;The order of axes is given as an array of 3 integers, whose values denote axes (i.e. {0,1,2} is XYZ, {1,0,2} is YXZ and so on. &lt;br /&gt;&lt;pre&gt;&lt;span style="color: blue;"&gt;// We need either quaternion or axis/angle pair&lt;br /&gt;// of given rotation&lt;br /&gt;// Go for quaternion now&lt;/span&gt;&lt;br /&gt;Quaternionf q = m.getRotationQuaternion ();&lt;br /&gt;float sign =&lt;br /&gt;    ((order[1] == (order[0] + 1)) ||&lt;br /&gt;     (order[2] == (order[1] + 1))) ? 1.0f : -1.0f;&lt;br /&gt;&lt;span style="color: blue;"&gt;// Array of identity axis vectors&lt;/span&gt;&lt;br /&gt;static Vector3f iv[] = {&lt;br /&gt;    Vector3fX, Vector3fY, Vector3fZ&lt;br /&gt;};&lt;br /&gt;&lt;span style="color: blue;"&gt;// Compose matrix that transforms to reordered axes space&lt;/span&gt;&lt;br /&gt;Matrix3x4f s;&lt;br /&gt;for (int row = 0; row &amp;lt; 3; row++) {&lt;br /&gt;    for (int col = 0; col &amp;lt; 3; col++) {&lt;br /&gt;        s[4 * row + col] = iv[order[row]][col];&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;for (int col = 0; col &amp;lt; 3; col++) {&lt;br /&gt;    s[8 + col] = sign * s[8 + col];&lt;br /&gt;}&lt;br /&gt;&lt;span style="color: blue;"&gt;// Transform quaternion&lt;br /&gt;// As we transform the first 3 values anyways,&lt;br /&gt;// we can as well use vector transformation method here&lt;/span&gt;&lt;br /&gt;s.transformVector3InPlace (q);&lt;br /&gt;&lt;span style="color: blue;"&gt;// Compose new rotation matrix from transformed quaternion&lt;/span&gt;&lt;br /&gt;Matrix3x4f rs;&lt;br /&gt;rs.setRotation (q);&lt;br /&gt;&lt;span style="color: blue;"&gt;// Decompose new matrix to Euler angles&lt;/span&gt;&lt;br /&gt;rs.getEulerAngles (angles);&lt;br /&gt;&lt;span style="color: blue;"&gt;// We have to switch sign, if order was left-hand&lt;/span&gt;&lt;br /&gt;angles[2] *= sign;&lt;br /&gt;&lt;/pre&gt;The idea is to transform matrix in such way, that the reordered rotation axes of original matrix become XYZ rotation axes of transformed matrix and then perform trivial decomposition.&lt;br /&gt;&lt;br /&gt;Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-8670206311578663377?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/8670206311578663377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/converting-rotation-matrix-to.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8670206311578663377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/8670206311578663377'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/converting-rotation-matrix-to.html' title='Converting rotation matrix to arbitrarily ordered Euler angles'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-7810864122973025787</id><published>2011-02-15T00:06:00.002+02:00</published><updated>2011-02-15T00:33:42.142+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='poser'/><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><title type='text'>Valentine day release</title><content type='html'>A new snapshot release 20110214 is available from &lt;a href="https://sourceforge.net/projects/floyd/"&gt;Sourceforge page&lt;/a&gt; as both source tarball and precompiled Win32 binary.&lt;br /&gt;This time it is mostly about Poser/DAZ 3D file format support.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Most joint properties work (there are slight inconsistencies with deformer spheres still)&lt;/li&gt;&lt;li&gt;Body morphs kinda work, but the results may vary depending on model and morph&amp;nbsp;&lt;/li&gt;&lt;li&gt;Hair can be imported as poseable item (attached to head actor)&lt;/li&gt;&lt;li&gt;Textures can include separate transparency maps&lt;/li&gt;&lt;li&gt;Clothes can be attached as skins so they follow poses&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&amp;nbsp;Not everything is easily available from GUI yet, so property editor is must-be tool.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-tw5bSGe5vYc/TVmEQNrb8GI/AAAAAAAAE7o/5oOsZmD7pgc/s1600/victoria-14.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://4.bp.blogspot.com/-tw5bSGe5vYc/TVmEQNrb8GI/AAAAAAAAE7o/5oOsZmD7pgc/s400/victoria-14.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;DAZ Victoria 4 posed in Khayyam and rendered with POV-Ray&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;I have tested it mostly with &lt;a href="http://www.daz3d.com/i/3d-models/victoria-base-products?cat=838&amp;amp;_m=d"&gt;Victoria 4&lt;/a&gt; and &lt;a href="http://www.daz3d.com/i/shop/itemdetails/?item=4676"&gt;Chibibel&lt;/a&gt;, so there can still be inconsistencies with other models. Victorial is the DAZ 3D base female model and freely available at moment. Chibibel used to be free also, but the promotion is over now.&lt;br /&gt;Other than Poser file formats (cr2, pp2 and hr2), there are improvements in Collada file exporter, OBJ importer skeleton editor. Plus small bugfixes here and there.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-7810864122973025787?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/7810864122973025787/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/valentine-day-release.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/7810864122973025787'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/7810864122973025787'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/valentine-day-release.html' title='Valentine day release'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-tw5bSGe5vYc/TVmEQNrb8GI/AAAAAAAAE7o/5oOsZmD7pgc/s72-c/victoria-14.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-1747940731745986200</id><published>2011-02-13T00:23:00.001+02:00</published><updated>2011-02-18T09:24:45.512+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='elea'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='mathematics'/><category scheme='http://www.blogger.com/atom/ns#' term='geometry'/><title type='text'>How to get quaternion from rotation matrix</title><content type='html'>For now probably the whole generation of programmers has learned the basics of projective geometry from the famous &lt;a href="http://www.j3d.org/matrix_faq/matrfaq_latest.html"&gt;Matrix and Quaternion FAQ&lt;/a&gt;. I myself have consulted it numerous times while writing Khayyam (or more precisely Elea support library). But experimenting with the 3D transformations I found that while theoretically correct, the two most esoteric methods of the FAQ give suboptimal results. Namely - converting the rotation matrix to quaternion or Euler angles becomes unstable for certain rotations and - at least for Euler angles - does not give the most intuitive solution. So decided to write slightly enhanced variants.&lt;br /&gt;&lt;br /&gt;The original code from FAQ:&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;T = 1 + mat[0] + mat[5] + mat[10]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;if ( T &amp;gt; 0.00000001 ) {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;S = sqrt(T) * 2;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;X = ( mat[9] - mat[6] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Y = ( mat[2] - mat[8] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Z = ( mat[4] - mat[1] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;W = 0.25 * S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;} if ( mat[0] &amp;gt; mat[5] &amp;amp;&amp;amp; mat[0] &amp;gt; mat[10] )  {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;S  = sqrt( 1.0 + mat[0] - mat[5] - mat[10] ) * 2;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;X = 0.25 * S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Y = (mat[4] + mat[1] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Z = (mat[2] + mat[8] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;W = (mat[9] - mat[6] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;} else if ( mat[5] &amp;gt; mat[10] ) {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;S  = sqrt( 1.0 + mat[5] - mat[0] - mat[10] ) * 2;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;X = (mat[4] + mat[1] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Y = 0.25 * S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Z = (mat[9] + mat[6] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;W = (mat[2] - mat[8] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;} else {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;S  = sqrt( 1.0 + mat[10] - mat[0] - mat[5] ) * 2;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;X = (mat[2] + mat[8] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Y = (mat[9] + mat[6] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Z = 0.25 * S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;W = (mat[4] - mat[1] ) / S;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;}&lt;/span&gt;&lt;/blockquote&gt;The problem lies in how the calculation method is switched, depending on the comparison of certain matrix elements. If, for two sequential rotations, the matrix values happen to be only slightly different, but fall into different calculation paths, the resulting quaternions will be very different and thus interpolating between them results in sudden twists. This should not happen, if your matrices are perfectly orthogonal - but if they come into existence as the result of many multiplications and (especially) inversions chances are good that they are not. Now add into the mix rotation angles close to 180 degrees, and the there will be distortions.&lt;br /&gt;&lt;br /&gt;Stable variant from libelea (please notice, that while usually libelea has column-major order, packed 3x4 matrices, as used here, have row-major order. So to port the code to OpenGL matrices you have to transpose matrix):&lt;br /&gt;&lt;blockquote&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* Get normalized transformed unit vectors of rotated space */&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f axx(Vector3f(m[0], m[4], m[8]).normalize ());&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f axy(Vector3f(m[1], m[5], m[9]).normalize ());&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f axz(Vector3f(m[2], m[6], m[10]).normalize ());&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* Calculate transposition vectors */&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* I.e. how the unit vector endpoints will be transformed */&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f tx = axx - Vector3fX;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f ty = axy - Vector3fY;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f tz = axz - Vector3fZ;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* Find two biggest transpositions */&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;f32 txl2 = tx.length2 ();&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;f32 tyl2 = ty.length2 ();&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;f32 tzl2 = tz.length2 ();&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f *a = &amp;amp;tx;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f *b = &amp;amp;ty;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;if ((txl2 &amp;lt; tyl2) &amp;amp;&amp;amp; (txl2 &amp;lt; tzl2)) {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;a = &amp;amp;ty;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;b = &amp;amp;tz;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;} else if ((tyl2 &amp;lt; txl2) &amp;amp;&amp;amp; (tyl2 &amp;lt; tzl2)) {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;a = &amp;amp;tz;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;b = &amp;amp;tx;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* Get axis (cross product of two biggest transpositions)*/&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f ax(*a * *b);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;if (!ax.length2 ()) {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* Zero rotation */&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Quaternionf0;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;ax.normalizeSelf ();&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* Get vector perpendicular to axis and transform it */&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f s(a-&amp;gt;normalize ());&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f t(m.transformVector3 (s));&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;t.normalizeSelf ();&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: blue;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;/* Find rotation angle (between vector and its transform) */&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;Vector3f s_t(s * t);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;f32 e = Vector3f::scalarProduct (s_t, ax);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;if (e &amp;lt; 0) {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ax = -ax;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;f32 theta = Vector3f::angle (s, t);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;f32 sint2 = sinf (theta / 2);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;f32 cost2 = cosf (theta / 2);&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;return Quaternionf(ax[X] * sint2, ax[Y] * sint2, ax[Z] * sint2, cost2);&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;The idea is, that any vector perpendicular to axis will be transformed to vector also perpendicular to axis. So we can get axis as the cross product between such vector and its transform.&lt;br /&gt;Now, as you can see, this is much slower, involving the vector normalization, cross products and trigonometric functions. But hopefully you do not have to do this very often - I mostly only needed it for importing foreign animation formats and compacting rotation matrices for serializing.&lt;br /&gt;I have used this method quite extensively and it has excellent stability.&lt;br /&gt;&lt;br /&gt;Next time I'll show, how to avoid sudden instabilities if converting rotation matrix to Euler angles.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-1747940731745986200?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/1747940731745986200/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/how-to-get-quaternion-from-rotation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/1747940731745986200'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/1747940731745986200'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/how-to-get-quaternion-from-rotation.html' title='How to get quaternion from rotation matrix'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-2046038569516947344</id><published>2011-02-10T01:51:00.000+02:00</published><updated>2011-02-10T01:51:04.771+02:00</updated><title type='text'>DAZ Victoria 4</title><content type='html'>Have been trying to get &lt;a href="http://www.daz3d.com/"&gt;DAZ 3D&lt;/a&gt; base model &lt;a href="http://www.daz3d.com/i/3d-models/victoria-base-products?cat=838&amp;amp;_m=d"&gt;Victoria 4&lt;/a&gt; to load and render in Khayyam. Things are slowly getting better, although it is not in very usable state yet.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TVMj87cBVWI/AAAAAAAAE7U/oTfK1jwKDgo/s1600/victoria4_inengine.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TVMj87cBVWI/AAAAAAAAE7U/oTfK1jwKDgo/s400/victoria4_inengine.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;In-engine screenshot of Victoria 4 with Jamie hair&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Body is loaded normally and most joints behave well&lt;/li&gt;&lt;li&gt;Morphs do not work yet&amp;nbsp;&lt;/li&gt;&lt;li&gt;Hair can be loaded only as static object (no morphing, not attached to skeleton)&lt;/li&gt;&lt;li&gt;Textures can be applied (only diffuse, specular and transparency)&lt;/li&gt;&lt;/ul&gt;With some patience it can be made looking pretty good. Like most Poser models, the textures are either missing from base model, or so ugly they are pretty useless. So one has to apply textures one-by-one, using material overrides.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-aqVX8AkvAb0/TVMj_gb0oFI/AAAAAAAAE7Q/VrAVsnyK1yg/s1600/victoria_4_povray.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://2.bp.blogspot.com/-aqVX8AkvAb0/TVMj_gb0oFI/AAAAAAAAE7Q/VrAVsnyK1yg/s400/victoria_4_povray.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;POV-Ray rendering of Victoria 4 with Jamie hair&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Getting Poser stuff to work is pretty high-priority now. Especially because Illusion does not seem to be planning to release anything noteworthy for some time. Meanwhile at places like &lt;a href="http://market.renderosity.com/"&gt;Renderosity&lt;/a&gt; there is almost infinite amount of very high quality content available - although most of it is a bit too realistic for my taste.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-2046038569516947344?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/2046038569516947344/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/daz-victoria-4.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2046038569516947344'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2046038569516947344'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/daz-victoria-4.html' title='DAZ Victoria 4'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_DBO6l3gpqrg/TVMj87cBVWI/AAAAAAAAE7U/oTfK1jwKDgo/s72-c/victoria4_inengine.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-1308398601828501197</id><published>2011-02-07T00:09:00.000+02:00</published><updated>2011-02-07T00:09:17.077+02:00</updated><title type='text'>Sara and Cyberdemon (and lighting)</title><content type='html'>I have been ill (sick if you are American) for the last week, so there was very little interesting things happening with Khayyam. But at last I am feeling better now, so hopefully more will come.&lt;br /&gt;&lt;br /&gt;I took few evenings some weeks ago and fixed diffuse and specular lighting (and tangent map calculation). As as result, there is again a reason to read normal- and specular map textures for objects.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TU8XhheoiFI/AAAAAAAAE6w/_51qdo8KWKw/s1600/sara-and-cyberdemon.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="311" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TU8XhheoiFI/AAAAAAAAE6w/_51qdo8KWKw/s400/sara-and-cyberdemon.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara does not surrender easily (of course AK-47 itself is also legendary)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;At least Doom creatures will look much better this way. Unfortunately Illusion games do not use normal maps, so there are no visible enhancements - unless you want to emulate the in-game plastic oily look.&lt;br /&gt;&lt;br /&gt;But - it is possible to replace textures on Illusion (and other formats too) models with materialDNS node (look at &lt;a href="http://poseablegeometry.blogspot.com/2010/12/importing-sexy-beach-zero-figures.html"&gt;tutorial&lt;/a&gt; for how exactly) - and this material node supports both normal and specular maps. So if someone wants to experiment, surface effects can be done with some effort.&lt;br /&gt;&lt;br /&gt;There are still some big things to do for lighting - like cascading shadow maps, shadows for point and parallel lights and so on. Although immediately I want to play with skin shader instead...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-1308398601828501197?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/1308398601828501197/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/02/sara-and-cyberdemon-and-lighting.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/1308398601828501197'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/1308398601828501197'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/02/sara-and-cyberdemon-and-lighting.html' title='Sara and Cyberdemon (and lighting)'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_DBO6l3gpqrg/TU8XhheoiFI/AAAAAAAAE6w/_51qdo8KWKw/s72-c/sara-and-cyberdemon.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-4680419887044260141</id><published>2011-01-30T18:50:00.001+02:00</published><updated>2011-02-22T01:39:57.766+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='khayyam'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='sara'/><title type='text'>Snapshot release 20110130</title><content type='html'>I have made a snapshot release of the latest development tree as of 2011/01/30. This time there are both source tarball and precompiled Win32 binaries.&lt;br /&gt;It can be found from &lt;a href="http://sourceforge.net/projects/floyd/"&gt;SourceForge project page&lt;/a&gt;, or use direct download links:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://sourceforge.net/projects/floyd/files/Khayyam/khayyam-win32-20110130.zip/download"&gt;Win32 binary (9.2 MB)&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;a href="http://sourceforge.net/projects/floyd/files/Khayyam/escher-0.2011.01.30.tar.bz2/download"&gt;Source package (3,7 MB) &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;What is new (compared to 4th of January release)?&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;OBJ and 3DS importers (static objects)&lt;/li&gt;&lt;li&gt;OBJ exporter&lt;/li&gt;&lt;li&gt;Fixed Collada importer/exporter (works with Blender 2.5)&lt;/li&gt;&lt;li&gt;Preliminary IK solver for skeleton posing&lt;/li&gt;&lt;li&gt;Bone constraints for IK solver&lt;/li&gt;&lt;li&gt;Object library with thumbnails to save useful models&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Basically the available list of models increased dramatically. There are literally hundreds of different sources of free or cheap 3D models in OBJ and 3DS formats.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Bonus model&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;As a extra bonus, Sara tween girl Collada model is included in release (in both versions).&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_DBO6l3gpqrg/TUKsUOPWjRI/AAAAAAAAE4o/FXi8CICjeyg/s1600/preview.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://2.bp.blogspot.com/_DBO6l3gpqrg/TUKsUOPWjRI/AAAAAAAAE4o/FXi8CICjeyg/s320/preview.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara tween girl, Blender model&lt;/td&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;The Blender source file can be downloaded from &lt;a href="http://www.turbosquid.com/3d-models/tween-girl-blend-free/583223"&gt;TurboSquid&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Mini-tutorials&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I have previously written two mini tutorials, both available from this site. In case you missed these, they can be found at:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://poseablegeometry.blogspot.com/2010/12/importing-sexy-beach-zero-figures.html"&gt;How to import SBZ figures (2010/12/22)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://poseablegeometry.blogspot.com/2011/01/rendering-with-pov-ray.html"&gt;How to export composed scenes for POV-Ray rendering (2011/01/30)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Have fun!&lt;br /&gt;Lauris Kaplinski&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-4680419887044260141?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/4680419887044260141/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/01/snapshot-release-20110130.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/4680419887044260141'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/4680419887044260141'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/01/snapshot-release-20110130.html' title='Snapshot release 20110130'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_DBO6l3gpqrg/TUKsUOPWjRI/AAAAAAAAE4o/FXi8CICjeyg/s72-c/preview.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-6788315372648844302</id><published>2011-01-30T01:54:00.001+02:00</published><updated>2011-02-22T01:34:50.194+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rendering'/><category scheme='http://www.blogger.com/atom/ns#' term='pov-ray'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><title type='text'>Rendering with POV-Ray</title><content type='html'>&lt;a href="http://www.povray.org/"&gt;POV-Ray&lt;/a&gt; (The Persistence of Vision Raytracer) is high quality free raytracing renderer. Khayyam can export most models and scenes into POV-Ray file format (.pov) to allow special effects, like focal blur, global illumination and so on. Following is a mini-tutorial for scene composing and exporting.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;1. Build your scene&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is really outside of this tutorial so only few hints.&lt;br /&gt;&lt;br /&gt;As the first thing, open &lt;i&gt;&lt;span style="color: black;"&gt;Document Tree&lt;/span&gt;&lt;/i&gt; editor (&lt;b style="background-color: white;"&gt;Dialogs -&amp;gt; Document Tree&lt;/b&gt;). It has property pages for the most of object types in scenes and most fine-tuning has to be done with it.&lt;br /&gt;&lt;br /&gt;Add lights. It can be done later, but a good idea is to add at least one directional light as the first step (&lt;b&gt;Objects -&amp;gt; Add -&amp;gt; Lights -&amp;gt; Directional Light&lt;/b&gt;) because otherwise model will look plain and it is hard to understand their poses exactly.&lt;br /&gt;&lt;br /&gt;Import objects and figures (&lt;b&gt;Objects -&amp;gt; Import&lt;/b&gt;). Currently there is support for most Illusion model formats (althoughthe ones from cell-shaded games do no look nice), MD5 (Doom3 and Quake4), OBJ, 3DS, Collada (partial) and Poser (partial) file formats.&lt;br /&gt;&lt;br /&gt;Scale models. Default scales are mostly OK for Illusion, MD5 and Poser files, but the ones imported from other formats may be of very different scales. For scaling, select object (click on it when it has yellow highlight box) - the highlight box should turn orange. Now look at &lt;i&gt;Document Tree&lt;/i&gt; dialog - either object of type &lt;b&gt;Figure&lt;/b&gt; (poseable object) or &lt;b&gt;StaticMesh&lt;/b&gt; (non-poseable) should be selected.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_DBO6l3gpqrg/TUSfNnl0P0I/AAAAAAAAE4w/Na9xQ666_yc/s1600/document-tree-figure.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="248" src="http://2.bp.blogspot.com/_DBO6l3gpqrg/TUSfNnl0P0I/AAAAAAAAE4w/Na9xQ666_yc/s320/document-tree-figure.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Document tree showing selected figure&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;b&gt;Figure&lt;/b&gt; and &lt;b&gt;StaticMesh&lt;/b&gt; are toplevel containers that can include many geometries (body, hair, clothes...). Select one geometry node in tree and the content of the rightmost notebook pane should change. Each notebook page corresponds to one level in selected object type hierarchy so switching pages gives you different levels of controls.&lt;br /&gt;&lt;br /&gt;Click on &lt;b&gt;Geometry&lt;/b&gt; tab and there are controls applicable to all &lt;b&gt;Geometry&lt;/b&gt; types, among others a scale spinner.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_DBO6l3gpqrg/TUSff1Im-GI/AAAAAAAAE40/SN_O8p-77zo/s1600/document-tree-geometry.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="248" src="http://2.bp.blogspot.com/_DBO6l3gpqrg/TUSff1Im-GI/AAAAAAAAE40/SN_O8p-77zo/s320/document-tree-geometry.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Document tree with SkinnedGeometry selected Geometry property page open&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Place models. For this select model and transform control should appear. If it is not visible, try pressing &lt;b style="background-color: #fce5cd;"&gt;Q&lt;/b&gt; key for few times - it cycles the control placement between object origin, object bounding box and the center of screen. Pressing &lt;b style="background-color: #fce5cd;"&gt;SPACE&lt;/b&gt; key toggles between move and rotation controls. The same can be achieved with clicking on already selected model.&lt;br /&gt;&lt;br /&gt;Take a look at hotkeys list (&lt;b&gt;Help -&amp;gt; Hotkeys&lt;/b&gt;) - there are some useful keyboard and mouse hotkeys, like hiding controls temporarily and so on.&lt;br /&gt;&lt;br /&gt;Pose figures - to do this, select figure (poseable object, like Illusion character, MD5 or Collada model) and choose &lt;i&gt;skeleton tool&lt;/i&gt; from toolbar. If the selected object is of figure type, bluish skeleton should appear. Nothing will happen, if the object is not of posebale type.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_DBO6l3gpqrg/TUSf8jP3DxI/AAAAAAAAE44/K2bgav9rHuQ/s1600/skeleton-control.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="248" src="http://1.bp.blogspot.com/_DBO6l3gpqrg/TUSf8jP3DxI/AAAAAAAAE44/K2bgav9rHuQ/s320/skeleton-control.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Selected object with skeleton control&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;Posing individual bones is quite similar to placing objects - although normally you do not want to move bones (it breaks body very unnaturally) but only rotate them.&lt;br /&gt;&lt;br /&gt;Save scene. Save often, as Khayyam is still alpha and crashes a lot.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;2. Set up lighting &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Special lighting is the main reason you want to raytrace your scene. So take some time and try to set up lighting as well as possible - it really makes difference in final images.&lt;br /&gt;Add spotlights (&lt;b&gt;Objects -&amp;gt; Add -&amp;gt; Lights -&amp;gt; Spotlight&lt;/b&gt;). There can be practically unlimited number of spotlights, but they make rendering slower. Also semitransparent objects (hair) cannot currently be lighted by more than four spotlights.&lt;br /&gt;Set light direction for spotlights. The easiest method to do this, is copying light direction from camera.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Move camera to show the scene light you want it to be seen from the spotlight.&lt;/li&gt;&lt;li&gt;From document tree select &lt;b&gt;SpotLight&lt;/b&gt; node&lt;/li&gt;&lt;li&gt;From right pane select &lt;b&gt;Light&lt;/b&gt; tab&lt;/li&gt;&lt;li&gt;Click &lt;b&gt;&lt;span style="background-color: #f3f3f3;"&gt;From Camera&lt;/span&gt;&lt;/b&gt; button&lt;/li&gt;&lt;li&gt;From the same place you can set the light intensity and color &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgS0PYJeI/AAAAAAAAE48/AmZ3BbZiMQY/s1600/document-tree-spotlight.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="248" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgS0PYJeI/AAAAAAAAE48/AmZ3BbZiMQY/s320/document-tree-spotlight.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Document tree with Spotlight selected and Light property page open&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Ambient lighting adds undirectional light to the whole scene. It can be used to make shadow regions to have less contrast - but it tends to make scene plain.&lt;br /&gt;Diffuse lighting determines the actual spotlight intensity and color. &lt;br /&gt;Direct lighting if for specular highlights, but at moment it does not work reliably.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If you want to add light with soft shadows, use &lt;b&gt;Area Lights&lt;/b&gt;. They are similar to spotlights, but are composed of many small lights on rectangular grid. Consequently they can slow down rendering quite a lot, so by default they only have 2 x 2 nodes. To achieve softer effect, you can add more sublights from document tree dialog.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;3. Export scene to POV-Ray format&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To export POV-Ray scene, select &lt;b&gt;File -&amp;gt; Export POV-Ray Scene...&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgmO1lW_I/AAAAAAAAE5E/RS-Urnc3i_A/s1600/pov-ray-1.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgmO1lW_I/AAAAAAAAE5E/RS-Urnc3i_A/s320/pov-ray-1.jpg" width="315" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;POV-Ray export dialog&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The exporter dialog has three tabs. Normally you want to start from  default values, as these render reasonably fast and you can check how  the scene looks like. Khayyam and POV-Ray use slightly different  lighting formula (actually very different if you use radiosity) so you  may want to adjust light intensities and colors before doing final  render.&lt;br /&gt;&lt;br /&gt;If you have exported scene once and have not added any new objects to  it, you can uncheck &lt;b&gt;Export Textures as PNG files&lt;/b&gt; checkbox - this saves  some exporter time, as it does not have to write texture files over and over. &lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgmiLIWEI/AAAAAAAAE5I/mhu0i0mF0-w/s1600/pov-ray-2.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgmiLIWEI/AAAAAAAAE5I/mhu0i0mF0-w/s320/pov-ray-2.jpg" width="315" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Lighting properties from POV-Ray export dialog&lt;/td&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;If you are otherwise content with the scene lookout, you can enable &lt;b&gt;Radiosity&lt;/b&gt;. This add indirect lighting to objects - i.e. light bouncing back and forth from surfaces and thus makes the sene much more natural. But it makes rendering considerably slower and can mess up lighting, as in Khayyam you cannot estimate easily, how much reflecting light changes the overall scene brightness - so experiment.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgl1ey22I/AAAAAAAAE5A/dtbr546_HD8/s1600/pov-ray-3.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSgl1ey22I/AAAAAAAAE5A/dtbr546_HD8/s320/pov-ray-3.jpg" width="315" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Camera properties from POV-Ray export dialog&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Adding &lt;b&gt;Aperture&lt;/b&gt; makes objects out of focus (nearer and further) slightly blurred - how much, depends on aperture value (smaller value reduces blur). The focus point is determined by the rotation center in Khayyam (fading target image that appears if you move camera) so place it close to the objects or features you want to make sharp.&lt;br /&gt;Aperture makes rendered images much more photo-like and can add a lot to the mood. But it makes rendering slower.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Click Export and save file with the extension &lt;b&gt;.pov&lt;/b&gt;. If you have  already  installed POV-Ray, it has registered itself as default handler  for those  file types.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;span style="font-size: large;"&gt;4. Render it with POV-Ray &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Using POV-Ray is outside this tutorial. Some hints though:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;POV-Ray opens all files in builtin editor, so to make small changes you do not have to re-export scene again.&lt;/li&gt;&lt;li&gt;Rendering starts with button Run&lt;/li&gt;&lt;li&gt;You can select render size from top-left menu box&lt;/li&gt;&lt;li&gt;Render result will automatically be saved as png file to the same directory as pov file.&lt;/li&gt;&lt;li&gt;POV-Ray uses custom scene description language that can be quite complex to understand.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_DBO6l3gpqrg/TUSjWJGU2uI/AAAAAAAAE5Q/YGTPZDJ4G6Y/s1600/sara-ak47-khayyam.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="248" src="http://4.bp.blogspot.com/_DBO6l3gpqrg/TUSjWJGU2uI/AAAAAAAAE5Q/YGTPZDJ4G6Y/s320/sara-ak47-khayyam.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Fully set up scene in Khayyam&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSjm2Pt7xI/AAAAAAAAE5o/t2YfT05Nbc0/s1600/test.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="240" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TUSjm2Pt7xI/AAAAAAAAE5o/t2YfT05Nbc0/s320/test.jpg" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The same scene rendered with POV-Ray (using radiosity and aperture). As you can see, using radiosity added lot of extra light to environment.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;And this is it! Have fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-6788315372648844302?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/6788315372648844302/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/01/rendering-with-pov-ray.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6788315372648844302'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6788315372648844302'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/01/rendering-with-pov-ray.html' title='Rendering with POV-Ray'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_DBO6l3gpqrg/TUSfNnl0P0I/AAAAAAAAE4w/Na9xQ666_yc/s72-c/document-tree-figure.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-3143534389469804936</id><published>2011-01-21T18:57:00.000+02:00</published><updated>2011-01-21T18:57:07.277+02:00</updated><title type='text'>Sara</title><content type='html'>I have for long time planned to bundle some freeware models with Khayyam. Now, at last, the first iteration of Sara - a young female - is nearing completion. She is quite low-poly, so she will be better suited for real-time animation than for renders.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_DBO6l3gpqrg/TTm45RIKRXI/AAAAAAAAE2s/zTIB9f61KZY/s1600/Sara-4.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="292" src="http://4.bp.blogspot.com/_DBO6l3gpqrg/TTm45RIKRXI/AAAAAAAAE2s/zTIB9f61KZY/s400/Sara-4.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Sara rigged in Blender&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;She still needs hair and some more clothes before making a public debut.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-3143534389469804936?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/3143534389469804936/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/01/sara.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3143534389469804936'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3143534389469804936'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/01/sara.html' title='Sara'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_DBO6l3gpqrg/TTm45RIKRXI/AAAAAAAAE2s/zTIB9f61KZY/s72-c/Sara-4.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-6684217063856995619</id><published>2011-01-02T21:31:00.000+02:00</published><updated>2011-01-02T21:31:39.088+02:00</updated><title type='text'>Experimenting with IK solver</title><content type='html'>Happy new year everyone!&lt;br /&gt;&lt;br /&gt;I have spent most of my free time between Christmas and New Year by moving the preliminary IK solver code from animation merge to generic pose editor. The results are mostly working, so expect a release sometimes soon. &lt;br /&gt;&lt;br /&gt;Inverse kinematics is a set of algorithms everyone is talking about, but there is actually very little useful information found in web. Other than studying the source code of existing software, that is. There are actually two complexities - first the algorithm itself and then specifying meaningful constraints. Plus all kinds of small technical things.&lt;br /&gt;&lt;br /&gt;The algorithm is an easier part. Just went with &lt;a href="http://paula.univ.gda.pl/%7Edokgrk/simplex.html"&gt;Downhill Simplex&lt;/a&gt;, mainly because it is conceptually easy and reasonably fast. The function is simply the distance between IK target and the predefined endpoint of moving limb.&lt;br /&gt;&lt;br /&gt;Constraints are trickier. We have to add the properly scaled tensions of joints to distance function, so joints try to find the easiest (most relaxed) position for given target. As much as I hate Euler angles, I cannot imagine quick and easy way to specify joint tension using only quaternions and/or matrices. It should be possible to create 3-dimensional grid of values and interpolate normalized quaternion in that space. But I do not want to experiment with it before I can get everything working simpler way.&lt;br /&gt;So what had to be done was to decompose all bone relative movements (compared to rest position) into Euler angles. The order of axes is configurable (XYZ, XZY and so on).&lt;br /&gt;&lt;br /&gt;Once we have Euler angles, it is quite logical to specify the maximum positive and negative rotations allowed to given bone. But there is one caveat - minimization algorithms normally require the tension function to be smooth. So we cannot simply clip the tension function at threshold. Also it is normally not a good idea to keep the tension value constant below threshold, because joints always prefer to be as close to rest position as possible.&lt;br /&gt;At moment I made a mix of two power functions for tension calculation:&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace; font-size: small;"&gt;T = R * (angle / P) ** 1.25 + (angle / P) ** 16&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace; font-size: small;"&gt;T - calculated tension value&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace; font-size: small;"&gt;R - rigidity (0-1) of the movement&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;span style="font-size: small;"&gt;P - threshold value (maximum rotation)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;There are two different set of threshold and rigidity values - one for positive, one for negative values. Rigidity determines, how fast the tension increases below the threshold value (it has small exponent 1.25 to make the overall function differentiable at zero).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_DBO6l3gpqrg/TSC-NTZI3CI/AAAAAAAAE1o/XJefycXwUpk/s1600/jointgraph.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="281" src="http://1.bp.blogspot.com/_DBO6l3gpqrg/TSC-NTZI3CI/AAAAAAAAE1o/XJefycXwUpk/s400/jointgraph.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The example of tension function&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;Here is a graph of example tension function with parameters -1.0;1.0  (negative) and 1.5;0.1 (positive). It  describes a joint, that moves backwards up to about one radian with considerable tension and forward up to 1.5 radians relatively freely. Although it does not seem so looking at graph, the function is actually smooth at zero.&lt;br /&gt;Certain joints have some rotations blocked - like twisting and sideways bending for knees. This is better done by turning those axes off completely instead of setting very small movement thresholds, because we want to keep the degrees of freedom as low as possible.&lt;br /&gt;&lt;br /&gt;And it works - at least wee enough for static positioning. For some reason the simplex solver diverges a lot for small changes in target position if the overall movement is relatively big.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_DBO6l3gpqrg/TSDMEOBRmTI/AAAAAAAAE2A/-DskLFhWHF4/s1600/Ai-IK.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://2.bp.blogspot.com/_DBO6l3gpqrg/TSDMEOBRmTI/AAAAAAAAE2A/-DskLFhWHF4/s400/Ai-IK.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Positioning of limbs with IK targets&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;The latest version of Khayyam has a set of meaningful IK chains and rotation constraints built into XPP importer (only for SBZ and compatible skeletons).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-6684217063856995619?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/6684217063856995619/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2011/01/experimenting-with-ik-solver.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6684217063856995619'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6684217063856995619'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2011/01/experimenting-with-ik-solver.html' title='Experimenting with IK solver'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_DBO6l3gpqrg/TSC-NTZI3CI/AAAAAAAAE1o/XJefycXwUpk/s72-c/jointgraph.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-2060017848795340439</id><published>2010-12-27T20:06:00.001+02:00</published><updated>2010-12-27T20:07:51.400+02:00</updated><title type='text'>How to teach Imp to dance</title><content type='html'>A new video showing how to import Characolle dancing animation to SBZ figure, then export it to BioVision (bvh) file and then apply to Doom 3 Imp model (md5).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;object class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://i.ytimg.com/vi/StQ93_Jmpng/0.jpg" height="266" width="320"&gt;&lt;param name="movie" value="http://www.youtube.com/v/StQ93_Jmpng?f=user_uploads&amp;c=google-webdrive-0&amp;app=youtube_gdata" /&gt;&lt;param name="bgcolor" value="#FFFFFF" /&gt;&lt;embed width="320" height="266" src="http://www.youtube.com/v/StQ93_Jmpng?f=user_uploads&amp;c=google-webdrive-0&amp;app=youtube_gdata" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;To be precise, the merged animation cannot be saved yet, expect more to come...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-2060017848795340439?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/2060017848795340439/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/12/how-to-teach-imp-to-dance.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2060017848795340439'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2060017848795340439'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/12/how-to-teach-imp-to-dance.html' title='How to teach Imp to dance'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-4600660741067725324</id><published>2010-12-22T16:41:00.002+02:00</published><updated>2011-02-22T01:35:43.858+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='importing'/><category scheme='http://www.blogger.com/atom/ns#' term='illusion'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='sbz'/><title type='text'>Importing Sexy Beach Zero figures</title><content type='html'>Current windows build of Khayyam can be downloaded from &lt;a href="http://sourceforge.net/projects/floyd/"&gt;Floyd Sourceforge page&lt;/a&gt;.&lt;br /&gt;&lt;a href="http://lauris.kaplinski.com/khayyam/khayyam-preview-20101222.zip"&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Here is quick tutorial how to import SBZ figures. The similar procedure can be used for other multi-part meshes (SB3, School Mate etc) - but not all collection formats have working autodetection for textures, bones and so on.&lt;br /&gt;From main menu select: &lt;b&gt;Objects -&amp;gt; Import -&amp;gt; Import Xpp Mesh&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;, &amp;quot;Courier&amp;quot;, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TRH45N9AuFI/AAAAAAAAEyk/l5alcIs9U5o/s1600/xpp-import-dialog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="266" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TRH45N9AuFI/AAAAAAAAEyk/l5alcIs9U5o/s400/xpp-import-dialog.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Click "&lt;b&gt;Choose directory...&lt;/b&gt;" and select the "&lt;b&gt;data&lt;/b&gt;" subdirectory in installed game location.&lt;br /&gt;At the right-left is the list of all collection files (&lt;b&gt;.pp&lt;/b&gt;) in chosen location. For SBZ, the bodies are in file &lt;b&gt;sb03_00.pp&lt;/b&gt;. The game format should be autodetected from file name, but if it fails, select "&lt;b&gt;Sexy Beach Zero&lt;/b&gt;" manually from game dropdown list. After selecting correct collection file the contents of it should appear in lower-left list.&lt;br /&gt;Female bodies are named &lt;b&gt;cw_body_??_??.xx&lt;/b&gt;.&lt;br /&gt;Keep "&lt;b&gt;Hide extras&lt;/b&gt;" and "&lt;b&gt;External textures&lt;/b&gt;" checked for now - these make oiling/massage guide mesh invisible and replace plain skin materials from predefined textures.&lt;br /&gt;Select proper body model and the preview should appear at right. The common camera controls (middle and right drag) work similar way to the main window.&lt;br /&gt;Click "&lt;b&gt;Import&lt;/b&gt;". The import options dialog should appear.&lt;br /&gt;&lt;br /&gt;&lt;span id="goog_1183625320"&gt;&lt;/span&gt;&lt;span id="goog_1183625321"&gt;&lt;/span&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_DBO6l3gpqrg/TRH7u-4xDHI/AAAAAAAAEy4/NpPERJ-R_v8/s1600/xpp-import-options-1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="289" src="http://1.bp.blogspot.com/_DBO6l3gpqrg/TRH7u-4xDHI/AAAAAAAAEy4/NpPERJ-R_v8/s320/xpp-import-options-1.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;If you are importing the first object, the only available option is "&lt;b&gt;Import as standalone mesh&lt;/b&gt;". Keep "&lt;b&gt;Create skeleton&lt;/b&gt;" checked if you want to pose the figure later. Click "&lt;b&gt;Import&lt;/b&gt;".&lt;br /&gt;Imported body appears in main window. But as there is no proper lighting, it looks plain.&lt;br /&gt;Select "&lt;b&gt;Object-&amp;gt;Add-&amp;gt;Directional Light&lt;/b&gt;" to make it better.&lt;br /&gt;&lt;br /&gt;&lt;span id="goog_692059680"&gt;&lt;/span&gt;&lt;span id="goog_692059681"&gt;&lt;/span&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_DBO6l3gpqrg/TRH9QP9pCjI/AAAAAAAAEzg/U6-AzrKDeFo/s1600/xpp-body-2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="310" src="http://1.bp.blogspot.com/_DBO6l3gpqrg/TRH9QP9pCjI/AAAAAAAAEzg/U6-AzrKDeFo/s400/xpp-body-2.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Select body by moving mouse over it (yellow highlight box should appear) and clicking. Selection is marked by orange wireframe around the body.&lt;br /&gt;Now you are ready to import head. Again chose "&lt;b&gt;Object-&amp;gt;Import-&amp;gt;Import Xpp mesh...&lt;/b&gt;"&lt;br /&gt;From the collection list select the same collection, chose any of the head files (&lt;b&gt;cw_head_??_??.xx&lt;/b&gt;) and click import.&lt;br /&gt;&lt;br /&gt;&lt;span id="goog_1667308147"&gt;&lt;/span&gt;&lt;span id="goog_1667308148"&gt;&lt;/span&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_DBO6l3gpqrg/TRH-wqIv2BI/AAAAAAAAEz0/Un8_eXqeooE/s1600/xpp-import-options-2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="293" src="http://4.bp.blogspot.com/_DBO6l3gpqrg/TRH-wqIv2BI/AAAAAAAAEz0/Un8_eXqeooE/s320/xpp-import-options-2.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The proper import options should be preselected. You have to import it as "&lt;b&gt;Skin of selected mesh&lt;/b&gt;" so it will be controlled by the same skeleton. "&lt;b&gt;Parent bone&lt;/b&gt;" and "&lt;b&gt;Anchor bone&lt;/b&gt;" determine, which bones will be merged between existing body and new head skeletons. Click "&lt;b&gt;Import&lt;/b&gt;" and voila - body has now head.&lt;br /&gt;Repeat the same procedure with hair (the name of mesh is &lt;b&gt;cw_hair_??_??.xx&lt;/b&gt;).&lt;br /&gt;&lt;br /&gt;&lt;span id="goog_1754748955"&gt;&lt;/span&gt;&lt;span id="goog_1754748956"&gt;&lt;/span&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_DBO6l3gpqrg/TRIBfEh0fcI/AAAAAAAAE0I/hdQFLLvp8ck/s1600/xpp-body-3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="310" src="http://4.bp.blogspot.com/_DBO6l3gpqrg/TRIBfEh0fcI/AAAAAAAAE0I/hdQFLLvp8ck/s400/xpp-body-3.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The next step is applying a texture to swimwear. For this we use builtin DOM editor - the most powerful tool of Khayyam.&lt;br /&gt;Choose "&lt;b&gt;Dialogs-&amp;gt;Document Tree&lt;/b&gt;".&lt;br /&gt;Expand "&lt;b&gt;Scene&lt;/b&gt;" and then "&lt;b&gt;Figure&lt;/b&gt;" nodes from left-hand tree view. Figure is a poseable object, that can have multiple skins (body, head, clothes) controlled by the single skeleton and additional poseable objects (like items at hand). The imported SBZ meshes are "&lt;b&gt;GeometryXX&lt;/b&gt;" nodes. Select the first one of them (the body mesh).&lt;br /&gt;&lt;br /&gt;&lt;span id="goog_438291368"&gt;&lt;/span&gt;&lt;span id="goog_438291369"&gt;&lt;/span&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_DBO6l3gpqrg/TRIDqq3kTcI/AAAAAAAAE0g/ckbuoPVcy0U/s1600/document-tree-1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="292" src="http://4.bp.blogspot.com/_DBO6l3gpqrg/TRIDqq3kTcI/AAAAAAAAE0g/ckbuoPVcy0U/s400/document-tree-1.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;At the right side there are tabs for the selected node and its parent classes. At moment we are interested in the Xpp geometry node itself (&lt;b&gt;GeometryXX&lt;/b&gt;). You can see the list of all used internal materials, with visiblity checkboxes and optional external material ids. Importer autodetection should have made oiling guides invisible already. The body materials should have external textures attached (materialDNS_6 and materialDNS_5).&lt;br /&gt;Select bra material &lt;b&gt;mn_mizugi_00_00&lt;/b&gt;. From dropdown box below select "&lt;b&gt;Create new&lt;/b&gt;" and click "&lt;b&gt;Apply&lt;/b&gt;". This creates new texture override node for bra and opens material editor to define texture for it.&lt;br /&gt;Texture editor looks like similar to Xpp mesh importer. First click "&lt;b&gt;Choose directory&lt;/b&gt;" at top left and navigate to "&lt;b&gt;data&lt;/b&gt;" directory under the installed game.&amp;nbsp; From "&lt;b&gt;Show files&lt;/b&gt;" dropdown list chose "&lt;b&gt;Collections&lt;/b&gt;". Select the collection with swimwear textures (&lt;b&gt;sb06_00.pp&lt;/b&gt;). From Type dropdown list select "&lt;b&gt;Sexy Beach Zero&lt;/b&gt;" (it is not autodetected at moment).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_DBO6l3gpqrg/TRIH0_pR08I/AAAAAAAAE00/5k1C6zFs6hE/s1600/import-texture-1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="212" src="http://1.bp.blogspot.com/_DBO6l3gpqrg/TRIH0_pR08I/AAAAAAAAE00/5k1C6zFs6hE/s400/import-texture-1.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;At lower left list are all available textures from that collection. Select the one you like and click "&lt;b&gt;Apply&lt;/b&gt;".&lt;br /&gt;The selected material is now updated with chosen texture. As a next step, apply the same texture to panties and inner sides of swimwear.&lt;br /&gt;Select "&lt;b&gt;mn_mizugi_01_00&lt;/b&gt;" from material list. From dropdown list below select the new material created for bra (&lt;b&gt;materialDNS_2&lt;/b&gt;) and click "&lt;b&gt;Apply&lt;/b&gt;". repeat the same procedure from the inside of swimwear (&lt;b&gt;mn_mizugi_00_01&lt;/b&gt; and &lt;b&gt;mn_mizugi_01_01&lt;/b&gt;).&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TRIH7GF0oSI/AAAAAAAAE08/0PL11pUWT1c/s1600/xpp-body-4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="310" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TRIH7GF0oSI/AAAAAAAAE08/0PL11pUWT1c/s400/xpp-body-4.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;And thats all. You have just imported Sexy Beach Zero girl with swimwear.&lt;br /&gt;&lt;br /&gt;For posing there is skeleton mode (the small button with figure and blue bones in toolbar).&lt;br /&gt;Before proceeding further it is good idea to familiarize yourself with document tree editor a bit - for example hiding bones without skin and extra bones you may not want to edit.&lt;br /&gt;Importing scene is similar to importing figure - just make sure, that you import it as standalone object.&lt;br /&gt;&lt;br /&gt;And last thing - save often. Khayyam is still alpha, so it has a bad habit to crash frequently.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-4600660741067725324?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/4600660741067725324/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/12/importing-sexy-beach-zero-figures.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/4600660741067725324'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/4600660741067725324'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/12/importing-sexy-beach-zero-figures.html' title='Importing Sexy Beach Zero figures'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_DBO6l3gpqrg/TRH45N9AuFI/AAAAAAAAEyk/l5alcIs9U5o/s72-c/xpp-import-dialog.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-3356426092399750185</id><published>2010-12-13T10:59:00.000+02:00</published><updated>2010-12-13T10:59:18.179+02:00</updated><title type='text'>Sexy Beach Zero</title><content type='html'>SBZ is the latest hentai game from Illusion (look at&amp;nbsp;&lt;a href="http://www.hongfire.com/forum/showthread.php?t=243190&amp;amp;page=1"&gt;ontopic Hongfire forum&lt;/a&gt; if you want to know more and do not mind erotic content). Like other Sexy Beach games, it's gameplay is quite unfun - woo girls, talk, date, and massage them, change swimwear and have sex - but it has upgraded rendering engine (although not very good) and, most importantly, semi-realistic (but still pretty) models. So I have spent the last days in trying to get Khayyam to read, render and pose those as well as possible.&lt;br /&gt;&lt;br /&gt;To make character customization possible, models are composed of three separate parts - body, head and hair. Also - because the game allows you to "tan" girls - neither body nor head texture is embedded in mesh, but instead has to be loaded separately. Same with swimwear textures. Body also has extra "envelope" layer, that is used to guide hand and oil during oiling/massage scenes.&lt;br /&gt;&lt;br /&gt;The skeleton has become even more complex, compared to previous models. One reason for this seems to be, that ingame body customization is now done with adjusting hidden bones.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_DBO6l3gpqrg/TQXYbvD3UMI/AAAAAAAAEog/xF3Cndf9DyU/s1600/khayyam-rin-skeleton.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="310" src="http://2.bp.blogspot.com/_DBO6l3gpqrg/TQXYbvD3UMI/AAAAAAAAEog/xF3Cndf9DyU/s400/khayyam-rin-skeleton.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Full skeleton of a SBZ model&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The item and clothing collection is very uninteresting, like one can expect from Sexy Beach series. Maps are of better quality than in previous games, but there are only few of them.&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TQXYcibdF6I/AAAAAAAAEok/FTfpcMJFLhE/s1600/ai-in-jungle.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TQXYcibdF6I/AAAAAAAAEok/FTfpcMJFLhE/s400/ai-in-jungle.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Ai in jungle (rendered with POV-Ray)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;And a render for you. It has too much focal blur, but I am too lazy at  moment to make better one ;) The girl is Ai, who first appeared in Real  Girlfriend, background is daytime jungle map from SBZ. Lighting consists  of one area light (daylight) plus 2 greenish spotlights to illuminate  shadows.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-3356426092399750185?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/3356426092399750185/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/12/sexy-beach-zero.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3356426092399750185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3356426092399750185'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/12/sexy-beach-zero.html' title='Sexy Beach Zero'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_DBO6l3gpqrg/TQXYbvD3UMI/AAAAAAAAEog/xF3Cndf9DyU/s72-c/khayyam-rin-skeleton.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-7669598784310203071</id><published>2010-11-18T16:21:00.000+02:00</published><updated>2010-11-18T16:21:34.544+02:00</updated><title type='text'>How to merge animations?</title><content type='html'>There are two basic problems with copying animations from one figure to another:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The skeletons do not match. First, the body proportions are off, especially if converting between male/female or realistic/stylized characters. And secondly, the non-obvious bones are often arranged completely differently. By obvious bones I mean the ones like armbones, shins and so on, that connect two visible joints. Non-obvious bones are hidden and their joints are not immediately visible - like backbone or shoulders. Thus the exact number and placement of such bones may vary, depending on how realistic and fluid motion is needed and how it is achieved.&lt;/li&gt;&lt;li&gt;The bone orientations do not match. Even for bones that are perfectly aligned, like arms, the local coordinate systems can be very different and thus the transformations from one skeleton cannot be immediately carried over to another.&lt;/li&gt;&lt;/ol&gt;&amp;nbsp;&lt;b&gt;How to deal with it?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In Khayyam I am currently doing the following procedure:&lt;br /&gt;&lt;ol&gt;&lt;li&gt; Create one-to-one links between the well-defined bones of each skeleton - i.e. legs, shins, arms, forearms and heads. These function as anchors for matching body movements via IK solver.&lt;/li&gt;&lt;li&gt;Pick rootmost joints of matched bones, i.e. hips and shoulders as anchor point pairs.&lt;/li&gt;&lt;li&gt;Find the bone chain leading to last common ancestor of such pairs (starting from the point where previous chain ended). I.e. we get chains like these:&lt;br /&gt;3.1. Center_of_Mass -&amp;gt; Skeleton_Root -&amp;gt; Pelvis&lt;br /&gt;3.2. Lower_Backbone -&amp;gt; Upper_Backbone -&amp;gt; Chest&lt;/li&gt;&lt;li&gt;Adjust chains, starting from rootmost, as closely as possible to anchor points using IK solver.&lt;/li&gt;&lt;li&gt;Point all well-defined bones to the same direction as the originals, using IK solver&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;As you can see, it basically works, although some constraints on joint movement would be nice.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;object height="385" width="480"&gt;&lt;param name="movie" value="http://www.youtube.com/v/MiqWa9v1SRc?fs=1&amp;amp;hl=en_US"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/MiqWa9v1SRc?fs=1&amp;amp;hl=en_US" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;A demo video showing how dancing moves for DAZ skeleton can be imported to Esk Anderson figure&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-7669598784310203071?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/7669598784310203071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/11/how-to-merge-animations.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/7669598784310203071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/7669598784310203071'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/11/how-to-merge-animations.html' title='How to merge animations?'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-5556232508166141180</id><published>2010-11-07T21:50:00.000+02:00</published><updated>2010-11-07T21:50:41.774+02:00</updated><title type='text'>About C++</title><content type='html'>I have had love/hate relationship with C++ from the time I started using it extensively about nine years ago. Before that everything was C - for example Sodipodi was 99.9% written in C (the only exception was the interface to KDE file dialogs). Those were good old times...&lt;br /&gt;&lt;br /&gt;The learning of C++ was mostly one big frustration. Few beautiful and useful features - buried in clumsy, unintuitive and ugly mess. So after trying out and throwing away one feature after another, I mostly joined the C with classes crowd. This is the style used in Khayyam/Miletos/Elea code. But even for this minor subset of features, the language is just implemented plain wrong. Some completely elementary, clean and logical things are missing - and on the other hand - extremely clumsy, opaque and hard to use features are present.&lt;br /&gt;&lt;br /&gt;So following is my list of things I'll one day implement in my own C Successor(TM) language.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Keep classes and struct separate. Structs should be POD, classes should ALWYAS have pointer to RTI. So no virtual methods for struct - and who in his right mind will miss these?&lt;/li&gt;&lt;li&gt;Keep memory layout standardized. Seriously. Probably all C++ variants implement things in one way - but the specification refuses to fix things once and for all.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Classes are struct(ure)s. Like in Java. Standardized containers having function pointers for virtual methods, RTI information, link to parent class and so on.&lt;/li&gt;&lt;li&gt;All object instances have hidden data field in first position, that is - you guess it - pointer to class struct(ure).&lt;/li&gt;&lt;li&gt;All nonstatic methods should have pointer to class instance as hidden first parameter. So you can call class method from C. It is implemented this way anyways - but why force people to jump through loops to use it.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Keep meta-language separate from core. Implement two-pass compiling or something (in addition to preprocessor).&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Operator overloading should be specified in some meta-language. This would allow really useful things, like implementing separate dot and cross operators for vectors and so on. It should be transparently translated into proper C code during metalanguage parsing pass and then compiled by normal C/C++ compiler.&lt;/li&gt;&lt;li&gt;Templates should be specified in proper metalanguage instead of "yet another hack on top of C syntax" and translated into normal C code during compilation.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;And for extra sugar - allow overloading the virtual method call operation. So I can actually subclass object systems from other languages without writing countless ugly wrapper classes.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_DBO6l3gpqrg/TNcB2vAfaYI/AAAAAAAAEck/QBgTIlJHTcU/s1600/Ichiro+forest.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://1.bp.blogspot.com/_DBO6l3gpqrg/TNcB2vAfaYI/AAAAAAAAEck/QBgTIlJHTcU/s400/Ichiro+forest.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Obligatory screenshot - Ichiro having break in a forest (needs better lighting actually)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-5556232508166141180?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/5556232508166141180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/11/about-c.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5556232508166141180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/5556232508166141180'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/11/about-c.html' title='About C++'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_DBO6l3gpqrg/TNcB2vAfaYI/AAAAAAAAEck/QBgTIlJHTcU/s72-c/Ichiro+forest.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-6778599417248416750</id><published>2010-10-26T14:12:00.001+03:00</published><updated>2011-02-22T01:38:27.186+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='poser'/><category scheme='http://www.blogger.com/atom/ns#' term='daz'/><category scheme='http://www.blogger.com/atom/ns#' term='chibibel'/><title type='text'>Chibibel</title><content type='html'>&lt;a href="http://www.daz3d.com/i/shop/itemdetails?item=4676"&gt;Chibibel&lt;/a&gt; is a character by &lt;a href="http://www.daz3d.com/"&gt;DAZ3D&lt;/a&gt;.&lt;br /&gt;Poser cr2 (character rigging) files are quite heterogenous. So until now Chibibel has been the main reference implementation for cr2 importer.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: small;"&gt;Things implemented so far:&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Parsing actor tree with channels. Actors (or props) are just a fancy name for bones (or frames) - i.e. positioned and transformed controllers for skin data. Channels are elementary property controllers - like individual rotation angles, morphs and so on. The main problem is, that the whole cr2 file format is mostly just (text based) dump of internal Poser controller structure as opposed to properly designed file format, so it contains many duplicate entries and meaningless data.&lt;/li&gt;&lt;li&gt;Twist and joint bend zones (no welds yet). Poser is special because the bending of joints is controlled with different weights for each main rotation axis - and these are specified algorithmically by line segments, angles and spheres. I am somewhat sceptical whether this would allow more natural movements - the weight system with extra bones and constraints should IMHO be easier and more powerful. But maybe it would be too difficult to set up for 3D modeling novices...&lt;/li&gt;&lt;li&gt;Welding obj meshes. Obj files cut meshes to groups, that cannot have interconnections. So cr2 file specifies, which groups should have their identically positioned vertices merged. &lt;/li&gt;&lt;li&gt;Loading textures from image files. Poser has quite complex material system, but currently Khayyam only looks it for diffuse texture entry and loads it as plain texture. Hopefully more is coming.&lt;/li&gt;&lt;li&gt;Conforming objects can be dropped to the same figure object as body. They behave surprisingly well, moving and bending with skeleton. But morphs cannot currently be linked, so if clothing has conforming morphs, they have to be adjusted manually.&lt;/li&gt;&lt;li&gt;Morphs work, but their values are not saved in XML data.&lt;/li&gt;&lt;li&gt;Props can be loaded but cannot be directly associated with bones. Props is (another) Poser fancy name for immutable objects (like tools, buildings etc).&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;a href="http://www.kuroyumes-developmentzone.com/poser/poserfilespec/basicstructure.html"&gt;Kuroyume Unoffcial CR2 File Specification&lt;/a&gt; has been invaluable resource for digging through the cryptic character data of Poser files.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;img border="0" height="300" src="http://2.bp.blogspot.com/_DBO6l3gpqrg/TMZxhG8pvnI/AAAAAAAAEaU/nxy9Wdizjmc/s400/Chibibel+selling+pottery.jpg" style="margin-left: auto; margin-right: auto;" width="400" /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Obligatory screenshot - Chibibel selling pottery in market (rendered with POV-Ray)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_DBO6l3gpqrg/TMZxhG8pvnI/AAAAAAAAEaU/nxy9Wdizjmc/s1600/Chibibel+selling+pottery.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-6778599417248416750?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/6778599417248416750/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/10/chibibel.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6778599417248416750'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6778599417248416750'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/10/chibibel.html' title='Chibibel'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_DBO6l3gpqrg/TMZxhG8pvnI/AAAAAAAAEaU/nxy9Wdizjmc/s72-c/Chibibel+selling+pottery.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-3582441982096168259</id><published>2010-10-16T19:40:00.000+03:00</published><updated>2010-10-16T19:40:15.613+03:00</updated><title type='text'>First steps with Poser(TM) figures</title><content type='html'>Khayyam can now load (no import dialog yet) some basic Poser figures. They are not even close to renderable status yet, but things are becoming more interesting now:&lt;br /&gt;- Basic morphing works&lt;br /&gt;- Skeleton transforms poses as twist/joint/joint angles to body&lt;br /&gt;- Blending angles are implemented&lt;br /&gt;- Spherical falloff zones work (somewhat)&lt;br /&gt;- Color values are parsed from materials&lt;br /&gt;&lt;br /&gt;The next things I am working on currently:&lt;br /&gt;- Loading conforming meshes (like hair and clothes)&lt;br /&gt;- Implementing bulges&lt;br /&gt;- Welding vertices&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;img border="0" height="310" src="http://3.bp.blogspot.com/_DBO6l3gpqrg/TLnVCZqmGPI/AAAAAAAAEYs/NAWLbz5h8_8/s400/Khayyam-chibibel-20101016.jpg" style="margin-left: auto; margin-right: auto;" width="400" /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Obligatory screenshot - chibibel from DAZ3D&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_DBO6l3gpqrg/TLnVCZqmGPI/AAAAAAAAEYs/NAWLbz5h8_8/s1600/Khayyam-chibibel-20101016.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-3582441982096168259?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/3582441982096168259/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/10/first-steps-with-posertm-figures.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3582441982096168259'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/3582441982096168259'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/10/first-steps-with-posertm-figures.html' title='First steps with Poser(TM) figures'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_DBO6l3gpqrg/TLnVCZqmGPI/AAAAAAAAEYs/NAWLbz5h8_8/s72-c/Khayyam-chibibel-20101016.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-2927591348913284347</id><published>2010-10-08T22:57:00.000+03:00</published><updated>2010-10-08T22:57:08.516+03:00</updated><title type='text'>Khayyam architecture</title><content type='html'>Khayyam is a collection of handful libraries plus shell application written in Gtk+ toolkit. Most components come from Sourceforge Floyd project, but core components are borrowed from Sodipodi. The most basic building blocks are&lt;br /&gt;&lt;br /&gt;1. libarikkei (Sodipodi)&lt;br /&gt;This is a collection of utility libraries for file handling, unicode parsing, text file tokenizing and similar things. It has both C and C++ versions of some most basic functions and is implemented as small, self-contained library that can be directly included into project.&lt;br /&gt;&lt;br /&gt;2. elea (Floyd)&lt;br /&gt;This is (yet another) 3D geometry library written in C++. It is meant to be small, so only the subset of functions used in Khayyam (and some other) are implemented. Again, it is self-contained and can be directly included into project.&lt;br /&gt;&lt;br /&gt;3. libnr (Sodipodi)&lt;br /&gt;This is small, self-contained, anti-aliased 2D rendering library.&lt;br /&gt;&lt;br /&gt;4. libnrtype (Sodipodi)&lt;br /&gt;Compact text rendering library on top of libnr&lt;br /&gt;&lt;br /&gt;5. sehle (Floyd)&lt;br /&gt;3D engine for OpenGL 2, using libelea for algebra. It is meant to be mostly bare-bones layer, implementing only the most basic stuff, like vertex buffers, textures, display list and so on. It should be quite flexible regards to rendering pipeline.&lt;br /&gt;&lt;br /&gt;6. thera (Floyd)&lt;br /&gt;Small and compact DOM-like library for XML parsing. It implements basic parser and writer on top of libxml, node tree, basic mutation callbacks and transaction system for rollback (undo and redo).&lt;br /&gt;&lt;br /&gt;7. miletos (Floyd)&lt;br /&gt;Fat scene graph library on top of thera, sehle and elea. Implements all more complex things like various file format parsers, skeletal animations and so on. It borrows the basic design concept from Sodipodi (although is implemented in C++ instead of C) - it is written as typed object tree, parallel to the purely untyped tree of thera. Most editing can be done either directly for temporary editing, or by customizing thera nodes or attributes for permanend modifications.&lt;br /&gt;&lt;br /&gt;8. khayyam (Floyd)&lt;br /&gt;This is the application itself.&lt;br /&gt;&lt;br /&gt;This is only the most basic overview. Next time I hope to write some more details.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;img border="0" height="300" src="http://2.bp.blogspot.com/_DBO6l3gpqrg/TKmE9dcBlDI/AAAAAAAAEYM/xdEfPuOMuyE/s400/Esk-and-Litis-1024.jpg" style="margin-left: auto; margin-right: auto;" width="400" /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Obligatory screenshot (Des Blood 4 models, Digital Girl background, rendered with POV-Ray)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_DBO6l3gpqrg/TKmE9dcBlDI/AAAAAAAAEYM/xdEfPuOMuyE/s1600/Esk-and-Litis-1024.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-2927591348913284347?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/2927591348913284347/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/10/khayyam-architecture.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2927591348913284347'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/2927591348913284347'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/10/khayyam-architecture.html' title='Khayyam architecture'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_DBO6l3gpqrg/TKmE9dcBlDI/AAAAAAAAEYM/xdEfPuOMuyE/s72-c/Esk-and-Litis-1024.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-783893035873019493.post-6721481924859466985</id><published>2010-10-04T16:51:00.000+03:00</published><updated>2010-10-04T16:51:48.808+03:00</updated><title type='text'>What is Khayyam?</title><content type='html'>Khayyam is a frontend tool to my experimental set of 3D (and related) libraries.&lt;br /&gt;&lt;span style="font-size: small;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;img border="0" height="300" src="http://4.bp.blogspot.com/_DBO6l3gpqrg/TKmE9RkstPI/AAAAAAAAEX8/Y7VLx2bBf2E/s400/Esk-1024.jpg" style="margin-left: auto; margin-right: auto;" width="400" /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;A scene composed in Khayyam and rendered in POV-Ray&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_DBO6l3gpqrg/TKmE9RkstPI/AAAAAAAAEX8/Y7VLx2bBf2E/s1600/Esk-1024.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style="font-size: small;"&gt;In current incarnation it cannot do much. Still you can import some model formats, place and pose these, and render resulting scene in POV-Ray. Plus it is free software. And works under both Windows and Linux.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: small;"&gt;A small overview of Khayyam can be found &lt;a href="http://floyd.sourceforge.net/index.html"&gt;here&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: small;"&gt;At moment I am working on Poser model format support. This will be the first step for Khayyam to become a serious rendering tool.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: small;"&gt; &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/783893035873019493-6721481924859466985?l=khayyam.kaplinski.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://khayyam.kaplinski.com/feeds/6721481924859466985/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://khayyam.kaplinski.com/2010/10/what-is-khayyam.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6721481924859466985'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/783893035873019493/posts/default/6721481924859466985'/><link rel='alternate' type='text/html' href='http://khayyam.kaplinski.com/2010/10/what-is-khayyam.html' title='What is Khayyam?'/><author><name>Lauris Kaplinski</name><uri>https://profiles.google.com/108101292210362191977</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh6.googleusercontent.com/-wvr8tfUwSTc/AAAAAAAAAAI/AAAAAAAAFPs/Qf4p8DW5Al4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_DBO6l3gpqrg/TKmE9RkstPI/AAAAAAAAEX8/Y7VLx2bBf2E/s72-c/Esk-1024.jpg' height='72' width='72'/><thr:total>0</thr:total></entry></feed>
