func (fov *FOV) LightMap(lt Lighter, srcs []gruid.Point) []LightNode {
	if fov.Costs == nil {
		fov.Costs = make([]int, fov.Capacity)
	}
	for i := range fov.Costs {
		fov.Costs[i] = 0
	}
	for _, src := range srcs {
		if !src.In(fov.Rg) {
			continue
		}
		fov.Src = src
		fov.Costs[fov.idx(src)] = 1
		for d := 1; d <= lt.MaxCost(src); d++ {
			rg := fov.Rg.Intersect(gruid.NewRange(src.X-d, src.Y-d+1, src.X+d+1, src.Y+d))
			if src.Y+d < fov.Rg.Max.Y {
				for x := rg.Min.X; x < rg.Max.X; x++ {
					fov.lightUpdate(lt, src, gruid.Point{x, src.Y + d})
				}
			}
			if src.Y-d >= fov.Rg.Min.Y {
				for x := rg.Min.X; x < rg.Max.X; x++ {
					fov.lightUpdate(lt, src, gruid.Point{x, src.Y - d})
				}
			}
			if src.X+d < fov.Rg.Max.X {
				for y := rg.Min.Y; y < rg.Max.Y; y++ {
					fov.lightUpdate(lt, src, gruid.Point{src.X + d, y})
				}
			}
			if src.X-d >= fov.Rg.Min.X {
				for y := rg.Min.Y; y < rg.Max.Y; y++ {
					fov.lightUpdate(lt, src, gruid.Point{src.X - d, y})
				}
			}
		}
	}
	fov.computeLighted()
	return fov.Lighted
}
LightMap builds a lighting map with given light sources. It returs a cached
slice of lighted nodes. Values can also be consulted with At.
package rl
import (
	"bytes"
	"encoding/gob"
	"github.com/anaseto/gruid"
)
type FOV struct {
	innerFOV
}
type innerFOV struct {
	Costs         []int   
	ShadowCasting []bool  
	Lighted       []LightNode
	Visibles      []gruid.Point
	RayCache      []LightNode
	Rg            gruid.Range  
	Src           gruid.Point
	passable      func(gruid.Point) bool
	tiles         []gruid.Point
	Capacity      int
}
func NewFOV(rg gruid.Range) *FOV {
	fov := &FOV{}
	fov.Rg = rg
	max := fov.Rg.Size()
	fov.Capacity = max.X * max.Y
	return fov
}
func (fov *FOV) SetRange(rg gruid.Range) {
	fov.Rg = rg
	max := rg.Size()
	if max.X*max.Y <= fov.Capacity {
		return
	}
	nfov := NewFOV(rg)
	*fov = *nfov
}
func (fov *FOV) Range() gruid.Range {
	return fov.Rg
}
func (fov *FOV) GobDecode(bs []byte) error {
	r := bytes.NewReader(bs)
	gd := gob.NewDecoder(r)
	ifov := &innerFOV{}
	err := gd.Decode(ifov)
	if err != nil {
		return err
	}
	fov.innerFOV = *ifov
	return nil
}
func (fov *FOV) GobEncode() ([]byte, error) {
	buf := bytes.Buffer{}
	ge := gob.NewEncoder(&buf)
	err := ge.Encode(&fov.innerFOV)
	return buf.Bytes(), err
}
func (fov *FOV) At(p gruid.Point) (int, bool) {
	if !p.In(fov.Rg) || fov.Costs == nil {
		return 0, false
	}
	cost := fov.Costs[fov.idx(p)]
	if cost <= 0 {
		return cost, false
	}
	return cost - 1, true
}
func (fov *FOV) Visible(p gruid.Point) bool {
	if !p.In(fov.Rg) || fov.ShadowCasting == nil {
		return false
	}
	return fov.ShadowCasting[fov.idx(p)]
}
func (fov *FOV) idx(p gruid.Point) int {
	p = p.Sub(fov.Rg.Min)
	w := fov.Rg.Max.X - fov.Rg.Min.X
	return p.Y*w + p.X
}
func (fov *FOV) Iter(fn func(LightNode)) {
	for _, n := range fov.Lighted {
		fn(n)
	}
}
func (fov *FOV) IterSSC(fn func(p gruid.Point)) {
	for _, p := range fov.Visibles {
		fn(p)
	}
}
func sign(n int) int {
	var i int
	switch {
	case n > 0:
		i = 1
	case n < 0:
		i = -1
	}
	return i
}
func (fov *FOV) octantParents(ps []LightNode, src, p gruid.Point) []LightNode {
	q := src.Sub(p)
	r := gruid.Point{sign(q.X), sign(q.Y)}
	p0 := p.Add(gruid.Point{r.X, r.Y})
	c0 := fov.Costs[fov.idx(p0)]
	if c0 > 0 {
		ps = append(ps, LightNode{P: p0, Cost: c0})
	}
	switch {
	case q.X == 0 || q.Y == 0 || abs(q.X) == abs(q.Y):
	case abs(q.X) > abs(q.Y):
		p1 := p.Add(gruid.Point{r.X, 0})
		c1 := fov.Costs[fov.idx(p1)]
		if c1 > 0 {
			ps = append(ps, LightNode{P: p1, Cost: c1})
		}
	default:
		p1 := p.Add(gruid.Point{0, r.Y})
		c1 := fov.Costs[fov.idx(p1)]
		if c1 > 0 {
			ps = append(ps, LightNode{P: p1, Cost: c1})
		}
	}
	return ps
}
func (fov *FOV) From(lt Lighter, to gruid.Point) (LightNode, bool) {
	_, ok := fov.At(to)
	if !ok {
		return LightNode{}, false
	}
	ln := fov.from(lt, to)
	if ln.Cost == 0 {
		return LightNode{}, false
	}
	return ln, true
}
func (fov *FOV) from(lt Lighter, to gruid.Point) LightNode {
	var pnodesa [2]LightNode
	pnodes := pnodesa[:0]
	pnodes = fov.octantParents(pnodes, fov.Src, to)
	switch len(pnodes) {
	case 0:
		return LightNode{Cost: 0}
	case 1:
		n := pnodes[0]
		return LightNode{P: n.P, Cost: n.Cost + lt.Cost(fov.Src, n.P, to)}
	default:
		n := pnodes[0]
		m := pnodes[1]
		cost0 := n.Cost + lt.Cost(fov.Src, n.P, to)
		cost1 := m.Cost + lt.Cost(fov.Src, m.P, to)
		if cost0 <= cost1 {
			return LightNode{P: n.P, Cost: cost0}
		}
		return LightNode{P: m.P, Cost: cost1}
	}
}
type Lighter interface {
	Cost(src gruid.Point, from gruid.Point, to gruid.Point) int
	MaxCost(src gruid.Point) int
}
func (fov *FOV) VisionMap(lt Lighter, src gruid.Point) []LightNode {
	fov.Lighted = fov.Lighted[:0]
	if !src.In(fov.Rg) {
		return fov.Lighted
	}
	if fov.Costs == nil {
		fov.Costs = make([]int, fov.Capacity)
	}
	for i := range fov.Costs {
		fov.Costs[i] = 0
	}
	fov.Src = src
	fov.Costs[fov.idx(src)] = 1
	fov.Lighted = append(fov.Lighted, LightNode{P: src, Cost: 0})
	for d := 1; d <= lt.MaxCost(src); d++ {
		rg := fov.Rg.Intersect(gruid.NewRange(src.X-d, src.Y-d+1, src.X+d+1, src.Y+d))
		if src.Y+d < fov.Rg.Max.Y {
			for x := rg.Min.X; x < rg.Max.X; x++ {
				fov.visionUpdate(lt, src, gruid.Point{x, src.Y + d})
			}
		}
		if src.Y-d >= fov.Rg.Min.Y {
			for x := rg.Min.X; x < rg.Max.X; x++ {
				fov.visionUpdate(lt, src, gruid.Point{x, src.Y - d})
			}
		}
		if src.X+d < fov.Rg.Max.X {
			for y := rg.Min.Y; y < rg.Max.Y; y++ {
				fov.visionUpdate(lt, src, gruid.Point{src.X + d, y})
			}
		}
		if src.X-d >= fov.Rg.Min.X {
			for y := rg.Min.Y; y < rg.Max.Y; y++ {
				fov.visionUpdate(lt, src, gruid.Point{src.X - d, y})
			}
		}
	}
	return fov.Lighted
}
func (fov *FOV) visionUpdate(lt Lighter, src gruid.Point, to gruid.Point) {
	n := fov.from(lt, to)
	if n.Cost > 0 {
		fov.Costs[fov.idx(to)] = n.Cost
		fov.Lighted = append(fov.Lighted, LightNode{P: to, Cost: n.Cost - 1})
	}
}