{"id":15,"date":"2013-06-18T16:55:28","date_gmt":"2013-06-18T16:55:28","guid":{"rendered":"https:\/\/blogs.scummvm.org\/t0by\/?p=15"},"modified":"2022-05-24T16:59:34","modified_gmt":"2022-05-24T16:59:34","slug":"rotozoom-part-1","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/t0by\/2013\/06\/18\/rotozoom-part-1\/","title":{"rendered":"Rotozoom, Part 1"},"content":{"rendered":"<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/up_tooo.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-18\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/up_tooo.jpg\" alt=\"\" width=\"600\" height=\"450\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/up_tooo.jpg 800w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/up_tooo-300x225.jpg 300w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/up_tooo-768x576.jpg 768w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/a><\/p>\n<p>During the first couple days of GSOC I\u2019ve put some work on the so-called rotozoom functionality \u2013 that is, the engine\u2019s ability to take a sprite and stretch\/rotate it at will according to game scripts.<br \/>\nAs has been said before, this is a key ingredient in at least a couple of games, so it\u2019s the one which will benefit the end user the most.<\/p>\n<p><strong>a. The testbed<\/strong><\/p>\n<p>For starters, I\u2019ve cooked up a quick test-game for testing purpouses.<br \/>\nIt could be mistaken for a Wheel Of Fortune-inspired game, but in truth it\u2019s just a testbed.<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/scumm_roto.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-19\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/scumm_roto.png\" alt=\"\" width=\"816\" height=\"638\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/scumm_roto.png 816w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/scumm_roto-300x235.png 300w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/scumm_roto-768x600.png 768w\" sizes=\"auto, (max-width: 816px) 100vw, 816px\" \/><\/a>I have a pair of sprites that <em>should <\/em>behave like a protractor <em>if <\/em>the implementation works correctly.<\/p>\n<p><strong>b. The implementation\/1, or: issues for a basic rotozoom implementation: <\/strong><\/p>\n<p>Now, the rotozoom functionality requires that sprites be<br \/>\n1. Rotated around a custom point, henceforth <strong>hotspot<br \/>\n<\/strong>2. Rotated by any number of degrees<br \/>\n3. Stretched at will by any amount, independently on the X and Y-axis.<\/p>\n<p>Assuming that we have a magic function that performs the rotation and returns a rotated sprite (we almost have one, as we\u2019ll see later) what we have here is a problem of <strong>offsets.<\/strong><\/p>\n<p>Three of them.<\/p>\n<p>1. The hotspot\/transform offset:<\/p>\n<p>In Wintermute, any Sprite entity\u2019s position is relative to its hotspot.<br \/>\nWe\u2019ll call Sprite (with a capital S) the sprite entity in the WME editor and \u201csprite\u201d a regular sprite.<br \/>\nSo, where do we place the \u201cactual sprite\u201d?<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/asdfasdfasdfasdfasdf.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-20\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/asdfasdfasdfasdfasdf.png\" alt=\"\" width=\"571\" height=\"223\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/asdfasdfasdfasdfasdf.png 571w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/asdfasdfasdfasdfasdf-300x117.png 300w\" sizes=\"auto, (max-width: 571px) 100vw, 571px\" \/><\/a>We subtract the hotspot coordinates from the position given by WME, so that we obtain the coordinates of the (0,0) point of the sprite with respect to the viewport.<\/p>\n<p>This was a piece of cake, wasn\u2019t it?<\/p>\n<p>It is less cake-y when the image is stretched or rotated, since we must compute the transform for the hotspot location (aka: where the hotspot ends up) and the subtract *that* value.<\/p>\n<p>We use the standard formula for rotation for that:<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/f4e2a65035540283e6f42be992415789.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-21\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/f4e2a65035540283e6f42be992415789.png\" alt=\"\" width=\"173\" height=\"49\" \/><\/a>2. The bounding box offset.<br \/>\nIn a sprite we can\u2019t have pixels with negative coordinates.<br \/>\nThus, when we rotate a sprite, the resulting sprite is <em>taller and wider<\/em>,\u00a0 even if the original sprite represented a circular object (because, of course, the transform does not know anything about alpha).<br \/>\n<em>Any <\/em>point on it can (and most likely) ends up somewhere else in the resulting sprite, <em>even the origin, <\/em>which may or may not be the same as the hotspot.<br \/>\nHence, we need to compute the offset to apply to the origin, so that the rotation can appear to \u201cpivot\u201d around the origin (or the hotspot, but we\u2019ll come to it later).<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/rotate.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-22\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/rotate.png\" alt=\"\" width=\"547\" height=\"316\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/rotate.png 547w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/rotate-300x173.png 300w\" sizes=\"auto, (max-width: 547px) 100vw, 547px\" \/><\/a>How we compute this offset is the subject of a latter post.<\/p>\n<p>So this is what we do: we subtract these two two vectors from the Pos vector we get from the scripts and are thus able to position the resulting sprite in a way that appears to<em> rotate around the hotspot<\/em>.<\/p>\n<p>Suppose we want to stretch &amp; rotate the smiley we\u2019ve seen above around its eye.<br \/>\nThis is what we do:<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/big_fun1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-23\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/big_fun1.png\" alt=\"\" width=\"579\" height=\"424\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/big_fun1.png 579w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/06\/big_fun1-300x220.png 300w\" sizes=\"auto, (max-width: 579px) 100vw, 579px\" \/><\/a>Notice the purple vector (origin-eye): it stays the same \u2013 the hotspot must stay in the same place.<br \/>\nWe subtract the blue vector (the transform for the hotspot) from the purple vector, we subtract the green vector (the bounding box offset) and we get exactly where the resulting sprite must be positioned (the orange vector).<\/p>\n<p>More about the actual implementation w\/code snippets soon.<\/p>\n<p>That will be all for today, goodnight!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>During the first couple days of GSOC I\u2019ve put some work on the so-called rotozoom functionality \u2013 that is, the engine\u2019s ability to take a sprite and stretch\/rotate it at will according to game scripts. As has been said before, this is a key ingredient in at least a couple of games, so it\u2019s the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-15","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/15","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/comments?post=15"}],"version-history":[{"count":3,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/15\/revisions"}],"predecessor-version":[{"id":25,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/15\/revisions\/25"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/media?parent=15"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/categories?post=15"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/tags?post=15"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}