--
--	g3dcube.occ -- simple test for graphics3d primitives
--	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
--

#INCLUDE "course.module"
#INCLUDE "sdlraster.module"
#INCLUDE "graphics3d.module"
#INCLUDE "maths.module"
#INCLUDE "ttyutil.module"
#INCLUDE "useful.module"

PROC display ()
  SKIP
:

PROC cube.network (G3D.RENDER.ENGINE? rlink.in, VAL INT width, height, CHAN G3D.GEN.CONTROL gen.ctl?,
                    CHAN G3D.ROT.CONTROL rot1.ctl?, rot2.ctl?, CHAN G3D.PRJ.CONTROL prj.ctl?, CHAN G3D.PGN.CONTROL pgn.ctl?,
                    CHAN G3D.RDR.CONTROL rdr.ctl?) --{{{
  CHAN INT trigger:
  CHAN MOBILE []G3D.POINT3 gp1, gp2, gp3:
  CHAN MOBILE []G3D.POINT2.3 gc1:
  CHAN MOBILE []G3D.POLY pc1:
  CHAN MOBILE []G3D.POLY.SCAN ps1:
  CHAN G3D.PSC.CONTROL psc.ctl:
  PAR
    cubepoints.generator (trigger?, gp1!, gen.ctl?) 
    points3d.rotate (gp1?, gp2!, rot1.ctl?)
    points3d.rotate (gp2?, gp3!, rot2.ctl?)
    points.3d.to.2d3 (gp3?, gc1!, prj.ctl?, G3D.DEFAULT.WIDTH, G3D.DEFAULT.HEIGHT)
    cubify.points (gc1?, pc1!, pgn.ctl?)
    poly.scanlines (pc1?, ps1!, psc.ctl?, G3D.DEFAULT.WIDTH, G3D.DEFAULT.HEIGHT)
    render.polyscans.trigger (ps1?, rlink.in?, rdr.ctl?, trigger!, G3D.DEFAULT.WIDTH, G3D.DEFAULT.HEIGHT)
:
--}}}


