{"id":37,"date":"2013-07-08T17:03:38","date_gmt":"2013-07-08T17:03:38","guid":{"rendered":"https:\/\/blogs.scummvm.org\/t0by\/?p=37"},"modified":"2022-05-24T17:06:27","modified_gmt":"2022-05-24T17:06:27","slug":"rotation-made-stupid","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/t0by\/2013\/07\/08\/rotation-made-stupid\/","title":{"rendered":"Rotation made stupid"},"content":{"rendered":"<blockquote>\n<div>In many of the more relaxed civilizations on the Outer Eastern Rim of the Galaxy, the <i>Hitchhiker\u2019s Guide<\/i> has already supplanted the great <i>Encyclopaedia Galactica<\/i> as the standard repository of all knowledge and wisdom, for though it has many omissions and contains much that is apocryphal, or at least wildly inaccurate, it scores over the older, more pedestrian work in two important respects. First, it is slightly cheaper; and second, it has the words \u201cDON\u2019T PANIC\u201d inscribed in large friendly letters on its cover.<\/div>\n<div>\u2014Hitchhiker\u2019s Guide to the Galaxy<\/div>\n<\/blockquote>\n<p>This is post will go into some detail explaining how to actually rotate a sprite in practice.<br \/>\nIt\u2019s a good starting point if you are rolling your own rotozoom function.<br \/>\nIt has, naturally, zero academic value and I am also quite sure you can buy manuals that will give you a better idea for just a few dollars.<br \/>\nAnd you can always troll IRC.<br \/>\nBut still.<\/p>\n<p>Now, we assume a little knowledge of linear algebra.<br \/>\nYou can probably go ahead all the same if you haven\u2019t taken Algebra 101, but it makes much more sense if you have.<br \/>\nThere is countless algebra books which will teach you the basics. Try Strang\u2019s or Nicholson\u2019s.<\/p>\n<p>That said.<br \/>\nLet\u2019s dive in.<br \/>\nWell, a sprite is a matrix of pixels, all right?<br \/>\nTo rotate, zoom it and do all the nice things we want to do we need to <strong>match a pixel in the source matrix to a pixel in the destination matrix and copy its color<\/strong>.<br \/>\n\u201cThis goes here\u201d.<\/p>\n<p>So \u2013 <strong>look<\/strong> at the source pixel, <strong>calculate<\/strong> where it ends up and <strong>copy<\/strong> that color. <em>[well, almost, hold on].<\/em><\/p>\n<p>Copying a byte is something very simple and your language of choice already offers support for that.<br \/>\nCalculating the destination, well, we need a function for that.<br \/>\nSo we have:<\/p>\n<p>(x\u2019, y\u2019) = f(x, y, phi)<\/p>\n<p>don\u2019t we?<br \/>\nThere is the textbook formula for rotation:<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/6794b4de87caedcc56ae6b759bb33c88.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-39\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/6794b4de87caedcc56ae6b759bb33c88.png\" alt=\"\" width=\"169\" height=\"21\" \/><\/a><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/1d9a9eb59b0f60941af8aa9b09aa5a41.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-40\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/1d9a9eb59b0f60941af8aa9b09aa5a41.png\" alt=\"\" width=\"169\" height=\"21\" \/><\/a><\/p>\n<p>I\u2019m not giving you a proof or anything (hey, if books aren\u2019t enough you can easily trick a doctoral student into doing the job for you)- it <em>just works.<br \/>\n<\/em>We assume that rotation is carried out around the origin and that no zooming is involved.<\/p>\n<p>Zooming is actually rather trivial (just multiply by the desidred amount), rotating around a custom point basically means shifting the whole thing so that the hotspot is the new origin, then rotating as above, then shifting <em>again<\/em> so that the hotspot is where it was in the first place and everything else has moved.<br \/>\nOnce you\u2019ve got the simple case, it\u2019s rather simple, though.<br \/>\nSee also:<\/p>\n<p><strong>That\u2019s it. Now we can match a point in our destination sprite to a point in our source sprite.<\/strong><br \/>\nWell, this is <em>not quite <\/em>it.<br \/>\nYou see, while rotating <em>points<\/em> is a rather mundane matter, we are dealing with pixels, that is\u2026 we are working in the discrete.<br \/>\nAs you can see in the drawing below, P (x,y) in the destination sprite does not match exactly <em>any <\/em>pixel in the source.<\/p>\n<p>Which means that we must do some approximation.<br \/>\nWhat do we do? Well, we can either round to the nearest integer or interpolate.<br \/>\nInterpolation will be the subject of the next post, for the moment, we round.<br \/>\nWe basically want something like a).<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/mapsz-copy.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-41\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/mapsz-copy.jpg\" alt=\"\" width=\"800\" height=\"749\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/mapsz-copy.jpg 800w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/mapsz-copy-300x281.jpg 300w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/07\/mapsz-copy-768x719.jpg 768w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/a>This also means that it\u2019s wonderfully more practical to <strong>scan the destination<\/strong>, calculate where the destination pixel would end up and copy <em>that<\/em> instead of the other way around.<\/p>\n<p>Now, all we need to do is to scan the whole of the destination matrix and copy the correct color into each pixel.<br \/>\nA nice for() loop will do.<br \/>\nHere you are.<br \/>\nFirst in pseudocode:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">procedure rotate (angle, src, dst, zoom):\r\n  begin\r\n  invAngle = 2 * PI - angle; \/\/ We need to rotate in the opposite direction, remember?\r\n  for (y = 0; y &lt; dstH; y++):\r\n    for (x = 0; x &lt; dstW; x++):\r\n    begin\r\n    targX = ((x * cos(invAngle) - y * sin(invAngle))) * zoom\r\n    targY = ((x * sin(invAngle) + y * cos(invAngle))) * zoom\r\n    dst[x][y] = src[targX][targY]\r\n    end\r\n  end<\/pre>\n<p>And now for the actual code used in my patch.<br \/>\nIt is only marginally different in that it supports custom centering (omitted above for clarity) and works in degrees instead of radians because that\u2019s what the original Wintermute Engine does.<\/p>\n<pre>TransparentSurface *TransparentSurface::rotate(Common::Rect aSrcRect, TransformStruct transform) const {\r\nPoint32 newHotspot;\r\nRect32 rect = TransformTools::newRect(Rect32 (aSrcRect), transform, &amp;newHotspot);\r\nCommon::Rect srcRect(0, 0, (int16)(aSrcRect.right - aSrcRect.left), (int16)(aSrcRect.bottom - aSrcRect.top));\r\nCommon::Rect dstRect(0, 0, (int16)(rect.right - rect.left), (int16)(rect.bottom - rect.top));\r\n \r\nTransparentSurface *target = new TransparentSurface();\r\nassert(format.bytesPerPixel == 4);\r\n \r\nint dstW = dstRect.width();\r\nint dstH = dstRect.height();\r\n \r\ntarget-&gt;create((uint16)dstW, (uint16)dstH, this-&gt;format);\r\n \r\nuint32 invAngle = (360 - transform._angle) % 360;\r\nfloat invCos = cos(invAngle * M_PI \/ 180.0);\r\nfloat invSin = sin(invAngle * M_PI \/ 180.0);\r\n \r\nfor (int y = 0; y &lt; dstH; y++) {\r\nfor (int x = 0; x &lt; dstW; x++) {\r\nint x1 = x - newHotspot.x;\r\nint y1 = y - newHotspot.y;\r\n \r\nfloat targX = ((x1 * invCos - y1 * invSin)) * 100.0 \/ transform._zoom.x + srcRect.left;\r\nfloat targY = ((x1 * invSin + y1 * invCos)) * 100.0 \/ transform._zoom.y + srcRect.top;\r\n \r\ntargX += transform._hotspot.x;\r\ntargY += transform._hotspot.y;\r\n \r\nif (FAST_TRANSFORM) {\r\nnearestCopy(targX, targY, x, y, srcRect, dstRect, this, target);\r\n} else {\r\nbilinearCopy(targX, targY, x, y, srcRect, dstRect, this, target);\r\n}\r\n}\r\n}\r\nreturn target;\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In many of the more relaxed civilizations on the Outer Eastern Rim of the Galaxy, the Hitchhiker\u2019s Guide has already supplanted the great Encyclopaedia Galactica as the standard repository of all knowledge and wisdom, for though it has many omissions and contains much that is apocryphal, or at least wildly inaccurate, it scores over 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-37","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/37","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=37"}],"version-history":[{"count":2,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/37\/revisions"}],"predecessor-version":[{"id":42,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/37\/revisions\/42"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/media?parent=37"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/categories?post=37"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/tags?post=37"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}