--
--	g3dchess.occ -- 3D raster graphics routines, chess pieces
--	Copyright (C) 2009 Fred Barnes <frmb@kent.ac.uk>
--
--	This program is free software; you can redistribute it and/or modify
--	it under the terms of the GNU General Public License as published by
--	the Free Software Foundation; either version 2 of the License, or
--	(at your option) any later version.
--
--	This program is distributed in the hope that it will be useful,
--	but WITHOUT ANY WARRANTY; without even the implied warranty of
--	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--	GNU General Public License for more details.
--
--	You should have received a copy of the GNU General Public License
--	along with this program; if not, write to the Free Software
--	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
--

--** @module graphics3d

#INCLUDE "course.module"
#INCLUDE "maths.module"
#INCLUDE "sdlraster.module"
#INCLUDE "graphics3d.inc"
#INCLUDE "g3dchess.inc"
#USE "graphics3d"


--{{{  PROC g3dchess.debug.writer (CHAN BYTE in?, CHAN BOOL kill?)
--* Debugging routine.
-- @private
PROC g3dchess.debug.writer (CHAN BYTE in?, CHAN BOOL kill?)

  #PRAGMA EXTERNAL "PROC C.out.stderr (VAL []BYTE str) = 0"

  [512]BYTE buffer:
  INT buf.len:
  SEQ
    buf.len := 0
    #PRAGMA DEFINED buffer

    INITIAL BOOL running IS TRUE:
    WHILE running
      PRI ALT
        kill ? running
          running := FALSE
        BYTE ch:
        in ? ch
          INITIAL BOOL flush IS FALSE:
          SEQ
            IF
              ch = #00
                flush := TRUE
              buf.len = ((SIZE buffer) - 2)
                SEQ
                  buffer[buf.len] := ch
                  flush := TRUE
                  buf.len := buf.len + 1
              TRUE
                SEQ
                  buffer[buf.len] := ch
                  buf.len := buf.len + 1
                  IF
                    ch = '*n'
                      flush := TRUE
                    TRUE
                      SKIP

            IF
              flush AND (buf.len > 0)
                --{{{  flush buffer
                SEQ
                  C.out.stderr ([buffer FOR buf.len])
                  buf.len := 0
                --}}}
              TRUE
                SKIP
:
--}}}

--{{{  PROC chesspoints.generator.int (CHAN INT trigger?, VAL INT npoints, piece, VAL [][2]REAL32 profile, CHAN MOBILE []G3D.POINT3 out!, CHAN G3D.GEN.CONTROL ctrl?, CHAN BYTE scr!)
--* Chess piece points generator.
--
-- @param trigger Trigger channel.
-- @param npoints Number of points around the outside.
-- @param piece Particular piece (defines how the topping is handled, see [@ref G3D.CP]).
-- @param profile Y/rad pairs for the piece.
-- @param out Generated points.
-- @param ctrl Control channel.
-- @param scr Screen channel.
--
-- @private
PROC chesspoints.generator.int (CHAN INT trigger?, VAL INT npoints, piece, VAL [][2]REAL32 profile, CHAN MOBILE []G3D.POINT3 out!, CHAN G3D.GEN.CONTROL ctrl?, CHAN BYTE scr!)
  MOBILE []G3D.POINT3 cpnts:
  INT npnts:

  PROC reset () --{{{
    SEQ
      npnts := (npoints * (SIZE profile)) + 2
      cpnts := MOBILE [npnts]G3D.POINT3

      #PRAGMA DEFINED cpnts

      SEQ v = 0 FOR SIZE profile
        VAL [2]REAL32 pp IS profile[v]:
        INITIAL INT sp IS (v * npoints):
        SEQ
          INITIAL REAL32 apt IS (PI * 2.0) / (REAL32 ROUND npoints):
          SEQ i = 0 FOR npoints
            INITIAL REAL32 aoffs IS apt * (REAL32 ROUND i):
            SEQ
              cpnts[sp + i] := [pp[1] * SIN (aoffs), pp[0], pp[1] * COS (aoffs)]

      INITIAL INT sp IS (SIZE profile) * npoints:
      SEQ
        -- last two points are the bottom and top centres
        cpnts[sp + 0] := [0.0, profile[0][0], 0.0]
        cpnts[sp + 1] := [0.0, profile[(SIZE profile) - 1][0], 0.0]

        -- knight has its top offset a bit
        IF
          piece = G3D.CP.KNIGHT
            cpnts[sp + 1][x] := profile[(SIZE profile) - 2][1] * 1.8
          TRUE
            SKIP
  :
  --}}}

  SEQ
    --{{{  initialise
    reset ()

    --}}}

    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        PRI ALT
          ctrl ? CASE
            reset --{{{
              reset ()
            --}}}
            [3]REAL32 scale:
            add.scale; scale --{{{
              SEQ
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] * scale[0]
                    cpnts[i][y] := cpnts[i][y] * scale[1]
                    cpnts[i][z] := cpnts[i][z] * scale[2]
            --}}}
            [3]REAL32 scale:
            set.scale; scale --{{{
              SEQ
                reset ()
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] * scale[0]
                    cpnts[i][y] := cpnts[i][y] * scale[1]
                    cpnts[i][z] := cpnts[i][z] * scale[2]
            --}}}
            [3]REAL32 trans:
            add.trans; trans --{{{
              SEQ
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] + trans[0]
                    cpnts[i][y] := cpnts[i][y] + trans[1]
                    cpnts[i][z] := cpnts[i][z] + trans[2]
            --}}}
            [3]REAL32 trans:
            set.trans; trans --{{{
              SEQ
                reset ()
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] + trans[0]
                    cpnts[i][y] := cpnts[i][y] + trans[1]
                    cpnts[i][z] := cpnts[i][z] + trans[2]
            --}}}
          INT any:
          trigger ? any
            out ! CLONE cpnts