PROC real32.menu (VAL []BYTE name, TTYUTIL.MENUITEM? link, VAL REAL32 ival, min.val, max.val, delta, CHAN REAL32 out!) --{{{
  INITIAL REAL32 val IS ival:
  MOBILE []BYTE str:

  PROC set.string ()
    VAL [15]BYTE padding IS "               ":
    INITIAL INT plen IS (SIZE padding) - (SIZE name):
    SEQ
      format.sssr (name, [padding FOR plen], ": ", val, str)
  :

  SEQ
    set.string ()

    out ! val

    INITIAL BOOL running IS TRUE:
    WHILE running
      INITIAL BOOL update IS FALSE:
      INITIAL BOOL reset IS FALSE:
      SEQ
        link[in] ? CASE
          get.text
            update := TRUE
          key.left
            SEQ
              val := val - delta
              IF
                val < min.val
                  val := min.val
                TRUE
                  SKIP
              reset := TRUE
          key.right
            SEQ
              val := val + delta
              IF
                val > max.val
                  val := max.val
                TRUE
                  SKIP
              reset := TRUE
          INT v:
          key.other; v
            SKIP
          quit
            running := FALSE

        IF
          reset
            SEQ
              set.string ()
              out ! val
              update := TRUE
          TRUE
            SKIP

        IF
          update
            link[out] ! text; CLONE str
          TRUE
            SKIP
:
--}}}
PROC int.menu (VAL []BYTE name, TTYUTIL.MENUITEM? link, VAL INT ival, min.val, max.val, delta, CHAN INT out!) --{{{
  INITIAL INT val IS ival:
  MOBILE []BYTE str:

  PROC set.string ()
    VAL [15]BYTE padding IS "               ":
    INITIAL INT plen IS (SIZE padding) - (SIZE name):
    SEQ
      format.sssi (name, [padding FOR plen], ": ", val, str)
  :

  SEQ
    set.string ()

    out ! val

    INITIAL BOOL running IS TRUE:
    WHILE running
      INITIAL BOOL update IS FALSE:
      INITIAL BOOL reset IS FALSE:
      SEQ
        link[in] ? CASE
          get.text
            update := TRUE
          key.left
            SEQ
              val := val - delta
              IF
                val < min.val
                  val := min.val
                TRUE
                  SKIP
              reset := TRUE
          key.right
            SEQ
              val := val + delta
              IF
                val > max.val
                  val := max.val
                TRUE
                  SKIP
              reset := TRUE
          INT v:
          key.other; v
            SKIP
          quit
            running := FALSE

        IF
          reset
            SEQ
              set.string ()
              out ! val
              update := TRUE
          TRUE
            SKIP

        IF
          update
            link[out] ! text; CLONE str
          TRUE
            SKIP
:
--}}}
PROC bool.menu (VAL []BYTE name, TTYUTIL.MENUITEM? link, VAL BOOL ival, CHAN BOOL out!) --{{{
  INITIAL BOOL val IS ival:
  MOBILE []BYTE str:

  PROC set.string ()
    VAL [15]BYTE padding IS "               ":
    INITIAL INT plen IS (SIZE padding) - (SIZE name):
    SEQ
      format.sssb (name, [padding FOR plen], ": ", val, str)
  :

  SEQ
    str := MOBILE [40]BYTE
    set.string ()

    out ! val

    INITIAL BOOL running IS TRUE:
    WHILE running
      INITIAL BOOL update IS FALSE:
      INITIAL BOOL reset IS FALSE:
      SEQ
        link[in] ? CASE
          get.text
            update := TRUE
          key.left
            SEQ
              val := NOT val
              reset := TRUE
          key.right
            SEQ
              val := NOT val
              reset := TRUE
          INT v:
          key.other; v
            SKIP
          quit
            running := FALSE

        IF
          reset
            SEQ
              set.string ()
              out ! val
              update := TRUE
          TRUE
            SKIP

        IF
          update
            link[out] ! text; CLONE str
          TRUE
            SKIP
:
--}}}
PROC render.mode.menu (TTYUTIL.MENUITEM? link, CHAN G3D.RDR.CONTROL rdr.ctl!) --{{{  
  INITIAL INT val IS 0:
  MOBILE []BYTE str:

  PROC set.string ()
    VAL [15]BYTE padding IS "render-mode    ":
    VAL [4][10]BYTE names IS ["none      ", "glenz     ", "norm      ", "glenz+norm"]:
    SEQ
      IF
        (val >= 0) AND (val < (SIZE names))
          format.sss (padding, ": ", names[val], str)
        TRUE
          format.sss (padding, ": ", "invalid", str)
  :

  VAL []INT modes IS [G3D.RENDER.MODE.NONE, G3D.RENDER.MODE.GLENZ, G3D.RENDER.MODE.NORM, G3D.RENDER.MODE.GLENZ \/ G3D.RENDER.MODE.NORM]:
  SEQ
    set.string ()
    rdr.ctl ! set.mode; modes[val]

    INITIAL BOOL running IS TRUE:
    WHILE running
      INITIAL BOOL update IS FALSE:
      INITIAL BOOL reset IS FALSE:
      SEQ
        link[in] ? CASE
          get.text
            update := TRUE
          key.left
            SEQ
              IF
                val = 0
                  val := (SIZE modes) - 1
                TRUE
                  val := val - 1
              reset := TRUE
          key.right
            SEQ
              val := (val + 1) \ (SIZE modes)
              reset := TRUE
          INT v:
          key.other; v
            SKIP
          quit
            running := FALSE

        IF
          reset
            SEQ
              set.string ()
              rdr.ctl ! set.mode; modes[val]
              update := TRUE
          TRUE
            SKIP

        IF
          update
            link[out] ! text; CLONE str
          TRUE
            SKIP
:
--}}}
PROC rotate.mode.menu (TTYUTIL.MENUITEM? link, SHARED CHAN G3D.ROT.CONTROL rot.ctl!) --{{{  
  INITIAL INT val IS 0:
  MOBILE []BYTE str:

  PROC set.string ()
    VAL [15]BYTE padding IS "rotate-mode    ":
    VAL [9][4]BYTE names IS ["none", "demo", "X   ", "Y   ", "Z   ", "XY  ", "XZ  ", "YZ  ", "XYZ "]:
    SEQ
      IF
        (val >= 0) AND (val < (SIZE names))
          format.sss (padding, ": ", names[val], str)
        TRUE
          format.sss (padding, ": ", "invalid", str)
  :

  VAL []INT modes IS [G3D.ROTATE.MODE.NONE, G3D.ROTATE.MODE.DEMO, G3D.ROTATE.MODE.X, G3D.ROTATE.MODE.Y, G3D.ROTATE.MODE.Z,
                      G3D.ROTATE.MODE.X \/ G3D.ROTATE.MODE.Y, G3D.ROTATE.MODE.X \/ G3D.ROTATE.MODE.Z,
                      G3D.ROTATE.MODE.Y \/ G3D.ROTATE.MODE.Z, (G3D.ROTATE.MODE.X \/ G3D.ROTATE.MODE.Y) \/ G3D.ROTATE.MODE.Z]:
  SEQ
    set.string ()
    CLAIM rot.ctl!
      rot.ctl ! set.mode; modes[val]

    INITIAL BOOL running IS TRUE:
    WHILE running
      INITIAL BOOL update IS FALSE:
      INITIAL BOOL reset IS FALSE:
      SEQ
        link[in] ? CASE
          get.text
            update := TRUE
          key.left
            SEQ
              IF
                val = 0
                  val := (SIZE modes) - 1
                TRUE
                  val := val - 1
              reset := TRUE
          key.right
            SEQ
              val := (val + 1) \ (SIZE modes)
              reset := TRUE
          INT v:
          key.other; v
            SKIP
          quit
            running := FALSE

        IF
          reset
            SEQ
              set.string ()
              CLAIM rot.ctl!
                rot.ctl ! set.mode; modes[val]
              update := TRUE
          TRUE
            SKIP

        IF
          update
            link[out] ! text; CLONE str
          TRUE
            SKIP
:
--}}}

