# Chapter 2. Drawing a line A line is a series of adjacent points. We will limit ourselves to two types of lines: Horizontal and vertical lines When drawing these lines point by point, just one coordinate changes, while the other stays constant. A point is an object consisting of x and y coordinates, and we define it and use it like this: ```nim import pixels type Point = object # 1 x: int # 2 y: int var p = Point(x: 5, y: 7) # 3 putPixel(p.x, p.y) # 4 ``` - 1 Defining a Point as an object. - 2 Coordinates x and y are integers. - 3 Variable p is an instance of a Point with coordinates (5, 7). - 4 We can access fields of an object with a dot. In this case, we access the x and y coordinates of the point p with p.x and p.y. ## 2.1. Drawing horizontal and vertical lines A line can be defined by a starting and an ending point, or by a starting point and its length and a direction. We’ll show both approaches. ## 2.1.1. Drawing a line using one point, length, direction We can define a line by its starting point, its length, and its direction. Having these two information, we can then draw a line pixel by pixel. A line length is a positive integer (a zero would be just a point, not a line; a negative value for the length is physically impossible). A direction can be either horizontal or vertical. This means that we will limit ourselves to always create lines from left to right (we specify the leftmost point of a line and then increase the x coordinate) or top to bottom (we increase the y coordinate). We can start by creating two similar procs: one for drawing horizontal lines, and one for drawing vertical lines. ```nim import pixels type Point = object x: int y: int proc drawHorizontalLine(start: Point; length: Positive) = # 1 for delta in 0 .. length: # 2 putPixel(start.x + delta, start.y) # 3 proc drawVerticalLine(start: Point; length: Positive) = for delta in 0 .. length: putPixel(start.x, start.y + delta) # 4 let a = Point(x: 60, y: 40) drawHorizontalLine(a, 50) # 5 drawVerticalLine(a, 30) # 6 ``` - 1 The length is defined as Nim’s built-in Positive type, which is a subtype of int, containing just positive numbers. - 2 The p..q is an (inline) iterator, which iterates from p to q (both ends are inclusive) in the ascending order, i.e. p must be smaller than q for the iteration to happen. In this case we’re iterating from zero to length. - 3 For a horizontal line, the y coordinate remains constant, and in each iteration step we draw a pixel one pixel to the right from the previous step. - 4 Similarly, for a vertical line, the x coordinate stays constant, and we increase the y coordinate to draw a new pixel. - 5 This will draw a horizontal line from (60, 40) to (110, 40). - 6 This will draw a vertical line from (60, 40) to (60, 70). We can have one proc, drawLine, which will draw both horizontal and vertical lines, depending on the provided user’s parameter. To model a set of a limited amount of values, we use the enum type. The benefits of this choice will be visible later, when we will see one of the features of Nim: exhaustiveness checking. We can now extend the previous example with these lines: ```nim type Direction = enum 1 Horizontal 2 Vertical proc drawLine(start: Point; length: Positive; direction: Direction) = 3 case direction 4 of Horizontal: drawHorizontalLine(start, length) 5 of Vertical: drawVerticalLine(start, length) drawLine(a, 50, Horizontal) 6 drawLine(a, 30, Vertical) ``` - 1 A Direction is an enumeration (enum) with two possible values. - 2 The values of a Direction are: Horizontal and Vertical. - 3 We now have an extra parameter of the Direction type. - 4 This is a case statement. It is similar to if statement, but more powerful. Not only we get separate branches for each case, we also have exhaustiveness checking: we cannot by accident leave out some cases. Concretely, we must account for all the enum values. - 5 We delegate the task of drawing lines to the previously defined procs. - 6 This will draw the same lines as in the example before. ## 2.1.2. Drawing a line by specifying start and end If we use two points to define a line, we don’t have to know (or calculate) the line length, and the direction can be inferred from the relation of these two points: if they have the same y coordinate, we are drawing a horizontal line, and a vertical line between two points with the same x coordinate. We will always iterate from a point with a smaller value of the changing coordinate, but we will allow our users to specify the points in any order. We can achieve this with recursion. ```nim proc drawHorizontalLine(a, b: Point) = # 1 if b.x < a.x: drawHorizontalLine(b, a) # 2 else: for x in a.x .. b.x: putPixel(x, a.y) # 3 proc drawVerticalLine(a, b: Point) = if b.y < a.y: drawVerticalLine(b, a) else: for y in a.y .. b.y: putPixel(a.x, y) let p = Point(x: 20, y: 20) q = Point(x: 50, y: 20) r = Point(x: 20, y: -10) drawHorizontalLine(p, q) # 4 drawVerticalLine(p, r) # 5 ``` - 1 Even though we already have a proc of the same name, there is no overriding: they are different procs because they have different parameters. - 2 Notice the reversed order of the arguments. - 3 The x is a changing variable, while the y remains constant. - 4 Draws a horizontal line between (20, 20) and (50, 20). - 5 Since r.y is smaller than p.y, this will in turn call drawVerticalLine(r, p) and draw a vertical line between (20, -10) and (20, 20).