Offsets

In Bricklayer, an offset is a (positive or negative) quantity that is added to every cell coordinate before using that coordinate in a computation. In Level 2 (and Level 3), there are three Bricklayer functions that can be used to change offsets: setOffset2D, incOffset2D, and decOffset2D.

For the xz-plane, an offset is denoted by a pair of integers and has the form: (xOffset, zOffset). The value xOffset is an integer that will be added to the x value of every coordinate processed. The zOffset is an integer that will be added to the z value of every coordinate processed.

For example, if we use the offset (3,4) to process cell coordinates, we get the following coordinate translation:

  • Processing the coordinate (0,0) yields (0+3,0+4) which when simplified becomes (3,4)
  • Processing the coordinate (1,5) yields (1+3,5+4) which when simplified becomes (4,9)
  • Processing the coordinate (2,1) yields (2+3,1+4) which when simplified becomes (5,5)

The default value (i.e., initial value) of the offset used by Bricklayer is (0,0). However, the function setOffset2D can be used to change Bricklayer’s offset. Below is an example showing a setOffset2D function call.

setOffset2D (3,4);

 

The above call instructs Bricklayer to use (3,4) as its offset when processing cell coordinates.

One way to understand offsets is that they move the location of the coordinate (0,0), which is commonly referred to as the origin. From this perspective, offsets let you “move things around” without having to re-calculate coordinates. You can create all your LEGO artifacts using the origin as a point of reference and then use offsets to move around the artifacts you have created.

For example, suppose you want to build a circle having radius 10. If you define the center of this circle to be (0,0), then only a quarter of the circle will be created. However, if you first perform the following function call

setOffset2D (10,10);

 

then (assuming the virtual xz-plane is large enough) the function call circleXZ 10 RED (0,0); will create a circle having radius 10 that consists
of red 1×1 bricks and is centered at (10,10).

Example I

The Bricklayer program shown below contains two declarations for the nullary functions thing1 and thing2. Both function bodies contain put instructions that place bricks at the coordinates (0,0), (0,1), (1,1), and (1,0).

Consider the following sequence of calls to the nullary functions thing1 and thing2.

thing1(); thing(2); …, thing1(); thing2();

 

would simply create a LEGO artifact corresponding to the last call in the sequence, which in this case is thing2(). However, the offset used when creating an artifact can be changed so that “things” will be placed at different (non-overlapping) locations. The code below combines nullary function calls with setOffset2D function calls to create four things.

open Level_2;

fun thing1() =  11_01_a
    (
        put2D_2x2_BLUE(0,0);
        put2D_1x1_GREEN(1,0);
        put2D_1x1_GREEN(0,1)
    );

fun thing2() =
    (
        put2D_2x2_GREEN(0,0);
        put2D_1x1_BLUE(1,0);
        put2D_1x1_BLUE(0,1)    
    );

build2D(32,32); 

thing1(); (* default offset = (0,0) *)

setOffset2D(3,0);
thing2(); (* offset = (3,0) *)

setOffset2D(3,3);
thing1(); (* offset = (3,3) *)

setOffset2D(0,3);
thing2(); (* offset = (0,3) *)

show2D "thing";

 

Example II

Calls to Bricklayer functions that affect offsets (e.g., setOffset2D) can occur in the bodies of function declarations. The code below is a variation of the code in the previous example where (1) all function calls between build2D(32,32); and show2D “things”; have been placed in the body of the nullary function fourThings, and (2) the offsets have been changed slightly so that the “things” now touch each other.

open Level_2;

fun thing1() = 11_01_b
    (
        put2D_2x2_BLUE(0,0);
        put2D_1x1_GREEN(1,0);
        put2D_1x1_GREEN(0,1)
    );

fun thing2() =
    (
        put2D_2x2_GREEN(0,0);
        put2D_1x1_BLUE(1,0);
        put2D_1x1_BLUE(0,1)    
    );

fun fourThings() =
    (
        thing1(); (* default offset = (0,0) *)

        setOffset2D(2,0);
        thing2(); (* offset = (2,0) *)

        setOffset2D(2,2);
        thing1(); (* offset = (2,2) *)

        setOffset2D(0,2);
        thing2() (* offset = (0,2) *)
    );
    
build2D(32,32); 

fourThings();
(* offset = (0,2) *)

show2D "thing";

 

Example III