PROC menu.keyfilter (CHAN INT in?, CHAN TTYUTIL.MENU to.menu!, CHAN INT other!) --{{{
  WHILE TRUE
    INT key:
    SEQ
      in ? key
      CASE key
        KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT
          to.menu ! key.in; key
        ELSE
          other ! key
:
--}}}
PROC keys.sink (CHAN INT in?) --{{{
  WHILE TRUE
    INT v:
    in ? v
:
--}}}

PROC gen.scale.trans.handle ([3]CHAN REAL32 scale?, trans?, SHARED CHAN G3D.GEN.CONTROL out!) --{{{
  [3]REAL32 t.val, s.val:
  SEQ
    t.val := [1.0, 1.0, 1.0]
    s.val := [0.0, 0.0, 0.0]

    WHILE TRUE
      ALT
        ALT i = 0 FOR SIZE scale?
          scale[i] ? s.val[i]
            CLAIM out!
              SEQ
                out ! set.scale; s.val
                out ! add.trans; t.val
        ALT i = 0 FOR SIZE trans?
          trans[i] ? t.val[i]
            CLAIM out!
              SEQ
                out ! set.scale; s.val
                out ! add.trans; t.val
:
--}}}
PROC rot.angle.handle (CHAN REAL32 in?, SHARED CHAN G3D.ROT.CONTROL out!) --{{{
  WHILE TRUE
    REAL32 v:
    SEQ
      in ? v
      CLAIM out!
        out ! set.angle; v
:
--}}}
PROC rot.incr.handle (CHAN REAL32 in?, SHARED CHAN G3D.ROT.CONTROL out!) --{{{
  WHILE TRUE
    REAL32 v:
    SEQ
      in ? v
      CLAIM out!
        out ! set.increment; v
:
--}}}
PROC prj.scale.handle ([2]CHAN REAL32 in?, SHARED CHAN G3D.PRJ.CONTROL out!) --{{{
  [2]REAL32 scale:
  SEQ
    scale := [490.0, 420.0]

    WHILE TRUE
      ALT i = 0 FOR SIZE in?
        in[i] ? scale[i]
          CLAIM out!
            out ! set.scaling; scale
:
--}}}
PROC prj.zdepth.handle (CHAN REAL32 in?, SHARED CHAN G3D.PRJ.CONTROL out!) --{{{
  WHILE TRUE
    REAL32 v:
    SEQ
      in ? v
      CLAIM out!
        out ! set.zdepth; v
:
--}}}
PROC prj.2dshift.handle ([2]CHAN INT in?, SHARED CHAN G3D.PRJ.CONTROL out!) --{{{
  [2]INT shift:
  SEQ
    shift := [320, 240]

    WHILE TRUE
      ALT i = 0 FOR SIZE in?
        in[i] ? shift[i]
          CLAIM out!
            out ! set.2dshift; shift
:
--}}}
PROC prj.3dshift.handle ([3]CHAN REAL32 in?, SHARED CHAN G3D.PRJ.CONTROL out!) --{{{
  [3]REAL32 shift:
  SEQ
    shift := [0.0, 0.0, 0.0]

    WHILE TRUE
      ALT i = 0 FOR SIZE in?
        in[i] ? shift[i]
          CLAIM out!
            out ! set.3dshift; shift
:
--}}}
PROC pgn.trimap.handle (CHAN BOOL in?, SHARED CHAN G3D.PGN.CONTROL out!) --{{{
  WHILE TRUE
    BOOL v:
    SEQ
      in ? v
      CLAIM out!
        out ! set.trimap; v
:
--}}}
PROC pgn.bfaces.handle (CHAN BOOL in?, SHARED CHAN G3D.PGN.CONTROL out!) --{{{
  WHILE TRUE
    BOOL v:
    SEQ
      in ? v
      CLAIM out!
        out ! set.backfaces; v
:
--}}}