:
--}}}
--{{{  PROC chesspoints.generator (CHAN INT trigger?, VAL INT npoints, piece, VAL [][2]REAL32 profile, CHAN MOBILE []G3D.POINT3 out!, CHAN G3D.GEN.CONTROL ctrl?)
--* Chess piece points generator.
--
-- @param trigger Trigger channel.
-- @param npoints Number of points around the outside.
-- @param piece Particular piece (defines how the topping is handled, see [@ref G3D.CP]).
-- @param profile Y/rad pairs for the piece.
-- @param out Generated points.
-- @param ctrl Control channel.
--
PROC chesspoints.generator (CHAN INT trigger?, VAL INT npoints, piece, VAL [][2]REAL32 profile, CHAN MOBILE []G3D.POINT3 out!, CHAN G3D.GEN.CONTROL ctrl?)
  CHAN BYTE write:
  CHAN BOOL kill:
  PAR
    SEQ
      chesspoints.generator.int (trigger?, npoints, piece, profile, out!, ctrl?, write!)
      kill ! FALSE
    g3dchess.debug.writer (write?, kill?)
:
--}}}
--{{{  PROC chessboardpoints.generator (CHAN INT trigger?, VAL INT dim, VAL REAL32 width, height, CHAN MOBILE []G3D.POINT3 out!, CHAN G3D.GEN.CONTROL ctrl?, CHAN BYTE scr!)

--* Chess board points generator (helper).
-- @private
PROC chessboardpoints.generator.int (CHAN INT trigger?, VAL INT dim, VAL REAL32 width, height, CHAN MOBILE []G3D.POINT3 out!, CHAN G3D.GEN.CONTROL ctrl?, CHAN BYTE scr!) --{{{
  MOBILE []G3D.POINT3 cpnts:
  INT npnts:

  PROC reset () --{{{
    INT lpnt:
    SEQ
      npnts := ((dim + 1) * (dim + 1)) + 4
      cpnts := MOBILE [npnts]G3D.POINT3

      #PRAGMA DEFINED cpnts

      -- top grid of points
      SEQ j = 0 FOR dim + 1
        INITIAL REAL32 py IS ((REAL32 ROUND (j - (dim / 2))) * width):
        SEQ i = 0 FOR dim + 1
          INITIAL REAL32 px IS ((REAL32 ROUND (i - (dim / 2))) * width):
          SEQ
            cpnts[((dim + 1) * j) + i] := [px, -(height / 2.0), py]
      lpnt := (dim + 1) * (dim + 1)

      -- bottom corner points
      cpnts[lpnt + 0] := [((REAL32 ROUND (-(dim / 2))) * width), (height / 2.0), ((REAL32 ROUND (-(dim / 2))) * width)]
      cpnts[lpnt + 1] := [((REAL32 ROUND (dim - (dim / 2))) * width), (height / 2.0), ((REAL32 ROUND (-(dim / 2))) * width)]
      cpnts[lpnt + 2] := [((REAL32 ROUND (dim - (dim / 2))) * width), (height / 2.0), ((REAL32 ROUND (dim - (dim / 2))) * width)]
      cpnts[lpnt + 3] := [((REAL32 ROUND (-(dim / 2))) * width), (height / 2.0), ((REAL32 ROUND (dim - (dim / 2))) * width)]

      #IF FALSE
      SEQ v = 0 FOR npnts
        SEQ
          out.string ("point ", 0, scr!)
          out.int (v, 0, scr!)
          out.string (" = [", 0, scr!)
          out.real32 (cpnts[v][x], 0, 2, scr!)
          out.string (",", 0, scr!)
          out.real32 (cpnts[v][y], 0, 2, scr!)
          out.string (",", 0, scr!)
          out.real32 (cpnts[v][z], 0, 2, scr!)
          out.string ("]*n", 0, scr!)
      #ENDIF
  :
  --}}}

  SEQ
    --{{{  initialise
    reset ()

    --}}}

    INITIAL BOOL running IS TRUE:
    WHILE running
      SEQ
        PRI ALT
          ctrl ? CASE
            reset --{{{
              reset ()
            --}}}
            [3]REAL32 scale:
            add.scale; scale --{{{
              SEQ
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] * scale[0]
                    cpnts[i][y] := cpnts[i][y] * scale[1]
                    cpnts[i][z] := cpnts[i][z] * scale[2]
            --}}}
            [3]REAL32 scale:
            set.scale; scale --{{{
              SEQ
                reset ()
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] * scale[0]
                    cpnts[i][y] := cpnts[i][y] * scale[1]
                    cpnts[i][z] := cpnts[i][z] * scale[2]
            --}}}
            [3]REAL32 trans:
            add.trans; trans --{{{
              SEQ
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] + trans[0]
                    cpnts[i][y] := cpnts[i][y] + trans[1]
                    cpnts[i][z] := cpnts[i][z] + trans[2]
            --}}}
            [3]REAL32 trans:
            set.trans; trans --{{{
              SEQ
                reset ()
                SEQ i = 0 FOR npnts
                  SEQ
                    cpnts[i][x] := cpnts[i][x] + trans[0]
                    cpnts[i][y] := cpnts[i][y] + trans[1]
                    cpnts[i][z] := cpnts[i][z] + trans[2]
            --}}}
          INT any:
          trigger ? any
            out ! CLONE cpnts
:
--}}}


