{"id":55,"date":"2013-09-13T20:19:57","date_gmt":"2013-09-13T20:19:57","guid":{"rendered":"https:\/\/blogs.scummvm.org\/richiesams\/?p=55"},"modified":"2022-05-22T20:23:36","modified_gmt":"2022-05-22T20:23:36","slug":"i-never-knew-moving-a-lever-could-be-so-hard","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/richiesams\/2013\/09\/13\/i-never-knew-moving-a-lever-could-be-so-hard\/","title":{"rendered":"I never knew moving a lever could be so hard"},"content":{"rendered":"<p>In the Zork games, there are switches\/twist knobs\/turn-tables:<\/p>\n<div class=\"separator\">\n<div style=\"width: 480px;\" class=\"wp-video\"><!--[if lt IE 9]><script>document.createElement('video');<\/script><![endif]-->\n<video class=\"wp-video-shortcode\" id=\"video-55-1\" width=\"480\" height=\"360\" preload=\"metadata\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/blogs.scummvm.org\/richiesams\/wp-content\/uploads\/sites\/34\/2022\/05\/ZEngine-Control-example-h357Pmn1Gc.mp4?_=1\" \/><a href=\"https:\/\/blogs.scummvm.org\/richiesams\/wp-content\/uploads\/sites\/34\/2022\/05\/ZEngine-Control-example-h357Pmn1Gc.mp4\">https:\/\/blogs.scummvm.org\/richiesams\/wp-content\/uploads\/sites\/34\/2022\/05\/ZEngine-Control-example-h357Pmn1Gc.mp4<\/a><\/video><\/div>\n<\/div>\n<p>These are all controlled by the Lever control:<\/p>\n<pre class=\"brush:text\">control:624 lever {\r\n    descfile(knocker.lev)\r\n    cursor(handpt)\r\n}\r\n<\/pre>\n<p>The knocker.lev file looks like this:<\/p>\n<pre class=\"brush:text\">animation_id:631~\r\nfilename:te2ea21c.rlf~\r\nskipcolor:0~\r\nanim_coords:200 88 343 315~\r\nmirrored:1~\r\nframes:11~\r\nelsewhere:0 0 511 319~\r\nout_of_control:0 0 511 319~\r\nstart_pos:0~\r\nhotspot_deltas:42 39~\r\n0:241 252 D=1,90 ^=P(0 to 1) P(1 to 0) P(0 to 1) P(1 to 0) E(0)~\r\n1:234 260 D=2,90 D=0,270 ^=P(1 to 0) E(0)~\r\n2:225 258 D=3,90 D=1,270 ^=P(2 to 0) P(0 to 1) P(1 to 0) E(0)~\r\n3:216 255 D=4,90 D=2,270 ^=P(3 to 0) P(0 to 1) P(1 to 0) E(0)~\r\n4:212 234 D=5,90 D=3,270 ^=P(4 to 0) P(0 to 2) P(2 to 0) E(0)~\r\n5:206 213 D=6,90 D=4,270 ^=P(5 to 0) P(0 to 3) P(3 to 0) E(0)~\r\n6:212 180 D=7,90 D=5,270 ^=P(6 to 0) P(0 to 3) P(3 to 0) E(0)~\r\n7:214 147 D=8,90 D=6,270 ^=P(7 to 0) P(0 to 4) P(4 to 0) E(0)~\r\n8:222 114 D=9,90 D=7,270 ^=P(8 to 0) P(0 to 5) P(4 to 0) E(0)~\r\n9:234 106 D=10,90 D=8,270 ^=P(9 to 0) P(0 to 5) P(4 to 0) E(0)~\r\n10:234 98 D=9,270~<\/pre>\n<ul>\n<li>animation_id\u00a0is unused.<\/li>\n<li>filename refers\u00a0to the animation file used.<\/li>\n<li>skip color\u00a0is unused.<\/li>\n<li>anim_coords\u00a0refers to the location the control will be rendered<\/li>\n<li>mirrored\u00a0says that the reverse of the animation is appended to the end of the file. Ex: 0, 1, 2, 3, 3, 2, 1, 0<\/li>\n<li>frames\u00a0refers to how many animation frames there are (If mirrored = 1, frames = animationFile::frameCount \/ 2)<\/li>\n<li>elsewhere\u00a0is unused<\/li>\n<li>out_of_control\u00a0is unused<\/li>\n<li>start_pos\u00a0refers to the first animation frame used by the control<\/li>\n<li>hotspot_deltas\u00a0refers to the width and height of the hotspots used to grab a control with the mouse<\/li>\n<\/ul>\n<p>The last section is a bit tricky. It&#8217;s formatted like so:<\/p>\n<pre class=\"brush:text\">`\r\n[frameNumber]:[hotspotX] [hotspotY] D=[directionToFrame],[directionAngle] .....(potentially more directions) ^=P([from] to [to]) P([from] to [to]) ... (potentially more return paths) E(0)~\r\n \r\n<\/pre>\n<p>&nbsp;<\/p>\n<ul>\n<li>frameNumber\u00a0corresponds the animationFile frame that should be displayed when the lever is in that state<\/li>\n<li>hotspotX\u00a0is the X coordinate of the hotspot rectangle in which the user can grab the control<\/li>\n<li>hotspotY\u00a0is the Y coordinate of the hotspot rectangle in which the user can grab the control<\/li>\n<\/ul>\n<p>D refers to &#8220;Direction&#8221;. Let&#8217;s say we&#8217;re at frame 0. D=1,90 means: &#8220;To get to frame 1, the mouse needs to be moving at a 90 degree angle.&#8221; (I&#8217;ll cover how the angles work in a bit)<\/p>\n<p>P refers to &#8220;Path&#8221;. This is what frames should be rendered after the user lets go of a control. For example, lets say we let go of the knocker at frame 6. The .lev file reads: ^=P(6 to 0) P(0 to 3) P(3 to 0). This says to render every frame from 6 to 0, then every frame from 0 to 3, then every frame from 3 to 0. So written out:<\/p>\n<div>6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0<\/div>\n<p>This allows for some cool effects such as the knocker returning to the lowest position and bouncing as though it had gravity.<\/p>\n<p>So what is that angle I was talking about? It refers to the direction the mouse is moving while the user is holding down left mouse button.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blogs.scummvm.org\/richiesams\/wp-content\/uploads\/sites\/34\/2022\/05\/angle.png\" \/><\/p>\n<div class=\"separator\"><\/div>\n<p>So let&#8217;s go over a typical user interaction:<\/p>\n<ol>\n<li>User hovers over the control. The cursor changes to a hand.<\/li>\n<li>User presses down the left mouse button<\/li>\n<li>Test if the mouse is within the current frame&#8217;s hotspot<\/li>\n<li>If so, begin a drag:\n<ol>\n<li>Calculate the distance between the last mouse position and the current<\/li>\n<li>If over 64 (a heuristic), calculate the angle. (Only calculating the angle when we&#8217;re sufficiently far from the last mouse position saves calculations as well as makes the lever less &#8220;twitchy&#8221;<\/li>\n<li>Test the angle against the directions<\/li>\n<li>If one passes, render the new frame<\/li>\n<\/ol>\n<\/li>\n<li>User moves a couple more times<\/li>\n<li>User releases the left mouse button\n<ol>\n<li>Follow any return paths set out in the .lev file<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p>And that&#8217;s it! Let me know if you have any questions or comments. The full source code can be found\u00a0<a href=\"https:\/\/github.com\/RichieSams\/scummvm\/blob\/zengine\/engines\/zengine\/lever_control.h\" target=\"_blank\" rel=\"noopener\">here<\/a>\u00a0and\u00a0<a href=\"https:\/\/github.com\/RichieSams\/scummvm\/blob\/zengine\/engines\/zengine\/lever_control.cpp\" target=\"_blank\" rel=\"noopener\">here<\/a>. Until next time<\/p>\n<p>-RichieSams<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the Zork games, there are switches\/twist knobs\/turn-tables: These are all controlled by the Lever control: control:624 lever { descfile(knocker.lev) cursor(handpt) } The knocker.lev file looks like this: animation_id:631~ filename:te2ea21c.rlf~ skipcolor:0~ anim_coords:200 88 343 315~ mirrored:1~ frames:11~ elsewhere:0 0 511 319~ out_of_control:0 0 511 319~ start_pos:0~ hotspot_deltas:42 39~ 0:241 252 D=1,90 ^=P(0 to 1) P(1 [&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-55","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/55","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/comments?post=55"}],"version-history":[{"count":1,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/55\/revisions"}],"predecessor-version":[{"id":58,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/posts\/55\/revisions\/58"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/media?parent=55"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/categories?post=55"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/richiesams\/wp-json\/wp\/v2\/tags?post=55"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}