PROC g3dcubes (CHAN BYTE kyb?, scr!) --{{{
  TTYUTIL.MENUITEM? gen1.ms, gen2.ms, gen3.ms, gen4.ms, gen5.ms, gen6.ms:
  TTYUTIL.MENUITEM! gen1.mc, gen2.mc, gen3.mc, gen4.mc, gen5.mc, gen6.mc:
  TTYUTIL.MENUITEM? rota1.ms, roti1.ms, rotm1.ms, rota2.ms, roti2.ms, rotm2.ms, rdr.ms:
  TTYUTIL.MENUITEM! rota1.mc, roti1.mc, rotm1.mc, rota2.mc, roti2.mc, rotm2.mc, rdr.mc:
  TTYUTIL.MENUITEM? prj1.ms, prj2.ms, prj3.ms, prj4.ms, prj5.ms, prj6.ms, prj7.ms, prj8.ms:
  TTYUTIL.MENUITEM! prj1.mc, prj2.mc, prj3.mc, prj4.mc, prj5.mc, prj6.mc, prj7.mc, prj8.mc:
  TTYUTIL.MENUITEM? pgn1.ms, pgn2.ms:
  TTYUTIL.MENUITEM! pgn1.mc, pgn2.mc:
  SEQ
    --{{{  allocate menu channels
    rdr.ms, rdr.mc := MOBILE TTYUTIL.MENUITEM
    gen1.ms, gen1.mc := MOBILE TTYUTIL.MENUITEM
    gen2.ms, gen2.mc := MOBILE TTYUTIL.MENUITEM
    gen3.ms, gen3.mc := MOBILE TTYUTIL.MENUITEM
    gen4.ms, gen4.mc := MOBILE TTYUTIL.MENUITEM
    gen5.ms, gen5.mc := MOBILE TTYUTIL.MENUITEM
    gen6.ms, gen6.mc := MOBILE TTYUTIL.MENUITEM
    rota1.ms, rota1.mc := MOBILE TTYUTIL.MENUITEM
    roti1.ms, roti1.mc := MOBILE TTYUTIL.MENUITEM
    rotm1.ms, rotm1.mc := MOBILE TTYUTIL.MENUITEM
    rota2.ms, rota2.mc := MOBILE TTYUTIL.MENUITEM
    roti2.ms, roti2.mc := MOBILE TTYUTIL.MENUITEM
    rotm2.ms, rotm2.mc := MOBILE TTYUTIL.MENUITEM
    prj1.ms, prj1.mc := MOBILE TTYUTIL.MENUITEM
    prj2.ms, prj2.mc := MOBILE TTYUTIL.MENUITEM
    prj3.ms, prj3.mc := MOBILE TTYUTIL.MENUITEM
    prj4.ms, prj4.mc := MOBILE TTYUTIL.MENUITEM
    prj5.ms, prj5.mc := MOBILE TTYUTIL.MENUITEM
    prj6.ms, prj6.mc := MOBILE TTYUTIL.MENUITEM
    prj7.ms, prj7.mc := MOBILE TTYUTIL.MENUITEM
    prj8.ms, prj8.mc := MOBILE TTYUTIL.MENUITEM
    pgn1.ms, pgn1.mc := MOBILE TTYUTIL.MENUITEM
    pgn2.ms, pgn2.mc := MOBILE TTYUTIL.MENUITEM

    --}}}

    CHAN G3D.RENDER.ENGINE! rre:
    CHAN G3D.TMR.CONTROL tmr.ctl:
    SHARED! CHAN G3D.GEN.CONTROL gen.ctl:
    SHARED! CHAN G3D.ROT.CONTROL rot1.ctl, rot2.ctl:
    SHARED! CHAN G3D.PRJ.CONTROL prj.ctl:
    SHARED! CHAN G3D.PGN.CONTROL pgn.ctl:
    CHAN G3D.RDR.CONTROL rdr.ctl:
    CHAN INT keys, keys.other, keys.kill:
    SHARED! CHAN TTYUTIL.DPY dpy:
    CHAN TTYUTIL.MENU menu:
    [3]CHAN REAL32 gen.scale.vals, gen.trans.vals:
    CHAN REAL32 ra1.vals, ra2.vals:
    CHAN REAL32 ri1.vals, ri2.vals:
    [2]CHAN REAL32 prj.scale.vals:
    [2]CHAN INT prj.2dshift.vals:
    CHAN REAL32 prj.zdepth.vals:
    [3]CHAN REAL32 prj.3dshift.vals:
    CHAN BOOL pgn.trimap.vals, pgn.bfaces.vals:
    PAR
      --{{{  display handler
      SEQ
        cursor.invisible (scr!)
        ttyutil.display (dpy?, scr!)
      --}}}
      --{{{  keyboard handler
      PAR
        ttyutil.keyhandler (kyb?, keys.kill?, keys!)
      --}}}
      --{{{  raster buffer loop
      CHAN RASTER rc1, rc2, rc3:
      PAR
        raster.timed.buffer (rc1?, rc2!, 60, tmr.ctl?)
        raster.display.simple ("Cube test", G3D.DEFAULT.WIDTH, G3D.DEFAULT.HEIGHT, 3, rc2?, rc3!)
        raster.chain (rc3?, rc1!, rre?, FALSE)
      --}}}
      --{{{  clear screen and run menu
      SEQ
        -- clear screen and run menu
        CLAIM dpy!
          dpy ! clear.screen
        ttyutil.menu (4, 3, 30, 20, TRUE, menu?, dpy!)
      --}}}
      --{{{  menu item handlers
      PAR
        render.mode.menu (rdr.ms?, rdr.ctl!)
        real32.menu ("gen-scale-X", gen1.ms?, 1.0, 0.1, 20.0, 0.1, gen.scale.vals[0]!)
        real32.menu ("gen-scale-Y", gen2.ms?, 1.0, 0.1, 20.0, 0.1, gen.scale.vals[1]!)
        real32.menu ("gen-scale-Z", gen3.ms?, 1.0, 0.1, 20.0, 0.1, gen.scale.vals[2]!)
        real32.menu ("gen-trans-X", gen4.ms?, 0.0, -10.0, 10.0, 0.25, gen.trans.vals[0]!)
        real32.menu ("gen-trans-Y", gen5.ms?, 0.0, -10.0, 10.0, 0.25, gen.trans.vals[1]!)
        real32.menu ("gen-trans-Z", gen6.ms?, 0.0, -10.0, 10.0, 0.25, gen.trans.vals[2]!)
        real32.menu ("rot1-angle", rota1.ms?, 0.0, -PI, PI, (PI / 32.0), ra1.vals!)
        real32.menu ("rot1-incr", roti1.ms?, 0.0, -1.0, 1.0, 0.05, ri1.vals!)
        rotate.mode.menu (rotm1.ms?, rot1.ctl!)
        real32.menu ("rot2-angle", rota2.ms?, 0.0, -PI, PI, (PI / 32.0), ra2.vals!)
        real32.menu ("rot2-incr", roti2.ms?, 0.0, -1.0, 1.0, 0.05, ri2.vals!)
        rotate.mode.menu (rotm2.ms?, rot2.ctl!)
        real32.menu ("prj-scale-x", prj1.ms?, 490.0, 100.0, 1000.0, 10.0, prj.scale.vals[0]!)
        real32.menu ("prj-scale-y", prj2.ms?, 420.0, 100.0, 1000.0, 10.0, prj.scale.vals[1]!)
        real32.menu ("prj-zdepth", prj3.ms?, 12.0, 1.0, 50.0, 1.0, prj.zdepth.vals!)
        int.menu ("prj-2dshift-x", prj4.ms?, G3D.DEFAULT.WIDTH / 2, 10, 1000, 10, prj.2dshift.vals[0]!)
        int.menu ("prj-2dshift-y", prj5.ms?, G3D.DEFAULT.HEIGHT / 2, 10, 1000, 10, prj.2dshift.vals[1]!)
        real32.menu ("prj-3dshift-x", prj6.ms?, 0.0, -10.0, 10.0, 0.1, prj.3dshift.vals[0]!)
        real32.menu ("prj-3dshift-y", prj7.ms?, 0.0, -10.0, 10.0, 0.1, prj.3dshift.vals[1]!)
        real32.menu ("prj-3dshift-z", prj8.ms?, 0.0, -10.0, 10.0, 0.1, prj.3dshift.vals[2]!)
        bool.menu ("pgn-trimap", pgn1.ms?, FALSE, pgn.trimap.vals!)
        bool.menu ("pgn-bfaces", pgn2.ms?, TRUE, pgn.bfaces.vals!)
      --}}}
      --{{{  glue
      PAR
        gen.scale.trans.handle (gen.scale.vals?, gen.trans.vals?, gen.ctl!)
        rot.angle.handle (ra1.vals?, rot1.ctl!)
        rot.angle.handle (ra2.vals?, rot2.ctl!)
        rot.incr.handle (ri1.vals?, rot1.ctl!)
        rot.incr.handle (ri2.vals?, rot2.ctl!)
        prj.scale.handle (prj.scale.vals?, prj.ctl!)
        prj.zdepth.handle (prj.zdepth.vals?, prj.ctl!)
        prj.2dshift.handle (prj.2dshift.vals?, prj.ctl!)
        prj.3dshift.handle (prj.3dshift.vals?, prj.ctl!)
        pgn.trimap.handle (pgn.trimap.vals?, pgn.ctl!)
        pgn.bfaces.handle (pgn.bfaces.vals?, pgn.ctl!)

        SEQ
          CLAIM pgn.ctl!
            pgn.ctl ! set.face.colours; [#00000080,#00005050,#00008000,#00505000,#00800000,#00500050]
      --}}}
      --{{{  add menu items and start keyboard filter
      SEQ
        -- add menu items
        menu ! add.item; gen1.mc
        menu ! add.item; gen2.mc
        menu ! add.item; gen3.mc
        menu ! add.item; gen4.mc
        menu ! add.item; gen5.mc
        menu ! add.item; gen6.mc
        menu ! add.item; rotm1.mc
        menu ! add.item; rota1.mc
        menu ! add.item; roti1.mc
        menu ! add.item; rotm2.mc
        menu ! add.item; rota2.mc
        menu ! add.item; roti2.mc
        menu ! add.item; prj1.mc
        menu ! add.item; prj2.mc
        menu ! add.item; prj3.mc
        menu ! add.item; prj4.mc
        menu ! add.item; prj5.mc
        menu ! add.item; prj6.mc
        menu ! add.item; prj7.mc
        menu ! add.item; prj8.mc
        menu ! add.item; pgn1.mc
        menu ! add.item; pgn2.mc
        menu ! add.item; rdr.mc
        menu.keyfilter (keys?, menu!, keys.other!)
      --}}}
      keys.sink (keys.other?)

      G3D.RENDER.ENGINE! r.cli:
      G3D.RENDER.ENGINE? r.svr:
      SEQ
        r.svr, r.cli := MOBILE G3D.RENDER.ENGINE
        rre ! r.cli
        cube.network (r.svr?, G3D.DEFAULT.WIDTH, G3D.DEFAULT.HEIGHT, gen.ctl?, rot1.ctl?, rot2.ctl?, prj.ctl?, pgn.ctl?, rdr.ctl?)
:
--}}}