The function incOffset2D can be used to incrementally change the current value of Bricklayer’s offset. More specifically, incOffset2D adds values to Bricklayer’s offset. Similarly, the function decOffset2D subtracts values
from Bricklayer’s offset.

For example, suppose the current offset is (15,17), then the function call

incOffset2D (8,9);

 

will change the offset to (15+8,17+9) which when simplified becomes (23,26).

Again, suppose the current offset is (15,17), then the function call

decOffset2D (8,9);

 

will change the offset to (15-8,17-9) which when simplified becomes (7,8).

In contrast, if the current offset is (15,17), then the function call

setOffset2D (8,9);

 

will set the offset to (8,9).

Conceptually, the difference between incOffset2D/decOffset2D and setOffset2D, is that the functions incOffset2D/decOffset2D make relative
changes to the offset while setOffset2D makes absolute changes to the offset.

As we shall see in Example IV, code that makes relative changes to offsets is more expressive than code making absolute changes to offsets. The code in this example, however, simply shows how incOffset2D/decOffset2D can be used in the body of the function fourThings to create a LEGO artifact similar to the one shown in the previous example. An important thing to note about this example is that the offset at the beginning of the body of fourThings is equal-to the offset at the end of the body of fourThings. This means that, in the environment in which fourThings is called, the offset does not change as a result of a call to fourThings. Such properties are referred to as invariants.

When it comes to understanding code, offset invariants can be extremely important. For this reason, we will add the comment

(* this function is offset invariant *)

 

(or some similar comment) to all functions whose bodies make changes to offsets in a manner that is offset invariant.

open Level_2;

fun thing3() = 11_01_c
    (
        put2D_2x2_RED(0,0);
        put2D_1x1_BLACK(1,0);
        put2D_1x1_BLACK(0,1)
    );

fun thing4() =
    (
        put2D_2x2_BLACK(0,0);
        put2D_1x1_RED(1,0);
        put2D_1x1_RED(0,1)
    );

fun fourThings() =
    (
        (* beginning offset = (xOffset,zOffset) *)
        thing3(); 

        incOffset2D(2,0); 
        (* offset = (xOffset+2,zOffset) *)
        thing4(); 

        incOffset2D(0,2); 
        (* offset = (xOffset+2,zOffset+2) *)
        thing3(); 

        decOffset2D(2,0); 
        (* offset = (xOffset,zOffset+2) *)
        thing4(); 
        
        decOffset2D(0,2)
        (* ending offset = (xOffset,zOffset) *)
    );
    
build2D(32,32); 

fourThings();

show2D "things";

 

Example IV

In this example, a LEGO artifact is created consisting of sixteen “things”. The functions incOffset2D/decOffset2D are used in offset invariant ways to position things at various locations. Note that only eight incOffset2D/decOffset2D function calls are needed to place sixteen “things”. Creating the same LEGO artifact using only setOffset2D would require sixteen setOffset2D function calls.

  
open Level_2;

fun thing1() = 11_01_e
    (
        put2D_2x2_BLUE(0,0);
        put2D_1x1_GREEN(1,0);
        put2D_1x1_GREEN(0,1)
    );

fun thing2() = 
    (
        put2D_2x2_GREEN(0,0);
        put2D_1x1_BLUE(1,0);
        put2D_1x1_BLUE(0,1)
    );

fun thing3() = 
    (
        put2D_2x2_RED(0,0);
        put2D_1x1_BLACK(1,0);
        put2D_1x1_BLACK(0,1)
    );

fun thing4() =
    (
        put2D_2x2_BLACK(0,0);
        put2D_1x1_RED(1,0);
        put2D_1x1_RED(0,1)
    );

fun fourThings() =
    (
        (* this function is offset invariant *)
        thing1(); 

        incOffset2D(2,0); 
        thing3(); 

        incOffset2D(0,2); 
        thing2(); 

        decOffset2D(2,0); 
        thing4(); 
        
        decOffset2D(0,2)
    );
 
fun  sixteenThings() =
    (
        (* this function is offset invariant *)
        fourThings(); 

        incOffset2D(4,0); 
        (* offset = (xOffset+4,zOffset) *)
        (* offset = (xOffset+4,zOffset+4) *) 
        fourThings(); 

        decOffset2D(4,0); 
        (* offset = (xOffset,zOffset+4) *)
        
        fourThings();  
        
        decOffset2D(0,4)
    );

 
build2D(32,32); 

sixteenThings();

show2D "things";