# vCanvas Design Document ### Introduction vCamera serves as the backbone of vCanvas. Panning, zooming, and rotating are all actions that is affecting the camera view. Think of vCanvas in this way: it's a camera looking at a 2d plane, and what a user sees on his/her screen is the viewport of the camera. The canvas itself is not moving; it's the 2d plane. The camera is doing all the heavy work. --- ### Clarafications #### Consistent Coordinate System The current usecase for vCamera is only for vCanvas which is a custom element extending from HTML 5 canvas element. The coordintate system of the native canvas element is down for positive y and right for positive x. However, considering the future incorporation of vPhysics which uses the more common up for positive y and right for positive x coordinate system, the coordinate system of vCamera would be consistent with the one vPhysics adopted. All future library that involves coordinate system would be using this up for positive y and right for positive x coordinate system. The direction for the angle is CCW for positive and CW for negative. ![Screenshot 2023-12-19 at 1.02.17 PM](https://hackmd.io/_uploads/r1U1VjALp.png) #### Attributes of the vCamera ##### Position This is the position of the camera in the world space. Precisely, this is where the center of the camera view port is in world space. ##### Rotation This is the orientation angle of the camera. When it's 0 degree it's in the normal orientation. CCW is negative. ##### Zoom Level This is the zoom level of the camera. When it's at 1, 1 pixel in the view port is 1 unit in the world space. When it's greater than 1, the object in world space is enlarged. 1 pixel in view port is 1 / zoomlevel unit in world space. ##### View Port Width / Height This is essentially the width and height of the view port in pixel. Therefore, should be consistent with the clientWidth and clientHeight of the vCanvas element. ##### Max/Min Zoom Level (Optional) This is to limit the zoom level of the camera. ##### Translation Boundaries (Optional) This is to limit the position of the camera within bounds. ![Screenshot 2023-12-19 at 2.04.25 PM](https://hackmd.io/_uploads/BJ8_G2RLp.png) --- ### Problems to Solve #### Coordinate Conversion In order for vCanvas to be useful, user interactivity is much needed. A major portion of that would involve where the user clicks on. In other word, a conversion between the point clicked in screen (view port of the camera) and the actual world space is needed. Translation, rotation, and zoom would all affect the conversion. First of all, a set of coordinate system needed to be settled on for the camera view port. The same is chosen; up for positive y and right for positive x. The remaining question would be the placement of the origin. Currently, it placed at the bottom left corner of the vCanvas element. ![Screenshot 2023-12-19 at 1.14.28 PM](https://hackmd.io/_uploads/ryZpUi0Up.png) Second of all, the process of the conversion. Let's take a point where a pointerdown or mousedown event occurs. The event would contain information of the clicked point in `event.clientX` and `event.clientY`. This point is in the coordinate system of the entire view port of the browser (down is positive y, right is positive x, and the origin is at the top left corner of the view port). Here comes the first conversion; it's from the view port of the browser to the view port of the camera (the visible part of the vCanvas element). This is relatively simple since there is no rotation and zoom involved. `getBoundingClientRect()` provides everything needed for the calculation. The properties `bottom` and `left` of the returned object combined is the bottom left corner of the canvas element. If the element has padding or border they have to be taken into account. `left + border width + left padding = actual left` and `bottom - border width - bottom padding = actual bottom` This [getBoundingClientRect() MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) explains things in greater detail do check it out if interested. To get the coordinate in the view port space of the camera. ``` actualLeft = left + border width + left padding actualBottom = bottom - border width - bottom padding pointInViewPortX = event.clientX - actualLeft pointInViewPortY = event.clientY - actualBottom ``` Remeber the coordinate system chosen for the camera view port is up for positive y. Therefore, the y coordinate would have to be flipped to reflect this. ``` pointInViewPortX = event.clientX - actualLeft pointInViewPortY = -(event.clientY - actualBottom) ``` At this point, (no pun intended) the point is converted to the camera view port space. To get the point in the world space another conversion is needed. To do this the position of the camera (center in the camera viewport) in world space is needed. The relative position between the point in interest and the center of the camera viewport would preserve even in the world space. ``` // delta would be a vector pointing from the center of the view port to the point delta = center of view port in view port space - point in view port space rotated delta = rotate delta to align with camera rotation scaled delta = divide the rotated delta vector with the zoom level ``` The `scaled delta` vector is the relative position to the center of the view port (camera position in world space) to the interest point. Using this vector the point in world space can be obtained by adding the delta vector to the camera position. ``` point in world space = camera position + scaled delta ``` To convert from world space to view port space, the above process can be inverted to perform the conversion. --- ### Attributes (translation, rotation, zoomlevel) Manipulation through Direct Methods and Gesture Inputs from User There are two ways to control the position, rotation, and zoom level of the camera. 1. From Gesture: the change in attribute request is intiated from a direct user input from keyboard-mouse, trackpad, touch gestures. For example, two finger swipe on a trackpad or middle mouse drag would notify the camera to pan accordingly. 2. Indirect Method: the change is originated from a result of an indirect interaction from the user. For example, clicking a button to move the camera to a certain location. 3. Locking a camera to a moving or static object in the world space. If the locked object is moving the camera update its position, rotation, and zoom level to the object. While locked on to an object, the user can still rotate the camera and also zoom the camera at the same time lock the camera position to the object. The relationship between the 3 methods of controlling the attributes of the camera is shown in the figure below. ![Screenshot 2023-12-19 at 3.07.17 PM](https://hackmd.io/_uploads/HJ7EWTCUp.png) --- ### Camera Movement Transition To avoid abruptly changing the camera position, rotation, and zoom level. There is an option to add transition from the current state of the camera to the target state. The transition is available to all 3 attributes. The goal of this functionality is to be able to interpolate values in between the current value and the target value of the transitioning attribute. For all animations in webpage, rAF(requestAnimationFrame) is always the way to go. To go with the rAF the vCanvas element has a step function to update the values that need to be updated at each frame, to acheive the animation effect. vCamera's movement transition is of the similar concept. To transition the camera attribute, the value for the attribute at each frame is needed. At the initial frame of the animation the starting value is the current value and the target value is specified from user input. The values in between have to be interpolated or even extrapolated if using certain ease functions. The camera will also have a member method that steps the camera using rAF. The method takes in one argument which is the delta time since the method is last called or the current timestamp (let the step function calculate its own delta time). ### Restriction on Camera Movement There are times when the restriction of camera movement is needed. For instance, to restrict the lateral movement so that the panning would become something similar to scroll. Currently, there are a set of restrictions that is in place. The affect of the restrictions are only limited to direct gesture input. Setting the camera attribute through indirect method can still work under restrictions. - translation (both the x and y direction) - x translation (the absolute x direction would be affected by the current orientation of the camera. For instance, at 45 degrees the absolute x direction in the view port would appear to be slanted pointing to the top right corner) - y translation - relative x translation (regardless of the orientation of the camera lateral movement is restricted; this is essentially the x axis of the camera view port) - relative y translation - rotation - zoom ### Case study on UI interactions #### Excalidraw - Pan: - Keyboard and Mouse: Middle mouse button - Trackpad: Two finger swipe - Touch: There is a pan mode that can be selected on the side toolbar; once that's selected pan is one finger drag. - Zoom: - Keyboard and Mouse: Scroll wheel with control btn or the meta btn - Trackpad: Two finger pinch - Touch: Two finger pinch - Zoom behavior: Zoom at the mouse location #### Figma - Pan: - Keyboard and Mouse: Middle mouse button - Trackpad: Two finger pinch ` - Touch: TBD - Zoom: - Keyboard and Mouse: Scroll wheel with control btn or the meta btn - Trackpad: Two finger pinch - Touch: TBD - Zoom behavior: Zoom at the mouse location #### tl;draw - Pan: - Keyboard and Mouse: Middle mouse button - Trackpad: Two finger swipe - Touch: TBD - Zoom: - Keyboard and Mouse: Scroll wheel w/ control btn or the meta btn - Trackpad: Two finger pinch - Touch: TBD - Zoom behavior: Zoom at the mouse location #### Sidenote Seems like a lot of the current product that utilize a canvas element with pan and zoom functionality that supports both the keyboard mouse and trackpad at the same time compromises the zoom for the keyboard and mouse combination to have the user to hold down the control btn or meta btn. This is likely due to the fact that trackpad pan and zoom both trigger wheel event, and the distinction being that for pinch movement the event would be triggered with the control key being pressed. Currently, there are no definite way to differenciate the source of a wheel event; that is whether the event comes from a trackpad or a mouse. One telltale sign of this is that when a user scroll with a mouse, the canvas would be panned in the vertical direction. That is because the wheel event handlers for the case of a scroll from a mouse and the case of a trackpad two finger swipe movement have the same condition. These are the observations made from using figma and excalidraw. --- ### Contentious Issues/features #### Camera Transition driven by vAnimation There is another library that is being developed along side the vCamera and vCanvas called vAnimation. It's inspired by implementation of the camera transition. Currently, the camera transition is still handled by itself which is independent to the other animation like tasks relating to the canvas. This prohibit the usecase of binding the camera transition animation with other animation. That is one of the features that I would like to put it out for the vCanvas. The trade off being if I remove the camera transition and make it being driven by the vAnimation library is that the user would lose the ability to have camera transition out of the box for vCanvas. The possible way to mitigate this would be to come up with a template using the vAnimation and bundle it with the vCanvas and have an option for the user to switch from the current implementation to using the vAnimation. This would make vAnimation a dependency for vCanvas. However, this would not be a major concern. #### Optimizing the render process Currently, the vCanvas draws everything in the world regardless the position or other factors. There had not been any issue, thus I think this optimization is not a must for now. However, I would like to implement this optimization ASAP. I have an implementation in mind; I would like to jog it down now in case I forget. I have already severed the portion where the vCanvas renders things with the vCanvas element. There are currently no default way to draw or allow user to insert UI components into the element for it to render. This is because I want to de-couple how and what users draw or use the canvas. I would like to make handing over the step control to external function as the default way of driving the controls. That being said, if I would like to optimize the rendering process I would need to expose a method for the external function to know the current "range" of the camera view port. Better yet, provide a simple method to determine if the location of the object of interest is within the camer view port; whether it's viewable or not.