--* Chess board points generator.
--
-- @param trigger Trigger channel.
-- @param dim Number of squares along one edge.
-- @param width Width of each square.
-- @param height Height of the board.
-- @param out Generated points.
-- @param ctrl Control channel.
--
PROC chessboardpoints.generator (CHAN INT trigger?, VAL INT dim, VAL REAL32 width, height, CHAN MOBILE []G3D.POINT3 out!, CHAN G3D.GEN.CONTROL ctrl?)
  CHAN BYTE write:
  CHAN BOOL kill:
  PAR
    SEQ
      chessboardpoints.generator.int (trigger?, dim, width, height, out!, ctrl?, write!)
      kill ! FALSE
    g3dchess.debug.writer (write?, kill?)
:
--}}}
--{{{  PROC chessify.points (CHAN MOBILE []G3D.POINT2.3 in?, VAL INT npoints, piece, VAL [][2]REAL32 profile, CHAN MOBILE []G3D.POLY out!, CHAN G3D.PGN.CONTROL ctrl?, CHAN BOOL hide?)
--* Generates chess-style polygons from points.
--
-- @param in Points in.
-- @param npoints Number of points around the base of the object.
-- @param piece Particular piece (defines how the topping is handled, see [@ref G3D.CP]).
-- @param profile Profile for the piece.
-- @param out Polygons out.
-- @param ctrl Control channel.
-- @param hide Hide control channel.
--
PROC chessify.points (CHAN MOBILE []G3D.POINT2.3 in?, VAL INT npoints, piece, VAL [][2]REAL32 profile, CHAN MOBILE []G3D.POLY out!, CHAN G3D.PGN.CONTROL ctrl?, CHAN BOOL hide?)

  PROC set.poly.point (G3D.POLY p, VAL INT pi, VAL G3D.POINT2.3 pnt) --{{{
    SEQ
      p[x][pi] := pnt[x]
      p[y][pi] := pnt[y]
      p[z][pi] := pnt[z]
  :
  --}}}
  INT FUNCTION compute.poly.normal (VAL G3D.POLY poly) --{{{
    INT r:
    VALOF
      IF
        poly[npoints] = 4
          r := ((poly[y][3] MINUS poly[y][0]) TIMES (poly[x][1] MINUS poly[x][0])) MINUS
                ((poly[y][1] MINUS poly[y][0]) TIMES (poly[x][3] MINUS poly[x][0]))
        poly[npoints] = 3
          r := ((poly[y][2] MINUS poly[y][0]) TIMES (poly[x][1] MINUS poly[x][0])) MINUS
                ((poly[y][1] MINUS poly[y][0]) TIMES (poly[x][2] MINUS poly[x][0]))
        TRUE
          r := 0
      RESULT r
  :
  --}}}

  DATA TYPE PPNT IS [4]INT:

  [3]INT pixmap:
  MOBILE []PPNT polymap:
  INITIAL INT lpts IS npoints * (SIZE profile):
  INITIAL BOOL backfaces IS FALSE:
  INT npolys:
  BOOL pointy.top:
  INT polyid:
  BOOL hidden:
  SEQ
    --{{{  initialise
    polyid := 0
    hidden := FALSE

    -- need 'npoints' polygons for each side ((SIZE profile) - 1), plus 'npoints' at the top and bottom
    -- if the last profile point is a point, minus that
    IF
      profile[(SIZE profile) - 1][1] = 0.0
        SEQ
          pointy.top := TRUE
          npolys := npoints * (SIZE profile)
      TRUE
        SEQ
          pointy.top := FALSE
          npolys := npoints * ((SIZE profile) + 1)

    polymap := MOBILE [npolys]PPNT

    -- bottom polygons
    SEQ i = 0 FOR npoints
      SEQ
        polymap[i] := [lpts, i, (i+1) \ npoints, -1]
    
    -- sides
    
    INT ilim:
    SEQ
      IF
        pointy.top
          ilim := (SIZE profile) - 2
        TRUE
          ilim := (SIZE profile) - 1

      SEQ i = 0 FOR ilim
        INITIAL INT sp IS ((i + 1) * npoints):
        INITIAL INT spnt IS (i * npoints):
        SEQ
          SEQ j = 0 FOR npoints
            SEQ
              polymap[sp + j] := [spnt + ((j + 1) \ npoints), spnt + j, (spnt + npoints) + j, (spnt + npoints) + ((j + 1) \ npoints)]

    -- top polygons
    INT pt.base:
    SEQ
      IF
        pointy.top
          pt.base := (SIZE profile) - 1
        TRUE
          pt.base := SIZE profile

      SEQ i = 0 FOR npoints
        INITIAL INT sp IS pt.base * npoints:
        INITIAL INT spnt IS (pt.base - 1) * npoints:
        SEQ
          polymap[sp + i] := [lpts + 1, spnt + ((i+1) \ npoints), spnt + i, -1]

    -- colouring
    pixmap := [#00A0A0A0, #00A0A0A0, #00A0A0A0]

    --}}}

    INITIAL BOOL running IS TRUE:
    WHILE running
      PRI ALT
        ctrl ? CASE
          reset --{{{
            SEQ
              backfaces := FALSE
          --}}}
          BOOL trimap:
          set.trimap; trimap --{{{
            SKIP
          --}}}
          set.backfaces; backfaces --{{{
            SKIP
          --}}}
          INT fn, col:
          set.face.colour; fn; col --{{{
            IF
              (fn >= 0) AND (fn < (SIZE pixmap))
                pixmap[fn] := col
              TRUE
                SKIP
          --}}}
          MOBILE []INT cols:
          set.face.colours; cols --{{{
            INT l:
            SEQ
              l := SIZE cols
              IF
                l >= (SIZE pixmap)
                  l := SIZE pixmap
                TRUE
                  SKIP
              SEQ i = 0 FOR l
                pixmap[i] := cols[i]
          --}}}
          set.polyid; polyid --{{{
            polyid := polyid /\ #FF
          --}}}
        hide ? hidden
          --{{{  hiding
          SKIP
          --}}}

        MOBILE []G3D.POINT2.3 pnts:
        in ? pnts
          --{{{  generate polygons
          IF
            (SIZE pnts) <> ((npoints * (SIZE profile)) + 2)
              SKIP
            hidden
              MOBILE []G3D.POLY polys:
              SEQ
                polys := MOBILE [0]G3D.POLY
                out ! polys
            TRUE
              MOBILE []G3D.POLY polys:
              INT npoly:
              SEQ
                IF
                  pointy.top
                    npoly := npoints * (SIZE profile)
                  TRUE
                    npoly := npoints * ((SIZE profile) + 1)

                polys := MOBILE [npoly]G3D.POLY

                SEQ i = 0 FOR npoly
                  INT np, norm:
                  SEQ
                    IF
                      polymap[i][3] = (-1)
                        np := 3
                      TRUE
                        np := 4

                    polys[i][npoints] := np

                    SEQ j = 0 FOR np
                      SEQ
                        set.poly.point (polys[i], j, pnts[polymap[i][j]])

                    norm := compute.poly.normal (polys[i])

                    polys[i][norm] := norm
                    polys[i][pixel] := pixmap[1]
                    polys[i][polyid] := polyid

                    IF
                      (NOT backfaces) AND (norm >= 0)
                        polys[i][npoints] := 0
                      TRUE
                        SKIP

                out ! polys
          --}}}
:
--}}}
--{{{  PROC chessboardify.points.int (CHAN MOBILE []G3D.POINT2.3 in?, VAL INT dim, CHAN MOBILE []G3D.POLY out!, CHAN G3D.PGN.CONTROL ctrl?, CHAN BYTE scr!)
--* Turns chessboard points into polygons (helper).
-- @private
PROC chessboardify.points.int (CHAN MOBILE []G3D.POINT2.3 in?, VAL INT dim, CHAN MOBILE []G3D.POLY out!, CHAN G3D.PGN.CONTROL ctrl?, CHAN BYTE scr!)

  PROC set.poly.point (G3D.POLY p, VAL INT pi, VAL G3D.POINT2.3 pnt) --{{{
    SEQ
      p[x][pi] := pnt[x]
      p[y][pi] := pnt[y]
      p[z][pi] := pnt[z]
  :
  --}}}
  INT FUNCTION compute.poly.normal (VAL G3D.POLY poly) --{{{
    INT r:
    VALOF
      r := ((poly[y][3] MINUS poly[y][0]) TIMES (poly[x][1] MINUS poly[x][0])) MINUS
            ((poly[y][1] MINUS poly[y][0]) TIMES (poly[x][3] MINUS poly[x][0]))
      RESULT r
  :
  --}}}

  DATA TYPE PPNT IS [4]INT:

  [3]INT pixmap:
  MOBILE []PPNT polymap:
  INITIAL INT npoints IS ((dim + 1) * (dim + 1)) + 4:
  INITIAL BOOL backfaces IS FALSE:
  INITIAL INT npolys IS (dim * dim) + 5:
  INT polyid:
  BOOL inc.polyid:
  SEQ

    --{{{  initialise
    polymap := MOBILE [npolys]PPNT
    polyid := 0
    inc.polyid := FALSE

    -- top faces
    SEQ j = 0 FOR dim
      INITIAL INT rp IS j * (dim + 1):
      INITIAL INT nrp IS (j + 1) * (dim + 1):
      SEQ i = 0 FOR dim
        INITIAL INT pno IS (j * dim) + i:
        SEQ
          polymap[pno] := [nrp + i, rp + i, rp + (i+1), nrp + (i+1)]
          --polymap[pno] := [nrp + (i+1), rp + (i+1), rp + i, nrp + i]

          #IF FALSE
          SEQ
            out.string ("poly ", 0, scr!)
            out.int (pno, 0, scr!)
            out.string (" = [", 0, scr!)
            SEQ v = 0 FOR 4
              SEQ
                out.int (polymap[pno][v], 0, scr!)
                IF
                  v = 3
                    out.string ("]", 0, scr!)
                  TRUE
                    out.string (",", 0, scr!)
            out.string ("*n", 0, scr!)

          #ENDIF

    -- other faces
    INT vp, ep:
    SEQ
      vp := (dim * dim)
      ep := ((dim+1) * (dim+1))

      polymap[vp + 0] := [dim, 0, ep, ep+1]
      polymap[vp + 1] := [ep-1, dim, ep+1, ep+2]
      polymap[vp + 2] := [ep-(dim+1),ep-1,ep+2,ep+3]
      polymap[vp + 3] := [0,ep-(dim+1),ep+3,ep]
      polymap[vp + 4] := [ep+2, ep+1, ep, ep+3]

    --}}}

    -- colouring
    pixmap := [#00204020, #00A0A0FF, #00101010]

    INITIAL BOOL running IS TRUE:
    WHILE running
      PRI ALT
        ctrl ? CASE
          reset --{{{
            SEQ
              backfaces := FALSE
          --}}}
          BOOL trimap:
          set.trimap; trimap --{{{
            SKIP
          --}}}
          set.backfaces; backfaces --{{{
            SKIP
          --}}}
          INT fn, col:
          set.face.colour; fn; col --{{{
            IF
              (fn >= 0) AND (fn < (SIZE pixmap))
                pixmap[fn] := col
              TRUE
                SKIP
          --}}}
          MOBILE []INT cols:
          set.face.colours; cols --{{{
            INT l:
            SEQ
              l := SIZE cols
              IF
                l >= (SIZE pixmap)
                  l := SIZE pixmap
                TRUE
                  SKIP
              SEQ i = 0 FOR l
                pixmap[i] := cols[i]
          --}}}
          set.polyid; polyid --{{{
            SEQ
              inc.polyid := (polyid /\ #100) <> 0
              polyid := polyid /\ #FF
          --}}}
        MOBILE []G3D.POINT2.3 pnts:
        in ? pnts
          --{{{  generate polygons
          IF
            (SIZE pnts) <> npoints
              SKIP
            TRUE
              MOBILE []G3D.POLY polys:
              INT ipid:
              SEQ
                polys := MOBILE [npolys]G3D.POLY
                ipid := polyid + 1

                -- main faces
                INITIAL INT rsel IS 0:
                SEQ j = 0 FOR dim
                  INITIAL INT csel IS rsel:
                  SEQ
                    SEQ i = 0 FOR dim
                      INITIAL INT pno IS (j * dim) + i:
                      INT norm:
                      SEQ
                        polys[pno][npoints] := 4

                        SEQ k = 0 FOR 4
                          set.poly.point (polys[pno], k, pnts[polymap[pno][k]])

                        norm := compute.poly.normal (polys[pno])

                        polys[pno][norm] := norm
                        polys[pno][pixel] := pixmap[csel]
                        polys[pno][polyid] := ipid
                        csel := 1 - csel

                        IF
                          (NOT backfaces) AND (norm >= 0)
                            polys[pno][npoints] := 0
                          TRUE
                            SKIP
                        IF
                          inc.polyid
                            ipid := ipid + 1
                          TRUE
                            SKIP
                    rsel := 1 - rsel

                -- sides
                SEQ i = (dim * dim) FOR 5
                  INT norm:
                  SEQ
                    polys[i][npoints] := 4

                    SEQ k = 0 FOR 4
                      set.poly.point (polys[i], k, pnts[polymap[i][k]])

                    norm := compute.poly.normal (polys[i])

                    polys[i][norm] := norm
                    polys[i][pixel] := pixmap[2]
                    polys[i][polyid] := polyid

                    IF
                      (NOT backfaces) AND (norm >= 0)
                        polys[i][npoints] := 0
                      TRUE
                        SKIP

                out ! polys
          --}}}
:
--}}}
--{{{  PROC chessboardify.points (CHAN MOBILE []G3D.POINT2.3 in?, VAL INT dim, CHAN MOBILE []G3D.POLY out!, CHAN G3D.PGN.CONTROL ctrl?)
--* Turns chessboard points into polygons.
--
-- @param in Points in.
-- @param dim Number of squares along one edge.
-- @param out Generated points.
-- @param ctrl Control channel.
--
PROC chessboardify.points (CHAN MOBILE []G3D.POINT2.3 in?, VAL INT dim, CHAN MOBILE []G3D.POLY out!, CHAN G3D.PGN.CONTROL ctrl?)
  CHAN BYTE write:
  CHAN BOOL kill:
  PAR
    SEQ
      chessboardify.points.int (in?, dim, out!, ctrl?, write!)
      kill ! FALSE
    g3dchess.debug.writer (write?, kill?)
:
--}}}

--{{{  PROC chesspiece.network (G3D.RENDER.ENGINE? rlink.in, VAL INT width, height, piece, npoints, bdim, VAL REAL32 sqw, bh, G3DC.PIECE.CTL? svr, G3D.SCENE.TRANS? sct)
--* Implements a basic chess piece (zdepth style).
--
-- @param rlink.in Render link.
-- @param width Width of raster.
-- @param height Height of raster.
-- @param piece Particular piece (see [@ref G3D.CP]).
-- @param npoints Number of points around the circumference.
-- @param bdim Board dimension.
-- @param sqw Board square width.
-- @param bh Board height.
-- @param svr Piece control channel-end.
-- @param sct Scene transform control channel-end.
--
PROC chesspiece.network (G3D.RENDER.ENGINE? rlink.in, VAL INT width, height, piece, npoints, bdim, VAL REAL32 sqw, bh, G3DC.PIECE.CTL? svr, G3D.SCENE.TRANS? sct)
  --{{{  piece profiles
  VAL [][2]REAL32 PROFILE.ROOK IS [[0.0, 0.5], [-0.2, 0.5], [-0.4, 0.4], [-1.2, 0.4], [-1.4, 0.5], [-1.8, 0.5], [-1.8, 0.3], [-1.6, 0.3]]:
  VAL [][2]REAL32 PROFILE.PAWN IS [[0.0, 0.4], [-0.2, 0.4], [-0.4, 0.2], [-1.0, 0.2], [-1.2, 0.4], [-1.4, 0.2]]:
  VAL [][2]REAL32 PROFILE.QUEEN IS [[0.0, 0.5], [-0.2, 0.5], [-0.4, 0.3], [-1.6, 0.3], [-1.7, 0.5], [-1.8, 0.4], [-1.6, 0.3], [-2.2, 0.1]]:
  VAL [][2]REAL32 PROFILE.KING IS [[0.0, 0.5], [-0.2, 0.5], [-0.4, 0.4], [-1.6, 0.4], [-1.7, 0.5], [-1.8, 0.1], [-2.2, 0.1], [-2.2, 0.3], [-2.3, 0.3], [-2.3, 0.1], [-2.5, 0.1]]:
  VAL [][2]REAL32 PROFILE.BISHOP IS [[0.0, 0.4], [-0.2, 0.4], [-0.4, 0.2], [-1.3, 0.2], [-1.4, 0.4], [-1.8, 0.4], [-2.1, 0.0]]:
  VAL [][2]REAL32 PROFILE.KNIGHT IS [[0.0, 0.4], [-0.2, 0.4], [-0.4, 0.2], [-1.1, 0.2], [-1.2, 0.3], [-1.4, 0.3], [-1.5, 0.2], [-1.7, 0.0]]:
  --}}}

  CHAN INT trigger:
  CHAN MOBILE []G3D.POLY pc1:
  CHAN MOBILE []G3D.POLY.SCAN ps1:
  CHAN G3D.PSC.CONTROL psc.ctl:

  CHAN MOBILE []G3D.POINT3 gp1, gp2, gp3:
  CHAN MOBILE []G3D.POINT2.3 gc1:
  CHAN G3D.TRN.CONTROL trn.ctl:
  CHAN G3D.PRJ.CONTROL prj.ctl:
  CHAN BOOL hide:
  PAR
    --{{{  generator
    CASE piece
      G3D.CP.ROOK
        chesspoints.generator (trigger?, npoints, piece, PROFILE.ROOK, gp1!, svr[gen.ctl]?)
      G3D.CP.PAWN
        chesspoints.generator (trigger?, npoints, piece, PROFILE.PAWN, gp1!, svr[gen.ctl]?)
      G3D.CP.QUEEN
        chesspoints.generator (trigger?, npoints, piece, PROFILE.QUEEN, gp1!, svr[gen.ctl]?)
      G3D.CP.KING
        chesspoints.generator (trigger?, npoints, piece, PROFILE.KING, gp1!, svr[gen.ctl]?)
      G3D.CP.BISHOP
        chesspoints.generator (trigger?, npoints, piece, PROFILE.BISHOP, gp1!, svr[gen.ctl]?)
      G3D.CP.KNIGHT
        chesspoints.generator (trigger?, npoints, piece, PROFILE.KNIGHT, gp1!, svr[gen.ctl]?)
    --}}}
    points3d.translate (gp1?, gp2!, trn.ctl?)
    points3d.transrotate.prj (gp2?, gp3!, sct?, svr[prj.ctl]?, prj.ctl!)
    points.3d.to.2d3 (gp3?, gc1!, prj.ctl?, width, height)
    --{{{  polygon-ify
    CASE piece
      G3D.CP.ROOK
        chessify.points (gc1?, npoints, piece, PROFILE.ROOK, pc1!, svr[pgn.ctl]?, hide?)
      G3D.CP.PAWN
        chessify.points (gc1?, npoints, piece, PROFILE.PAWN, pc1!, svr[pgn.ctl]?, hide?)
      G3D.CP.QUEEN
        chessify.points (gc1?, npoints, piece, PROFILE.QUEEN, pc1!, svr[pgn.ctl]?, hide?)
      G3D.CP.KING
        chessify.points (gc1?, npoints, piece, PROFILE.KING, pc1!, svr[pgn.ctl]?, hide?)
      G3D.CP.BISHOP
        chessify.points (gc1?, npoints, piece, PROFILE.BISHOP, pc1!, svr[pgn.ctl]?, hide?)
      G3D.CP.KNIGHT
        chessify.points (gc1?, npoints, piece, PROFILE.KNIGHT, pc1!, svr[pgn.ctl]?, hide?)
    --}}}
    --{{{  local handling for svr[in] (and state)
    INT bx, by:
    REAL32 rx, ry:
    BOOL moving:
    TIMER tim:
    INT t:
    REAL32 dx, dy:
    INT sleft:
    SEQ
      bx := 0
      by := 0
      rx := 0.0
      ry := 0.0

      tim ? t
      moving := FALSE
      dx := 0.0
      dy := 0.0
      sleft := 0

      INITIAL BOOL running IS TRUE:
      WHILE running
        PRI ALT
          svr[in] ? CASE
            set.pos; bx; by
              SEQ
                rx := (sqw * ((REAL32 ROUND (bdim - 1)) / 2.0)) - (sqw * (REAL32 ROUND bx))
                ry := (sqw * ((REAL32 ROUND (bdim - 1)) / 2.0)) - (sqw * (REAL32 ROUND by))

                trn.ctl ! set.3dshift; [rx, 0.0, ry]
            INT x, y:
            move.pos; x; y
              REAL32 nrx, nry:
              SEQ
                -- current position is rx,ry
                bx := x
                by := y
                nrx := (sqw * ((REAL32 ROUND (bdim - 1)) / 2.0)) - (sqw * (REAL32 ROUND bx))
                nry := (sqw * ((REAL32 ROUND (bdim - 1)) / 2.0)) - (sqw * (REAL32 ROUND by))
                dx := (nrx - rx) / 20.0
                dy := (nry - ry) / 20.0
                sleft := 20
                tim ? t
                moving := TRUE

            BOOL hidden:
            hide; hidden
              hide ! hidden

          moving & tim ? AFTER t
            SEQ
              rx := rx + dx
              ry := ry + dy
              sleft := sleft - 1
              IF
                sleft = 0
                  moving := FALSE
                TRUE
                  t := t PLUS 50000
              trn.ctl ! set.3dshift; [rx, 0.0, ry]

    --}}}

    poly.scanlines (pc1?, ps1!, psc.ctl?, width, height)
    render.polyscans.trigger (ps1?, rlink.in?, svr[rdr.ctl]?, trigger!, width, height)

    SEQ
      psc.ctl ! set.zcolour; TRUE

:
--}}}
--{{{  PROC chesspiece.init (G3DC.PIECE.CTL! ctl)
--* Initialises chess piece rendering settings.
-- @private
PROC chesspiece.init (G3DC.PIECE.CTL! ctl)
  SEQ
    ctl[prj.ctl] ! set.3dshift; [0.0, 0.0, 0.0]
    ctl[pgn.ctl] ! set.backfaces; FALSE
    ctl[gen.ctl] ! set.scale; [0.8, 1.0, 0.8]
    ctl[rdr.ctl] ! set.mode; G3D.RENDER.MODE.NONE
    ctl[pgn.ctl] ! set.polyid; 0
:
--}}}
--{{{  PROC chesspiece.network.spawn (VAL INT width, height, CHAN G3D.RENDER.ENGINE! new.link!, VAL INT piece, npoints, bdim, VAL REAL32 sqw, bh, RESULT G3DC.PIECE.CTL! pctl, RESULT G3D.SCENE.TRANS! sti)
--* Dynamically spawns a new 'chesspiece.network'.
--
-- @param width Raster width.
-- @param height Raster height.
-- @param new.link Channel to communicate new object link.
-- @param piece Particular piece (see [@ref G3D.CP]).
-- @param npoints Number of points around the circumference.
-- @param bdim Board dimension.
-- @param sqw Board square width.
-- @param bh Board height.
-- @param pctl Piece control channel-end (returned).
-- @param sti Returned scene transform client channel-end.
--
PROC chesspiece.network.spawn (VAL INT width, height, CHAN G3D.RENDER.ENGINE! new.link!, VAL INT piece, npoints, bdim, VAL REAL32 sqw, bh, RESULT G3DC.PIECE.CTL! pctl, RESULT G3D.SCENE.TRANS! sti)
  G3D.RENDER.ENGINE! rlink:
  G3D.RENDER.ENGINE? rsvr:
  G3DC.PIECE.CTL? psvr:
  G3D.SCENE.TRANS? sct:
  SEQ
    rsvr, rlink := MOBILE G3D.RENDER.ENGINE
    psvr, pctl := MOBILE G3DC.PIECE.CTL
    sct, sti := MOBILE G3D.SCENE.TRANS
    FORK chesspiece.network (rsvr?, width, height, piece, npoints, bdim, sqw, bh, psvr?, sct?)
    chesspiece.init (pctl!)
    new.link ! rlink
:
--}}}
--{{{  PROC chessboard.network (G3D.RENDER.ENGINE? rlink.in, VAL INT width, height, dim, VAL REAL32 sqw, bh, G3D.SCENE.TRANS? sct)
--* Implements a basic chess board (zdepth style).
--
-- @param rlink.in Render link.
-- @param width Width of raster.
-- @param height Height of raster.
-- @param dim Board dimension.
-- @param polyid Polygon id (0-255 or +256 for incrementing).
-- @param sqw Width of each square.
-- @param bh Board height.
-- @param sct Scene transform control.
--
PROC chessboard.network (G3D.RENDER.ENGINE? rlink.in, VAL INT width, height, dim, polyid, VAL REAL32 sqw, bh, G3D.SCENE.TRANS? sct)
  CHAN INT trigger:
  CHAN MOBILE []G3D.POINT3 gp1, gp2:
  CHAN MOBILE []G3D.POINT2.3 gc1:
  CHAN MOBILE []G3D.POLY pc1:
  CHAN MOBILE []G3D.POLY.SCAN ps1:
  CHAN G3D.GEN.CONTROL gen.ctl:
  CHAN G3D.PRJ.CONTROL prj.ctl1, prj.ctl2:
  CHAN G3D.PGN.CONTROL pgn.ctl:
  CHAN G3D.PSC.CONTROL psc.ctl:
  CHAN G3D.RDR.CONTROL rdr.ctl:
  PAR
    chessboardpoints.generator (trigger?, dim, sqw, bh, gp1!, gen.ctl?)
    points3d.transrotate.prj (gp1?, gp2!, sct?, prj.ctl1?, prj.ctl2!)
    points.3d.to.2d3 (gp2?, gc1!, prj.ctl2?, width, height)
    chessboardify.points (gc1?, dim, pc1!, pgn.ctl?)
    poly.scanlines (pc1?, ps1!, psc.ctl?, width, height)
    render.polyscans.trigger (ps1?, rlink.in?, rdr.ctl?, trigger!, width, height)

    SEQ
      gen.ctl ! set.trans; [0.0, bh / 2.0, 0.0]
      prj.ctl1 ! set.3dshift; [0.0, 0.0, 0.0]
      rdr.ctl ! set.mode; G3D.RENDER.MODE.NORM
      pgn.ctl ! set.backfaces; FALSE
      pgn.ctl ! set.polyid; polyid
      psc.ctl ! set.zcolour; FALSE
:
--}}}
--{{{  PROC chessboard.network.spawn (VAL INT width, height, CHAN G3D.RENDER.ENGINE! new.link!, VAL INT dim, VAL REAL32 sqw, bh, RESULT G3D.SCENE.TRANS! sti)
--* Spawns a new 'chessboard.network'.
--
-- @param width Raster width.
-- @param height Raster height.
-- @param new.link Channel to communicate new object link.
-- @param dim Board dimension.
-- @param polyid Polygon id (0-255 or +256 for incrementing).
-- @param sqw Size of each square.
-- @param bh Board height.
-- @param sti Returned scene-transform client channel-end.
--
PROC chessboard.network.spawn (VAL INT width, height, CHAN G3D.RENDER.ENGINE! new.link!, VAL INT dim, polyid, VAL REAL32 sqw, bh, RESULT G3D.SCENE.TRANS! sti)
  G3D.RENDER.ENGINE! rlink:
  G3D.RENDER.ENGINE? rsvr:
  G3D.SCENE.TRANS? sct:
  SEQ
    rsvr, rlink := MOBILE G3D.RENDER.ENGINE
    sct, sti := MOBILE G3D.SCENE.TRANS
    FORK chessboard.network (rsvr?, width, height, dim, polyid, sqw, bh, sct?)
    new.link ! rlink
:
--}}}


