Source

Donovan and Kernighan, The Go Programming Language

Contents

Description

Exercise 3.1: If the function f returns a non-finite float64 value, the SVG file will contain invalid <polygon> elements (although many SVG renderers handle this gracefully). Modify the program to skip invalid polygons.

Exercise 3.3 Color each polygon based on its height, so that the peaks are colored red (ff0000) and the valleys blue (#0000ff).

My solution

Modifying original version given in §3.2 Floating Point Numbers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package main

import (
    "fmt"
    "math"
)

const (
    width, height = 600, 320            // canvas size in pixels
    cells         = 100                 // number of grid cells
    xyrange       = 30.0                // axis ranges (-xyrange..+xyrange)
    xyscale       = width / 2 / xyrange // pixels per x or y unit
    zscale        = height * 0.4        // pixels per z unit
    angle         = math.Pi / 6         // angle of x, y axes (=30°)
)

var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)

func main() {
    fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' " +
        "style='stroke: grey; fill: white; stroke-width: 0.7' " +
        "width='%d' height='%d'>", width, height)
    for i := 0; i < cells; i++ {
        for j := 0; j < cells; j++ {

            // Modified for Exercise 3.1 and 3.3.
            ax, ay, isPeak := corner(i+1, j); if ax < 1 || ay < 1 { continue }
            bx, by, _ := corner(i, j); if bx < 1 || by < 1 { continue }
            cx, cy, _ := corner(i, j+1); if cx < 1 || cy < 1 { continue }
            dx, dy, _ := corner(i+1, j+1); if dx < 1 || dy < 1 { continue }

            var fillclr string
            if isPeak {
                fillclr = "#0000ff"
            } else {
                fillclr = "#ff0000"
            }

            fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g' " +
                "style='fill:%s'/>\n",
                ax, ay, bx, by, cx, cy, dx, dy, fillclr)
        }
    }
    fmt.Println("</svg>")
}

func corner(i, j int) (float64, float64, bool) {

    // Find point (x, y) at corner of cell (i, j).
    x := xyrange * (float64(i) / cells - 0.5)
    y := xyrange * (float64(j) / cells - 0.5)

    // Compute surface height z.
    z := f(x, y)

    // Modified for Exercise 3.3.
    var zIsPos bool
    if z >= 0 {
        zIsPos = true
    } else {
        zIsPos = false
    }

    // Project (x, y, z) isometrically onto 2D SVG canvas (sx, sy).
    sx := width/2 + (x-y) * cos30 * xyscale
    sy := height/2 + (x+y) * sin30 * xyscale - z * zscale

    // Modified for Exercise 3.1.
    if math.IsInf(sx, 0) || math.IsInf(sy, 0) {
        return -1, -1, zIsPos
    }

    return sx, sy, zIsPos
}

func f(x, y float64) float64 {
    r := math.Hypot(x, y) // distance from (0, 0)
    return math.Sin(r) / r
}