gocolors3disometric

Coloring for 3D Isometric projection


The ask is, base on the following program https://github.com/adonovan/gopl.io/blob/master/ch3/surface/main.go

  1. Turn it to a web server and render the SVG as web page
  2. Color the SVG so that the peak is red and valley is blue

I've got the 1st part right for sure, and I think I got the 2nd part right but apparently not, yet I have no idea where I'm wrong. Please help.

package main

import (
    "fmt"
    "math"
    "net/http"
    "strconv"
)

const (
    cells   = 100         // number of grid cells
    xyrange = 30.0        // axis ranges (-xyrange..+xyrange)
    angle   = math.Pi / 6 // angle of x, y axes (=30°)
)

var height, width = 300, 600 // canvas size in pixels

var xyscale = width / 2 / xyrange  // pixels per x or y unit
var zscale = float64(height) * 0.4 // pixels per z unit

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

func main() {
    addr := ":8000"
    fmt.Printf("Visit\n  http://localhost%s/\n  http://localhost%[1]s/?height=600&width=1200\n", addr)

    //http server
    http.HandleFunc("/", handle)
    http.ListenAndServe(addr, nil)
}

func handle(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "image/svg+xml")
    if err := r.ParseForm(); err != nil {
        return
    }
    for k, v := range r.Form {
        if k == "height" {
            h, _ := strconv.Atoi(v[0])
            if h > 0 {
                height = h
            }
        }
        if k == "width" {
            w, _ := strconv.Atoi(v[0])
            if w > 0 {
                width = w
            }
        }
    }

    xyscale = width / 2 / xyrange
    zscale = float64(height) * 0.4
    fmt.Fprintf(w, "<svg xmlns='http://www.w3.org/2000/svg' "+
        "style='stroke: grey; stroke-width: 0.7' "+
        "width='%d' height='%d'>", width, height)
    for i := 0; i < cells; i++ {
        for j := 0; j < cells; j++ {
            ax, ay := corner(i+1, j)
            bx, by := corner(i, j)
            cx, cy := corner(i, j+1)
            dx, dy := corner(i+1, j+1)
            r, g, b := getColor(i, j)
            fmt.Fprintf(w, "<polygon points='%g,%g %g,%g %g,%g %g,%g' fill='#%x%x%x'/>\n",
                ax, ay, bx, by, cx, cy, dx, dy, r, g, b)
        }
    }
    fmt.Fprintf(w, "</svg>")
}

func corner(i, j int) (float64, float64) {
    // 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)

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

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

func getColor(i, j int) (int, int, int) {
    // Find point (x,y) at middle of corner of cell (i,j) to cell (i+1,j+1).
    x := xyrange * (float64(i)/cells + 0.5/cells - 0.5)
    y := xyrange * (float64(j)/cells + 0.5/cells - 0.5)

    // Compute surface height z.
    z := math.Hypot(x, y) // distance from (0,0)
    v := int(math.Sin(z)*127) + 128
    r := v
    g := 0
    b := 255 - v
    return r, g, b
}

Here is the result that I got:

enter image description here

NB, although the question seems to be for Go, but it is actually the getColor() algorithm that I'm asking about. You can understand/answer even if you don't write in Go.


Solution

  • Your code uses the format verb %x to print the hex values to the SVG's fill attribute:

    fmt.Fprintf(w, "<polygon points='%g,%g %g,%g %g,%g %g,%g' fill='#%x%x%x'/>\n",
                    ax, ay, bx, by, cx, cy, dx, dy, r, g, b)
    

    This causes some numbers like 0 and 1 to be formatted with one hex digit. For example RGB (254, 0, 1) would be formatted as fe01. The browser then render colors incorrectly.

    Change the format verbs to %02x to ensure the RGB is always printed with two hex digits.

    Now RGB (254, 0, 1) is printed as fe0001, which is the correct hex color.

    Output:

    enter image description here