updating picking to support multiple view

@private type my_custom_shaderProgram = {
  program: Webgl.WebGLProgram;
  vertexPositionAttribute: Webgl.GLint;
  vertexNormalAttribute: Webgl.GLint;
  pMatrixUniform: Webgl.WebGLUniformLocation;
  mvMatrixUniform: Webgl.WebGLUniformLocation;
  nMatrixUniform: Webgl.WebGLUniformLocation;
  ambientColorUniform: Webgl.WebGLUniformLocation;
  lightingDirectionUniform: Webgl.WebGLUniformLocation;
  useLightingUniform: Webgl.WebGLUniformLocation
};

@private type static_buffer = {
  positions: Webgl.WebGLBuffer;
  itemSize: int; numItems: int;
  normals: Webgl.WebGLBuffer
};

@private type engine = {
  context: Webgl.Context.private;
  canvas: { selector:dom; width: int; height: int };
  shaderProgram: my_custom_shaderProgram
  static_buffers: { repcoords: { x:static_buffer; y:static_buffer; z:static_buffer } };
  framePickBuffer: Webgl.WebGLFramebuffer;
  scene: list((vec3, object));
  selector: dom
} ;

@client initPickBuffer(eng) =
  gl = eng.context;
  framePickBuffer = Webgl.createFramebuffer(gl);
  renderPickBuffer = Webgl.createRenderbuffer(gl);
  pickTexture = Webgl.createTexture(gl);
  do Webgl.bindTexture(gl, Webgl.TEXTURE_2D(gl), Option.some(pickTexture));
  tex = Webgl.Uint8Array.from_int_list([0, 0, 0]);
  do Webgl.texImage2D(gl, Webgl.TEXTURE_2D(gl), 0, Webgl.RGB(gl), eng.canvas.width, eng.canvas.height, 0, Webgl.RGB(gl), Webgl.UNSIGNED_BYTE(gl), Webgl.Uint8Array.to_ArrayBuffer(tex));
  do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.some(framePickBuffer));
  do Webgl.bindRenderbuffer(gl, Webgl.RENDERBUFFER(gl), Option.some(renderPickBuffer));
  do Webgl.renderbufferStorage(gl, Webgl.RENDERBUFFER(gl), Webgl.DEPTH_COMPONENT16(gl), eng.canvas.width, eng.canvas.height);
  do Webgl.bindRenderbuffer(gl, Webgl.RENDERBUFFER(gl), Option.none);

  do Webgl.framebufferTexture2D(gl, Webgl.FRAMEBUFFER(gl), Webgl.COLOR_ATTACHMENT0(gl), Webgl.TEXTURE_2D(gl), pickTexture, 0);
  do Webgl.framebufferRenderbuffer(gl, Webgl.FRAMEBUFFER(gl), Webgl.DEPTH_ATTACHMENT(gl), Webgl.RENDERBUFFER(gl), renderPickBuffer);
  do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.none);
  do Webgl.bindTexture(gl, Webgl.TEXTURE_2D(gl), Option.none);
  framePickBuffer
;

initShaders(gl) =
  f(e, str) =
    shader = Webgl.createShader(gl, e);
    do Webgl.shaderSource(gl, shader, str);
    do Webgl.compileShader(gl, shader);
    shader;
  fragmentShader = f(Webgl.FRAGMENT_SHADER(gl), CustomShaders.shader_fs);
  vertexShader = f(Webgl.VERTEX_SHADER(gl), CustomShaders.shader_vs);
  // TODO getShaderParameter
  shaderProgram = Webgl.createProgram(gl);
  do Webgl.attachShader(gl, shaderProgram, vertexShader);
  do Webgl.attachShader(gl, shaderProgram, fragmentShader);
  do Webgl.linkProgram(gl, shaderProgram);
  // TODO : getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
  do Webgl.useProgram(gl, shaderProgram);
  shaderProgram =
    vertexPositionAttribute = Webgl.getAttribLocation(gl, shaderProgram, "aVertexPosition");
    vertexNormalAttribute = Webgl.getAttribLocation(gl, shaderProgram, "aVertexNormal");
    do Webgl.enableVertexAttribArray(gl, vertexPositionAttribute);
    do Webgl.enableVertexAttribArray(gl, vertexNormalAttribute);
    { program = shaderProgram;
      ~vertexPositionAttribute;
      ~vertexNormalAttribute;
      pMatrixUniform = Webgl.getUniformLocation(gl, shaderProgram, "uPMatrix");
      mvMatrixUniform = Webgl.getUniformLocation(gl, shaderProgram, "uMVMatrix");
      nMatrixUniform = Webgl.getUniformLocation(gl, shaderProgram, "uNMatrix");
      ambientColorUniform = Webgl.getUniformLocation(gl, shaderProgram, "uAmbientColor");
      lightingDirectionUniform = Webgl.getUniformLocation(gl, shaderProgram, "uLightingDirection");
      useLightingUniform = Webgl.getUniformLocation(gl, shaderProgram, "uUseLighting");
    };
  shaderProgram
;

initLineXBuffers(gl, orient) =
  (vertices, vertexNormals) = match orient with
    | {x} -> ([ 0.0, 0.0, 0.0, 5.0, 0.0, 0.0 ], [ 2.5, 0.0, 0.0,        2.5, -1.0, 0.0 ])
    | {y} -> ([ 0.0, 0.0, 0.0, 0.0, 5.0, 0.0 ], [ 0.0, 2.5, 0.0,        -1.0, 2.5, 0.0 ])
    | {z} -> ([ 0.0, 0.0, 0.0, 0.0, 0.0, 5.0 ], [ 0.0, 0.0, 2.5,        0.0, -1.0, 2.5 ])
    end ;
  vertexPositionBuffer = Webgl.createBuffer(gl);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), vertexPositionBuffer);
  do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl),
    Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertices)),
    Webgl.STATIC_DRAW(gl));
  vertexNormalBuffer = Webgl.createBuffer(gl);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), vertexNormalBuffer);
  do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl),
    Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertexNormals)),
    Webgl.STATIC_DRAW(gl));
  { positions=vertexPositionBuffer; itemSize=3; numItems=2; normals=vertexNormalBuffer }
;

setMatrixUniforms(gl, shaderProgram, pMatrix, mvMatrix) =
  a =
    b = mat4.to_list(pMatrix);
    c = Webgl.Float32Array.from_float_list(b);
    c;
  e =
    f = mat4.to_list(mvMatrix);
    g = Webgl.Float32Array.from_float_list(f);
    g;
  do Webgl.uniformMatrix4fv(gl, shaderProgram.pMatrixUniform, false, a);
  do Webgl.uniformMatrix4fv(gl, shaderProgram.mvMatrixUniform, false, e);
  normalMatrix = mat3.create();
  do mat4.toInverseMat3(mvMatrix, normalMatrix);
  do mat3.transpose(normalMatrix, normalMatrix);
  do Webgl.uniformMatrix3fv(gl, shaderProgram.nMatrixUniform, false, Webgl.Float32Array.from_float_list(mat3.to_list(normalMatrix)));
  void
;

setup_boxes(eng) =
  g(x:int, y:int, w:int, h:int) =
    inbox(pos) = (pos.x_px >= x) && (pos.y_px >= y) && (pos.x_px < (w + x)) && (pos.y_px < (h + y));
    { ~x; ~y; ~w; ~h; ~inbox } ;
  compute_left_right(x) = if ((mod(x, 2)) == 0) then
      a = (x / 2);
      ((a, a), 0)
    else
      b = (x / 2);
      ((b, b+1), 0);
  ((w_l, w_r), e_w) = compute_left_right(eng.canvas.width);
  ((h_u, h_d), e_h) = compute_left_right(eng.canvas.height);
  { _YX=g(0, h_d+e_h, w_l, h_u); _YZ=g(w_l+e_w, h_d+e_h, w_r, h_u);
    _ZX=g(0, 0, w_l, h_d);       _3D=g(w_l+e_w, 0, w_r, h_d) }
;

which_box(b, pos) =
  if b._YX.inbox(pos) then {_YX}
  else if b._YZ.inbox(pos) then {_YZ}
  else if b._ZX.inbox(pos) then {_ZX}
  else if b._3D.inbox(pos) then {_3D}
  else {out}
;

fetch_box(b, who) = match who with
  | {_YX} -> b._YX
  | {_YZ} -> b._YZ
  | {_ZX} -> b._ZX
  | {_3D} -> b._3D
  end ;

drawScene_for_a_viewport(eng, who, viewport, eye, up, scene, mode) =
  gl = eng.context; shaderProgram = eng.shaderProgram; repcoords = eng.static_buffers.repcoords;
  do Webgl.viewport(gl, viewport.x, viewport.y, viewport.w, viewport.h);
  pMatrix =
    tmp_pMatrix = mat4.create();
    match who with
    | {_YX} ->
      do mat4.ortho(-7., 7., -7., 7., -10., 10., tmp_pMatrix);
      tmp_pMatrix
    | {_YZ} ->
      do mat4.ortho(-7., 7., -7., 7., -10., 10., tmp_pMatrix);
      mat4.rotateY(tmp_pMatrix, (90. * Math.PI / 180.), tmp_pMatrix)
    | {_ZX} ->
      do mat4.ortho(-7., 7., -7., 7., -10., 10., tmp_pMatrix);
      mat4.rotateX(tmp_pMatrix, (90. * Math.PI / 180.), tmp_pMatrix)
    | {_3D} ->
      do mat4.perspective(45., float_of_int(eng.canvas.width) / float_of_int(eng.canvas.height), 0.1, 100.0, tmp_pMatrix);
      c = mat4.create() ;
      do mat4.lookAt(vec3.from_public(eye), vec3.from_public((0.0, 0.0, 0.0)), vec3.from_public(up), c);
      do mat4.multiply(tmp_pMatrix, c, tmp_pMatrix);
      tmp_pMatrix
    end ;
  mvMatrix =
    tmp_mvMatrix = mat4.create();
    do mat4.identity(tmp_mvMatrix);
    Stack.push(Stack.create(), tmp_mvMatrix);
  do setMatrixUniforms(gl, shaderProgram, pMatrix, Stack.peek(mvMatrix));
  draw_rep(r, g, b, rep) =
    do Webgl.uniform3f(gl, shaderProgram.ambientColorUniform, r, g, b);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), rep.positions);
    do Webgl.vertexAttribPointer(gl, shaderProgram.vertexPositionAttribute, rep.itemSize, Webgl.FLOAT(gl), false, 0, 0);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), rep.normals);
    do Webgl.vertexAttribPointer(gl, shaderProgram.vertexNormalAttribute, rep.itemSize, Webgl.FLOAT(gl), false, 0, 0);
    do Webgl.drawArrays(gl, Webgl.LINES(gl), 0, rep.numItems);
    void ;

  _ =
    match mode with
    | {normal} ->
      do Webgl.uniform1i(gl, shaderProgram.useLightingUniform, 0); // 0 = false
      do draw_rep(1.0, 0.0, 0.0, repcoords.x);
      do draw_rep(0.0, 1.0, 0.0, repcoords.y);
      do draw_rep(0.0, 0.0, 1.0, repcoords.z);
      do match who with
        | {_3D} ->
          do Webgl.uniform1i(gl, shaderProgram.useLightingUniform, 1); // 1 = true
          Webgl.uniform3f(gl, shaderProgram.lightingDirectionUniform, 0.85, 0.8, 0.75)
        | _ -> void end;
      List.iter(((pos, object) -> display(eng, pMatrix, mvMatrix, pos, object, false)), scene)
    | {pick} ->
      do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.some(eng.framePickBuffer));
      do Webgl.clear(gl, Webgl.GLbitfield_OR(Webgl.COLOR_BUFFER_BIT(gl), Webgl.DEPTH_BUFFER_BIT(gl)));
      do Webgl.uniform1i(gl, shaderProgram.useLightingUniform, 0); // 0 = false;
      do List.iter(((pos, object) -> display(eng, pMatrix, mvMatrix, pos, object, true)), scene);
      void
    end ;

  (pMatrix, mvMatrix)
;

drawScene_and_register(org_eng, get_scene : (->Modeler.scene), get_mode) =
  viewbox = setup_boxes(org_eng) ;
  rec aux(eng) =
    gl = eng.context;
    (eng, scene) =
      f(p) =
        match List.find((z_in_mem -> z_in_mem.f2.id == p.id), eng.scene) with
        | { some=in_mem } -> { in_mem with f1=p.cube }
        | { none } -> (p.cube, Cube.create(gl, p.id))
        end;
      scene = List.map(f, get_scene());
      ({ eng with ~scene }, scene);
    do match get_mode() with
    | {pick=pos; ~cont} ->
      match which_box(viewbox, pos) with
        | {out} -> void
        | {_YX} as who | {_YZ} as who | {_ZX} as who | {_3D} as who ->
          do Log.debug("Picking", "in box: '{who}' \t {viewbox}");
          this_viewbox = fetch_box(viewbox, who);
          (pMatrix, s) = drawScene_for_a_viewport(eng, who, this_viewbox, (0.0, 0.0, 15.0), (0.0, 1.0, 0.0), scene, {pick});
          x = (float_of_int(pos.x_px - this_viewbox.x) / float_of_int(this_viewbox.w)) * 2.0 - 1.0;
          y = (float_of_int(pos.y_px - this_viewbox.y) / float_of_int(this_viewbox.h)) * 2.0 - 1.0;
          mvMatrix = Stack.peek(s) ;
          do mat4.multiply(pMatrix, mvMatrix, mvMatrix);
          do mat4.inverse(mvMatrix, mvMatrix);
          v1 = vec4.from_public((x, y, 1.0, 1.0));
          v2 = vec4.from_public((9.9, 9.9, 9.9, 9.9));
          do mat4.multiplyVec4(mvMatrix, v1, v2);
          v22 = vec4.to_public(v2);
          //v22 = { v22 with f2=0.0 };
          v2 = (v22.f1 / v22.f4, v22.f2 / v22.f4, v22.f3 / v22.f4, v22.f4);
          data = Webgl.Uint8Array.from_int_list(List.init((_->123), 4));
          do Webgl.readPixels(gl, pos.x_px, pos.y_px, 1, 1, Webgl.RGBA(gl), Webgl.UNSIGNED_BYTE(gl), Webgl.Uint8Array.to_ArrayBuffer(data));
          pickedColor = match Webgl.Uint8Array.to_int_list(data) with
            | [r, g, b, _] -> (r, g, b)
            | _ -> error("Picking failure")
            end ;
          do Log.debug("Picking", "Color is: { pickedColor }; v1={ vec4.str(v1) }, \t v2={ v2 }");
          //do jlog("Color is: { pickedColor }; v={ vec3.str(v) }");
          possible_target =
            (r, g, b) = pickedColor;
            f(z) = (z.f2.picking_color == (float_of_int(r) / 255., float_of_int(g) / 255., float_of_int(b) / 255.)) ;
            Option.map((u -> u.f2.id), List.find(f, eng.scene));
          do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.none);
          cont({ mousedown; x=v2.f1; z=v2.f3; ~possible_target })
          end
    | {normal} ->
      do Webgl.clear(gl, Webgl.GLbitfield_OR(Webgl.COLOR_BUFFER_BIT(gl), Webgl.DEPTH_BUFFER_BIT(gl)));
      _ = drawScene_for_a_viewport(eng, {_YX}, viewbox._YX, (0.0, 0.0, 15.0), (0.0, 1.0, 0.0), scene, {normal});
      _ = drawScene_for_a_viewport(eng, {_YZ}, viewbox._YZ, (-15.0, 0.0, 0.0), (0.0, 1.0, 0.0), scene, {normal});
      _ = drawScene_for_a_viewport(eng, {_ZX}, viewbox._ZX, (0.0, -15.0, 0.0), (0.0, 0.0, 1.0), scene, {normal});
      _ = drawScene_for_a_viewport(eng, {_3D}, viewbox._3D, (10.0, 5.0, 15.0), (0.0, 1.0, 0.0), scene, {normal});
      void
    end ;
    do RequestAnimationFrame.request((_ -> aux(eng)), eng.selector);
    void
    ;
  aux(org_eng)
;

initGL(canvas_sel, width, height, get_scene, mouse_listener) : outcome =
  match WebGLUtils.setupWebGL_with_custom_failure(Dom.of_selection(canvas_sel)) with
  | { ok=context } ->
    mode = Mutable.make_client({normal});
    is_picking(m) = match m with | { pick=_; cont=_ } -> true | _ -> false end;
    _ =
      handler(e) =
        m_pos = // we transform the pos to be relative to the upper leftcorner of the canvas
          c_pos = Dom.get_offset(canvas_sel);
          rel = e.mouse_position_on_page;
          { x_px=max(rel.x_px - c_pos.x_px, 0); y_px=max(rel.y_px - c_pos.y_px, 0) };
        // now with gl the origin will be at the lower left corner
        gl_pos = { x_px=min(max(m_pos.x_px,0), height-1); y_px=min(max(height - 1 - m_pos.y_px, 0), width-1) };
        cont(e) =
          do mode.set({normal});
          mouse_listener(e);
        do if not(is_picking(mode.get())) then mode.set({pick=gl_pos; ~cont});
        Log.debug("P", "[(,)-({m_pos.x_px},{m_pos.y_px})] {gl_pos.x_px}, {gl_pos.y_px}");
      Dom.bind(canvas_sel, { mousedown }, handler);
    gl = context;
    eng : engine =
      start = @openrecord({ context=gl; canvas={ selector=canvas_sel; ~width; ~height }; scene=List.empty; selector=canvas_sel });
      start = { start with shaderProgram=initShaders(gl) };
      start = { start with framePickBuffer=initPickBuffer(start) } ;
      { start with static_buffers.repcoords=
        { x=initLineXBuffers(gl, {x}); y=initLineXBuffers(gl, {y}); z=initLineXBuffers(gl, {z}) } };
    //Clear screen and make everything light gray
    do Webgl.clearColor(gl, 0.9, 0.9, 0.9, 1.0);
    //we should do depth testing so that things drawn behind other
    //things should be hidden by the things in front of them).
    do Webgl.clearDepth(gl, 1.0);
    do Webgl.enable(gl, Webgl.DEPTH_TEST(gl));
    do Webgl.depthFunc(gl, Webgl.LEQUAL(gl));
    do drawScene_and_register(eng, get_scene, mode.get);
    { success }
  | { ~ko } -> { failure=ko }
  end ;

@private type my_custom_shaderProgram = {
  program: Webgl.WebGLProgram;
  vertexPositionAttribute: Webgl.GLint;
  vertexNormalAttribute: Webgl.GLint;
  pMatrixUniform: Webgl.WebGLUniformLocation;
  mvMatrixUniform: Webgl.WebGLUniformLocation;
  nMatrixUniform: Webgl.WebGLUniformLocation;
  ambientColorUniform: Webgl.WebGLUniformLocation;
  lightingDirectionUniform: Webgl.WebGLUniformLocation;
  useLightingUniform: Webgl.WebGLUniformLocation
};

@private type static_buffer = {
  positions: Webgl.WebGLBuffer;
  itemSize: int; numItems: int;
  normals: Webgl.WebGLBuffer
};

@private type engine = {
  context: Webgl.Context.private;
  canvas: { selector:dom; width: int; height: int };
  shaderProgram: my_custom_shaderProgram
  static_buffers: { repcoords: { x:static_buffer; y:static_buffer; z:static_buffer } };
  framePickBuffer: Webgl.WebGLFramebuffer;
  scene: list((vec3, object));
  selector: dom
} ;

@client initPickBuffer(eng) =
  gl = eng.context;
  framePickBuffer = Webgl.createFramebuffer(gl);
  renderPickBuffer = Webgl.createRenderbuffer(gl);
  pickTexture = Webgl.createTexture(gl);
  do Webgl.bindTexture(gl, Webgl.TEXTURE_2D(gl), Option.some(pickTexture));
  tex = Webgl.Uint8Array.from_int_list([0, 0, 0]);
  do Webgl.texImage2D(gl, Webgl.TEXTURE_2D(gl), 0, Webgl.RGB(gl), eng.canvas.width, eng.canvas.height, 0, Webgl.RGB(gl), Webgl.UNSIGNED_BYTE(gl), Webgl.Uint8Array.to_ArrayBuffer(tex));
  do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.some(framePickBuffer));
  do Webgl.bindRenderbuffer(gl, Webgl.RENDERBUFFER(gl), Option.some(renderPickBuffer));
  do Webgl.renderbufferStorage(gl, Webgl.RENDERBUFFER(gl), Webgl.DEPTH_COMPONENT16(gl), eng.canvas.width, eng.canvas.height);
  do Webgl.bindRenderbuffer(gl, Webgl.RENDERBUFFER(gl), Option.none);

  do Webgl.framebufferTexture2D(gl, Webgl.FRAMEBUFFER(gl), Webgl.COLOR_ATTACHMENT0(gl), Webgl.TEXTURE_2D(gl), pickTexture, 0);
  do Webgl.framebufferRenderbuffer(gl, Webgl.FRAMEBUFFER(gl), Webgl.DEPTH_ATTACHMENT(gl), Webgl.RENDERBUFFER(gl), renderPickBuffer);
  do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.none);
  do Webgl.bindTexture(gl, Webgl.TEXTURE_2D(gl), Option.none);
  framePickBuffer
;

initShaders(gl) =
  f(e, str) =
    shader = Webgl.createShader(gl, e);
    do Webgl.shaderSource(gl, shader, str);
    do Webgl.compileShader(gl, shader);
    shader;
  fragmentShader = f(Webgl.FRAGMENT_SHADER(gl), CustomShaders.shader_fs);
  vertexShader = f(Webgl.VERTEX_SHADER(gl), CustomShaders.shader_vs);
  // TODO getShaderParameter
  shaderProgram = Webgl.createProgram(gl);
  do Webgl.attachShader(gl, shaderProgram, vertexShader);
  do Webgl.attachShader(gl, shaderProgram, fragmentShader);
  do Webgl.linkProgram(gl, shaderProgram);
  // TODO : getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
  do Webgl.useProgram(gl, shaderProgram);
  shaderProgram =
    vertexPositionAttribute = Webgl.getAttribLocation(gl, shaderProgram, "aVertexPosition");
    vertexNormalAttribute = Webgl.getAttribLocation(gl, shaderProgram, "aVertexNormal");
    do Webgl.enableVertexAttribArray(gl, vertexPositionAttribute);
    do Webgl.enableVertexAttribArray(gl, vertexNormalAttribute);
    { program = shaderProgram;
      ~vertexPositionAttribute;
      ~vertexNormalAttribute;
      pMatrixUniform = Webgl.getUniformLocation(gl, shaderProgram, "uPMatrix");
      mvMatrixUniform = Webgl.getUniformLocation(gl, shaderProgram, "uMVMatrix");
      nMatrixUniform = Webgl.getUniformLocation(gl, shaderProgram, "uNMatrix");
      ambientColorUniform = Webgl.getUniformLocation(gl, shaderProgram, "uAmbientColor");
      lightingDirectionUniform = Webgl.getUniformLocation(gl, shaderProgram, "uLightingDirection");
      useLightingUniform = Webgl.getUniformLocation(gl, shaderProgram, "uUseLighting");
    };
  shaderProgram
;

initLineXBuffers(gl, orient) =
  (vertices, vertexNormals) = match orient with
    | {x} -> ([ 0.0, 0.0, 0.0, 5.0, 0.0, 0.0 ], [ 2.5, 0.0, 0.0,        2.5, -1.0, 0.0 ])
    | {y} -> ([ 0.0, 0.0, 0.0, 0.0, 5.0, 0.0 ], [ 0.0, 2.5, 0.0,        -1.0, 2.5, 0.0 ])
    | {z} -> ([ 0.0, 0.0, 0.0, 0.0, 0.0, 5.0 ], [ 0.0, 0.0, 2.5,        0.0, -1.0, 2.5 ])
    end ;
  vertexPositionBuffer = Webgl.createBuffer(gl);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), vertexPositionBuffer);
  do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl),
    Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertices)),
    Webgl.STATIC_DRAW(gl));
  vertexNormalBuffer = Webgl.createBuffer(gl);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), vertexNormalBuffer);
  do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl),
    Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertexNormals)),
    Webgl.STATIC_DRAW(gl));
  { positions=vertexPositionBuffer; itemSize=3; numItems=2; normals=vertexNormalBuffer }
;

setMatrixUniforms(gl, shaderProgram, pMatrix, mvMatrix) =
  a =
    b = mat4.to_list(pMatrix);
    c = Webgl.Float32Array.from_float_list(b);
    c;
  e =
    f = mat4.to_list(mvMatrix);
    g = Webgl.Float32Array.from_float_list(f);
    g;
  do Webgl.uniformMatrix4fv(gl, shaderProgram.pMatrixUniform, false, a);
  do Webgl.uniformMatrix4fv(gl, shaderProgram.mvMatrixUniform, false, e);
  normalMatrix = mat3.create();
  do mat4.toInverseMat3(mvMatrix, normalMatrix);
  do mat3.transpose(normalMatrix, normalMatrix);
  do Webgl.uniformMatrix3fv(gl, shaderProgram.nMatrixUniform, false, Webgl.Float32Array.from_float_list(mat3.to_list(normalMatrix)));
  void
;

setup_boxes(eng) =
  g(x:int, y:int, w:int, h:int) =
    inbox(pos) = (pos.x_px >= x) && (pos.y_px >= y) && (pos.x_px < (w + x)) && (pos.y_px < (h + y));
    { ~x; ~y; ~w; ~h; ~inbox } ;
  compute_left_right(x) = if ((mod(x, 2)) == 0) then
      a = (x / 2);
      ((a, a), 0)
    else
      b = (x / 2);
      ((b, b+1), 0);
  ((w_l, w_r), e_w) = compute_left_right(eng.canvas.width);
  ((h_u, h_d), e_h) = compute_left_right(eng.canvas.height);
  { _YX=g(0, h_d+e_h, w_l, h_u); _YZ=g(w_l+e_w, h_d+e_h, w_r, h_u);
    _ZX=g(0, 0, w_l, h_d);       _3D=g(w_l+e_w, 0, w_r, h_d) }
;

which_boxes(b, pos) =
  if b._YX.inbox(pos) then {_YX}
  else if b._YZ.inbox(pos) then {_YZ}
  else if b._ZX.inbox(pos) then {_ZX}
  else if b._3D.inbox(pos) then {_3D}
  else {out}
;

drawScene_for_a_viewport(eng, who, viewport, eye, up, scene, mode) =
  gl = eng.context; shaderProgram = eng.shaderProgram; repcoords = eng.static_buffers.repcoords;
  do Webgl.viewport(gl, viewport.x, viewport.y, viewport.w, viewport.h);
  pMatrix =
    tmp_pMatrix = mat4.create();
    match who with
    | {_YX} ->
      do mat4.ortho(-7., 7., -7., 7., -10., 10., tmp_pMatrix);
      tmp_pMatrix
    | {_YZ} ->
      do mat4.ortho(-7., 7., -7., 7., -10., 10., tmp_pMatrix);
      mat4.rotateY(tmp_pMatrix, (90. * Math.PI / 180.), tmp_pMatrix)
    | {_ZX} ->
      do mat4.ortho(-7., 7., -7., 7., -10., 10., tmp_pMatrix);
      mat4.rotateX(tmp_pMatrix, (90. * Math.PI / 180.), tmp_pMatrix)
    | {_3D} ->
      do mat4.perspective(45., float_of_int(eng.canvas.width) / float_of_int(eng.canvas.height), 0.1, 100.0, tmp_pMatrix);
      c = mat4.create() ;
      do mat4.lookAt(vec3.from_public(eye), vec3.from_public((0.0, 0.0, 0.0)), vec3.from_public(up), c);
      do mat4.multiply(tmp_pMatrix, c, tmp_pMatrix);
      tmp_pMatrix
    end ;
  mvMatrix =
    tmp_mvMatrix = mat4.create();
    do mat4.identity(tmp_mvMatrix);
    Stack.push(Stack.create(), tmp_mvMatrix);
  do setMatrixUniforms(gl, shaderProgram, pMatrix, Stack.peek(mvMatrix));
  draw_rep(r, g, b, rep) =
    do Webgl.uniform3f(gl, shaderProgram.ambientColorUniform, r, g, b);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), rep.positions);
    do Webgl.vertexAttribPointer(gl, shaderProgram.vertexPositionAttribute, rep.itemSize, Webgl.FLOAT(gl), false, 0, 0);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), rep.normals);
    do Webgl.vertexAttribPointer(gl, shaderProgram.vertexNormalAttribute, rep.itemSize, Webgl.FLOAT(gl), false, 0, 0);
    do Webgl.drawArrays(gl, Webgl.LINES(gl), 0, rep.numItems);
    void ;

  _ =
    match mode with
    | {normal} ->
      do Webgl.uniform1i(gl, shaderProgram.useLightingUniform, 0); // 0 = false
      do draw_rep(1.0, 0.0, 0.0, repcoords.x);
      do draw_rep(0.0, 1.0, 0.0, repcoords.y);
      do draw_rep(0.0, 0.0, 1.0, repcoords.z);
      do match who with
        | {_3D} ->
          do Webgl.uniform1i(gl, shaderProgram.useLightingUniform, 1); // 1 = true
          Webgl.uniform3f(gl, shaderProgram.lightingDirectionUniform, 0.85, 0.8, 0.75)
        | _ -> void end;
      List.iter(((pos, object) -> display(eng, pMatrix, mvMatrix, pos, object, false)), scene)
    | {pick} ->
      do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.some(eng.framePickBuffer));
      do Webgl.clear(gl, Webgl.GLbitfield_OR(Webgl.COLOR_BUFFER_BIT(gl), Webgl.DEPTH_BUFFER_BIT(gl)));
      do Webgl.uniform1i(gl, shaderProgram.useLightingUniform, 0); // 0 = false;
      do List.iter(((pos, object) -> display(eng, pMatrix, mvMatrix, pos, object, true)), scene);
      void
    end ;

  (pMatrix, mvMatrix)
;

drawScene_and_register(org_eng, get_scene : (->Modeler.scene), get_mode) =
  viewbox = setup_boxes(org_eng) ;
  rec aux(eng) =
    gl = eng.context;
    (eng, scene) =
      f(p) =
        match List.find((z_in_mem -> z_in_mem.f2.id == p.id), eng.scene) with
        | { some=in_mem } -> { in_mem with f1=p.cube }
        | { none } -> (p.cube, Cube.create(gl, p.id))
        end;
      scene = List.map(f, get_scene());
      ({ eng with ~scene }, scene);
    do match get_mode() with
    | {pick=pos; ~cont} ->
      who = which_boxes(viewbox, pos);
      do Log.debug("Picking", "in box: '{who}' \t {viewbox}");
      _ = drawScene_for_a_viewport(eng, {_YX}, viewbox._YX, (0.0, 0.0, 15.0), (0.0, 1.0, 0.0), scene, {pick});
      _ = drawScene_for_a_viewport(eng, {_YZ}, viewbox._YZ, (-15.0, 0.0, 0.0), (0.0, 1.0, 0.0), scene, {pick});
      (pMatrix, s) = drawScene_for_a_viewport(eng, {_ZX}, viewbox._ZX, (0.0, -15.0, 0.0), (0.0, 0.0, 1.0), scene, {pick});
      //(pMatrix, s) = drawScene_for_a_viewport(eng, {_3D}, viewbox._3D, (10.0, 5.0, 15.0), (0.0, 1.0, 0.0), scene, {pick});
      x = (float_of_int(pos.x_px - viewbox._ZX.x) / float_of_int(viewbox._ZX.w)) * 2.0 - 1.0;
      y = (float_of_int(pos.y_px - viewbox._ZX.y) / float_of_int(viewbox._ZX.h)) * 2.0 - 1.0;
      mvMatrix = Stack.peek(s) ;
      do mat4.multiply(pMatrix, mvMatrix, mvMatrix);
      do mat4.inverse(mvMatrix, mvMatrix);
      v1 = vec4.from_public((x, y, 1.0, 1.0));
      v2 = vec4.from_public((9.9, 9.9, 9.9, 9.9));
      do mat4.multiplyVec4(mvMatrix, v1, v2);
      v22 = vec4.to_public(v2);
      //v22 = { v22 with f2=0.0 };
      v2 = (v22.f1 / v22.f4, v22.f2 / v22.f4, v22.f3 / v22.f4, v22.f4);
      data = Webgl.Uint8Array.from_int_list(List.init((_->123), 4));
      do Webgl.readPixels(gl, pos.x_px, pos.y_px, 1, 1, Webgl.RGBA(gl), Webgl.UNSIGNED_BYTE(gl), Webgl.Uint8Array.to_ArrayBuffer(data));
      pickedColor = match Webgl.Uint8Array.to_int_list(data) with
        | [r, g, b, _] -> (r, g, b)
        | _ -> error("Picking failure")
        end ;
      do Log.debug("Picking", "Color is: { pickedColor }; v1={ vec4.str(v1) }, \t v2={ v2 }");
      //do jlog("Color is: { pickedColor }; v={ vec3.str(v) }");
      possible_target =
        (r, g, b) = pickedColor;
        f(z) = (z.f2.picking_color == (float_of_int(r) / 255., float_of_int(g) / 255., float_of_int(b) / 255.)) ;
        Option.map((u -> u.f2.id), List.find(f, eng.scene));
      do Webgl.bindFramebuffer(gl, Webgl.FRAMEBUFFER(gl), Option.none);
      cont({ mousedown; x=v2.f1; z=v2.f3; ~possible_target })
    | {normal} ->
      do Webgl.clear(gl, Webgl.GLbitfield_OR(Webgl.COLOR_BUFFER_BIT(gl), Webgl.DEPTH_BUFFER_BIT(gl)));
      _ = drawScene_for_a_viewport(eng, {_YX}, viewbox._YX, (0.0, 0.0, 15.0), (0.0, 1.0, 0.0), scene, {normal});
      _ = drawScene_for_a_viewport(eng, {_YZ}, viewbox._YZ, (-15.0, 0.0, 0.0), (0.0, 1.0, 0.0), scene, {normal});
      _ = drawScene_for_a_viewport(eng, {_ZX}, viewbox._ZX, (0.0, -15.0, 0.0), (0.0, 0.0, 1.0), scene, {normal});
      _ = drawScene_for_a_viewport(eng, {_3D}, viewbox._3D, (10.0, 5.0, 15.0), (0.0, 1.0, 0.0), scene, {normal});
      void
    end ;
    do RequestAnimationFrame.request((_ -> aux(eng)), eng.selector);
    void
    ;
  aux(org_eng)
;

initGL(canvas_sel, width, height, get_scene, mouse_listener) : outcome =
  match WebGLUtils.setupWebGL_with_custom_failure(Dom.of_selection(canvas_sel)) with
  | { ok=context } ->
    mode = Mutable.make_client({normal});
    is_picking(m) = match m with | { pick=_; cont=_ } -> true | _ -> false end;
    _ =
      handler(e) =
        m_pos = // we transform the pos to be relative to the upper leftcorner of the canvas
          c_pos = Dom.get_offset(canvas_sel);
          rel = e.mouse_position_on_page;
          { x_px=max(rel.x_px - c_pos.x_px, 0); y_px=max(rel.y_px - c_pos.y_px, 0) };
        // now with gl the origin will be at the lower left corner
        gl_pos = { x_px=min(max(m_pos.x_px,0), height-1); y_px=min(max(height - 1 - m_pos.y_px, 0), width-1) };
        cont(e) =
          do mode.set({normal});
          mouse_listener(e);
        do if not(is_picking(mode.get())) then mode.set({pick=gl_pos; ~cont});
        Log.debug("P", "[(,)-({m_pos.x_px},{m_pos.y_px})] {gl_pos.x_px}, {gl_pos.y_px}");
      Dom.bind(canvas_sel, { mousedown }, handler);
    gl = context;
    eng : engine =
      start = @openrecord({ context=gl; canvas={ selector=canvas_sel; ~width; ~height }; scene=List.empty; selector=canvas_sel });
      start = { start with shaderProgram=initShaders(gl) };
      start = { start with framePickBuffer=initPickBuffer(start) } ;
      { start with static_buffers.repcoords=
        { x=initLineXBuffers(gl, {x}); y=initLineXBuffers(gl, {y}); z=initLineXBuffers(gl, {z}) } };
    //Clear screen and make everything light gray
    do Webgl.clearColor(gl, 0.9, 0.9, 0.9, 1.0);
    //we should do depth testing so that things drawn behind other
    //things should be hidden by the things in front of them).
    do Webgl.clearDepth(gl, 1.0);
    do Webgl.enable(gl, Webgl.DEPTH_TEST(gl));
    do Webgl.depthFunc(gl, Webgl.LEQUAL(gl));
    do drawScene_and_register(eng, get_scene, mode.get);
    { success }
  | { ~ko } -> { failure=ko }
  end ;


injecting a session

injecting a session

import stdlib.widgets.colorpicker
import stdlib.widgets.core

type GuiModeler.t = {
  modeler: Modeler.modeler;
  subjects: {
    tool: subject(Modeler.tool);
    selection: { this: subject(option(Scene.objects)); color: subject(ColorFloat.color) }
    };
  last_coord_fixer: option(Dom.dimensions -> vec3)
  width: int; height: int
} ;

@client GuiModeler = {{

  Sync = {{
    on_message(state : GuiModeler.t, message) =
      set_modeler(modeler) = { set={ state with ~modeler } } ;
      set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
      match message : Central.Modelers.sync.message with
      | {load=a_scene} ->
        modeler = Modeler.load(`Scene.Client`.load(a_scene, state.modeler.client_id), state.modeler.address, state.modeler.client_id, float_of_int(state.height) / float_of_int(state.width));
        subjects =
          { state.subjects with selection={
              this=Observable.change_state(modeler.scene.selection, state.subjects.selection.this);
              color=
                tmp = Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection);
                Observable.change_state(tmp , state.subjects.selection.color) } };
        set({ ~subjects; ~modeler })
      | { write_patch; ~patch } ->
        modeler = Modeler.write_patch(state.modeler, patch);
        set_modeler(modeler)
      end ;
  }}

  empty(scene_url, client_id, width, height) : GuiModeler.t =
    modeler = Modeler.empty(scene_url, client_id, float_of_int(height) / float_of_int(width));
    subjects = { tool=Observable.make(modeler.tool); selection={ this=Observable.make(modeler.scene.selection); color=Observable.make( Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection) ) } };
    { ~modeler; ~subjects; last_coord_fixer=Option.none; ~width; ~height };

  @private on_message(state : GuiModeler.t, message) =
    _set_modeler(modeler) = { set={ state with ~modeler } } ;
    _set_subjects(subjects) = { set={ state with ~subjects } } ;
    set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
    _set_with_last(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler; last_coord_fixer=new_state.last_coord_fixer } };
    send_opatch(opatch) =
      m(patch) = { apply_patch; ~patch; address=state.modeler.address };
      Option.iter((p -> Session.send(central_modelers, m(p))), opatch);
    reset_subjects(modeler) =
      subjects = { state.subjects with selection={ this=Observable.change_state(modeler.scene.selection, state.subjects.selection.this);
                                                   color=Observable.change_state( Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection) , state.subjects.selection.color) } };
      { state with ~subjects; ~modeler };
    match message with
    | {click_on_scene; ~where; ~possible_target; ~last_coord_fixer} ->
      (modeler, opatch) = Modeler.tool_use(state.modeler, where, possible_target);
      do send_opatch(opatch);
      if Observable.get_state(state.subjects.selection.this) == modeler.scene.selection then
        { set={ state with ~modeler; ~last_coord_fixer } }
      else
        state = { reset_subjects(modeler) with ~last_coord_fixer };
        set(state)
    | {modeler_change_tool=new_tool} ->
      modeler = Modeler.tool_change(state.modeler, new_tool);
      subjects = { state.subjects with tool=Observable.change_state(new_tool, state.subjects.tool) };
      set({ ~subjects; ~modeler })
    | {modeler_change_scene_selection_color; ~new_color} ->
      (modeler, opatch) = Modeler.scene_change_selection_color(state.modeler, new_color);
      do send_opatch(opatch);
      subjects = { state.subjects with selection.color=Observable.change_state(new_color, state.subjects.selection.color) };
      set({ ~subjects; ~modeler})
    | {modeler_apply_possible_move; ~where; ~switch } ->
      (modeler, opatch) = Modeler.do_possible_move(state.modeler, where, switch);
      do send_opatch(opatch);
      { set={ state with ~modeler } }
    | {modeler_delete_scene_selection} ->
      (modeler, opatch) = Modeler.scene_delete_selection(state.modeler);
      do send_opatch(opatch);
      set(reset_subjects(modeler))
    end ;

  setup_menu(parent_sel, channel, s_tool) : void =
    f(some_tool) = match some_tool with
      | {selection} -> (Select, Cube)
      | {add_cube} -> (Select, Cube)
      end ;
    (id_a, id_b) = (Random.string(7), Random.string(7));
    on_tool_change =
      then_do(new_tool) =
        (text_a, text_b) = f(new_tool);
        Dom.transform([#{id_a}<- text_a, #{id_b}<- text_b]);
      { si=(-> not(Dom.is_empty(#{id_a})) && not(Dom.is_empty(#{id_b}))); ~then_do; else_autoclean };
    do Observable.register(on_tool_change, s_tool);
    menu =
      s(x) = (_ -> Session.send(channel, {modeler_change_tool=x}));

;
    ignore(Dom.put_at_start(parent_sel, Dom.of_xhtml(menu)));

  setup_selection_view(parent_sel, channel, s_selection) : void =
    id = Random.string(7);
    do
      f(some_selection) = match some_selection with
        | {none} -> <></>
        | {some=_} ->
          id = Random.string(7);
          config =
            style = { WColorpicker.default_config_style with preview=WStyler.make_style(css { height:10px; width:10px; border:1px double black;}) };
            s(a_color) =
              Session.send(channel, {modeler_change_scene_selection_color; new_color=ColorFloat.from_Color_color(a_color)});
            { WColorpicker.default_config with on_select=s; ~style };
          do
            on_sel_color_change =
              then_do(f_new_color) =
                new_color = ColorFloat.to_Color_color(f_new_color);
                WColorpicker.set_color(id, config, new_color, false);
              { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
            Observable.register(on_sel_color_change, s_selection.color);

            Cube

Color:
{ WColorpicker.html(id, config) }

 Session.send(channel, {modeler_delete_scene_selection})}>Remove me

        end ;
      on_selection_change =
        then_do(new_selection) = Dom.transform([#{id}<- f(new_selection)]);
        { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
      Observable.register(on_selection_change, s_selection.this);
    sel =

;
    ignore(Dom.put_at_end(parent_sel, Dom.of_xhtml(sel)));

  init(scene_url, client_id, parent_sel:dom, width, height) : void =
    id_canvas_canvas = "canvas_canvas" ;
    fail_msg =
      <>It seems that your browser and/or graphics card are incompatible with Webgl.Learn a little more about webgl support</> ;
    and_do(_) =
      (channel, get_scene, get_subjects, get_camera_setting) =
        (channel, sync_channel, get_state) = SessionExt.make_2_side(empty(scene_url, client_id, width, height), on_message, Sync.on_message);
        do Session.send(central_modelers, {register; ~scene_url; ~sync_channel; ~client_id});
        (channel, (-> get_state().modeler.scene), (->get_state().subjects), (->get_state().modeler.views)) ;
      support_gpu_picking = Mutable.make(Option.none);
      lock_mouse_event = Mutable.make(false);
      on_message_mouse_listener(msg) =
        match msg with
        | { mousedown; event=_; ~abs_full_pos; ~gl_pos } ->
          f(gpu_picker_regsiter) =
            do lock_mouse_event.set(true);
            gpu_picker_regsiter((abs_full_pos, (possible_target -> do Session.send(channel, {click_on_scene; where=gl_pos; ~possible_target; last_coord_fixer=Option.none}); lock_mouse_event.set(false)))) ;
          do Option.switch(f, void, support_gpu_picking.get());
          { continue }
        | { mouseup; event=_; ~gl_pos; ~switch } ->
          msg = {modeler_apply_possible_move; where=gl_pos; ~switch };
          do Session.send(channel, msg);
          { continue }
        end ;
      mouse_listener =
        channel_mouse_listener = Session.make_stateless(on_message_mouse_listener);
        (msg -> Session.send(channel_mouse_listener, msg));
      res = initGL(#{id_canvas_canvas}, width, height, get_scene, get_camera_setting, mouse_listener, support_gpu_picking) ;
      if Outcome.is_failure(res) then ignore(Dom.put_replace(parent_sel, Dom.of_xhtml(fail_msg)))
      else
        the_subjects = get_subjects();
        do setup_menu(parent_sel, channel, the_subjects.tool);
        setup_selection_view(parent_sel, channel, the_subjects.selection);
    base =

<canvas width={width} height={height} id=#{id_canvas_canvas} onready={and_do} ></canvas>
;
    ignore(Dom.put_inside(parent_sel, Dom.of_xhtml(base)));

}}

import stdlib.widgets.colorpicker
import stdlib.widgets.core

type GuiModeler.t = {
  modeler: Modeler.modeler;
  subjects: {
    tool: subject(Modeler.tool);
    selection: { this: subject(option(Scene.objects)); color: subject(ColorFloat.color) }
    };
  last_coord_fixer: option(Dom.dimensions -> vec3)
  width: int; height: int
} ;

@client GuiModeler = {{

  Sync = {{
    on_message(state : GuiModeler.t, message) =
      set_modeler(modeler) = { set={ state with ~modeler } } ;
      set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
      match message : Central.Modelers.sync.message with
      | {load=a_scene} ->
        modeler = Modeler.load(`Scene.Client`.load(a_scene, state.modeler.client_id), state.modeler.address, state.modeler.client_id, float_of_int(state.height) / float_of_int(state.width));
        subjects =
          { state.subjects with selection={
              this=Observable.change_state(modeler.scene.selection, state.subjects.selection.this);
              color=
                tmp = Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection);
                Observable.change_state(tmp , state.subjects.selection.color) } };
        set({ ~subjects; ~modeler })
      | { write_patch; ~patch } ->
        modeler = Modeler.write_patch(state.modeler, patch);
        set_modeler(modeler)
      end ;
  }}

  empty(scene_url, client_id, width, height) : GuiModeler.t =
    modeler = Modeler.empty(scene_url, client_id, float_of_int(height) / float_of_int(width));
    subjects = { tool=Observable.make(modeler.tool); selection={ this=Observable.make(modeler.scene.selection); color=Observable.make( Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection) ) } };
    { ~modeler; ~subjects; last_coord_fixer=Option.none; ~width; ~height };

  @private on_message(state : GuiModeler.t, message) =
    _set_modeler(modeler) = { set={ state with ~modeler } } ;
    _set_subjects(subjects) = { set={ state with ~subjects } } ;
    set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
    _set_with_last(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler; last_coord_fixer=new_state.last_coord_fixer } };
    send_opatch(opatch) =
      m(patch) = { apply_patch; ~patch; address=state.modeler.address };
      Option.iter((p -> Session.send(central_modelers, m(p))), opatch);
    reset_subjects(modeler) =
      subjects = { state.subjects with selection={ this=Observable.change_state(modeler.scene.selection, state.subjects.selection.this);
                                                   color=Observable.change_state( Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection) , state.subjects.selection.color) } };
      { state with ~subjects; ~modeler };
    match message with
    | {click_on_scene; ~where; ~possible_target; ~last_coord_fixer} ->
      (modeler, opatch) = Modeler.tool_use(state.modeler, where, possible_target);
      do send_opatch(opatch);
      if Observable.get_state(state.subjects.selection.this) == modeler.scene.selection then
        { set={ state with ~modeler; ~last_coord_fixer } }
      else
        state = { reset_subjects(modeler) with ~last_coord_fixer };
        set(state)
    | {modeler_change_tool=new_tool} ->
      modeler = Modeler.tool_change(state.modeler, new_tool);
      subjects = { state.subjects with tool=Observable.change_state(new_tool, state.subjects.tool) };
      set({ ~subjects; ~modeler })
    | {modeler_change_scene_selection_color; ~new_color} ->
      (modeler, opatch) = Modeler.scene_change_selection_color(state.modeler, new_color);
      do send_opatch(opatch);
      subjects = { state.subjects with selection.color=Observable.change_state(new_color, state.subjects.selection.color) };
      set({ ~subjects; ~modeler})
    | {modeler_apply_possible_move; ~where; ~switch } ->
      (modeler, opatch) = Modeler.do_possible_move(state.modeler, where, switch);
      do send_opatch(opatch);
      { set={ state with ~modeler } }
    | {modeler_delete_scene_selection} ->
      (modeler, opatch) = Modeler.scene_delete_selection(state.modeler);
      do send_opatch(opatch);
      set(reset_subjects(modeler))
    end ;

  setup_menu(parent_sel, channel, s_tool) : void =
    f(some_tool) = match some_tool with
      | {selection} -> (Select, Cube)
      | {add_cube} -> (Select, Cube)
      end ;
    (id_a, id_b) = (Random.string(7), Random.string(7));
    on_tool_change =
      then_do(new_tool) =
        (text_a, text_b) = f(new_tool);
        Dom.transform([#{id_a}<- text_a, #{id_b}<- text_b]);
      { si=(-> not(Dom.is_empty(#{id_a})) && not(Dom.is_empty(#{id_b}))); ~then_do; else_autoclean };
    do Observable.register(on_tool_change, s_tool);
    menu =
      s(x) = (_ -> Session.send(channel, {modeler_change_tool=x}));

;
    ignore(Dom.put_at_start(parent_sel, Dom.of_xhtml(menu)));

  setup_selection_view(parent_sel, channel, s_selection) : void =
    id = Random.string(7);
    do
      f(some_selection) = match some_selection with
        | {none} -> <></>
        | {some=_} ->
          id = Random.string(7);
          config =
            style = { WColorpicker.default_config_style with preview=WStyler.make_style(css { height:10px; width:10px; border:1px double black;}) };
            s(a_color) =
              Session.send(channel, {modeler_change_scene_selection_color; new_color=ColorFloat.from_Color_color(a_color)});
            { WColorpicker.default_config with on_select=s; ~style };
          do
            on_sel_color_change =
              then_do(f_new_color) =
                new_color = ColorFloat.to_Color_color(f_new_color);
                WColorpicker.set_color(id, config, new_color, false);
              { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
            Observable.register(on_sel_color_change, s_selection.color);

            Cube

Color:
{ WColorpicker.html(id, config) }

 Session.send(channel, {modeler_delete_scene_selection})}>Remove me

        end ;
      on_selection_change =
        then_do(new_selection) = Dom.transform([#{id}<- f(new_selection)]);
        { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
      Observable.register(on_selection_change, s_selection.this);
    sel =

;
    ignore(Dom.put_at_end(parent_sel, Dom.of_xhtml(sel)));

  init(scene_url, client_id, parent_sel:dom, width, height) : void =
    id_canvas_canvas = "canvas_canvas" ;
    fail_msg =
      <>It seems that your browser and/or graphics card are incompatible with Webgl.Learn a little more about webgl support</> ;
    and_do(_) =
      (channel, get_scene, get_subjects, get_camera_setting) =
        (channel, sync_channel, get_state) = SessionExt.make_2_side(empty(scene_url, client_id, width, height), on_message, Sync.on_message);
        do Session.send(central_modelers, {register; ~scene_url; ~sync_channel; ~client_id});
        (channel, (-> get_state().modeler.scene), (->get_state().subjects), (->get_state().modeler.views)) ;
      support_gpu_picking = Mutable.make(Option.none);
      lock_mouse_event = Mutable.make(false);
      mouse_listener(msg) =
        match msg with
        | { mousedown; event=_; ~abs_full_pos; ~gl_pos } ->
          f(gpu_picker_regsiter) =
            do lock_mouse_event.set(true);
            gpu_picker_regsiter((abs_full_pos, (possible_target -> do Session.send(channel, {click_on_scene; where=gl_pos; ~possible_target; last_coord_fixer=Option.none}); lock_mouse_event.set(false)))) ;
          Option.switch(f, void, support_gpu_picking.get())
        | { mouseup; event=_; ~gl_pos; ~switch } ->
          msg = {modeler_apply_possible_move; where=gl_pos; ~switch };
          Session.send(channel, msg)
        end ;
      res = initGL(#{id_canvas_canvas}, width, height, get_scene, get_camera_setting, mouse_listener, support_gpu_picking) ;
      if Outcome.is_failure(res) then ignore(Dom.put_replace(parent_sel, Dom.of_xhtml(fail_msg)))
      else
        the_subjects = get_subjects();
        do setup_menu(parent_sel, channel, the_subjects.tool);
        setup_selection_view(parent_sel, channel, the_subjects.selection);
    base =

<canvas width={width} height={height} id=#{id_canvas_canvas} onready={and_do} ></canvas>
;
    ignore(Dom.put_inside(parent_sel, Dom.of_xhtml(base)));

}}


adding some icon to tool

adding some icon to tool

import stdlib.widgets.colorpicker
import stdlib.widgets.core

type GuiModeler.t = {
  modeler: Modeler.modeler;
  subjects: {
    tool: subject(Modeler.tool);
    selection: { this: subject(option(Scene.objects)); color: subject(ColorFloat.color) }
    };
  width: int; height: int
} ;

type subjects_changes = list({tool} / { selection: list({this} / {color}) });

apply_changes(state : GuiModeler.t, changes : subjects_changes) =
  modeler = state.modeler;
  a(tmp_a, sub) = match tmp_a with
    | {tool} -> { sub with tool=Observable.change_state(modeler.tool, sub.tool) }
    | {selection=chan_sel} ->
      b(tmp_b, sel) = match tmp_b with
        | {this} -> { sel with this=Observable.change_state(modeler.scene.selection, sel.this) }
        | {color} ->
          tmp = Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection);
          { sel with color=Observable.change_state(tmp , sel.color) }
        end;
      { sub with selection=List.fold(b, chan_sel, sub.selection) }
    end;
  { state with subjects=List.fold(a, changes : subjects_changes, state.subjects) } : GuiModeler.t;

@client GuiModeler = {{

  Sync = {{
    on_message(state : GuiModeler.t, message) =
      set_modeler(modeler) = { set={ state with ~modeler } } ;
      set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
      match message : Central.Modelers.sync.message with
      | {load=a_scene} ->
        modeler = Modeler.load(`Scene.Client`.load(a_scene, state.modeler.client_id), state.modeler.address, state.modeler.client_id, float_of_int(state.height) / float_of_int(state.width));
        set(apply_changes({state with ~modeler}, [{selection=[{this},{color}]}]))
      | { write_patch; ~patch } ->
        modeler = Modeler.write_patch(state.modeler, patch);
        set_modeler(modeler)
      end ;
  }}

  empty(scene_url, client_id, width, height) : GuiModeler.t =
    modeler = Modeler.empty(scene_url, client_id, float_of_int(height) / float_of_int(width));
    subjects = { tool=Observable.make(modeler.tool); selection={ this=Observable.make(modeler.scene.selection); color=Observable.make( Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection) ) } };
    { ~modeler; ~subjects; ~width; ~height };

  @private on_message(state : GuiModeler.t, message) =
    set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
    send_opatch(opatch) =
      m(patch) = { apply_patch; ~patch; address=state.modeler.address };
      Option.iter((p -> Session.send(central_modelers, m(p))), opatch);
    apply_changes(modeler, cs) = apply_changes({ state with ~modeler }, cs);
    reset_subjects(modeler) = apply_changes(modeler, [{selection=[{this}, {color}] }]);
    match message with
    | {click_on_scene; ~where; ~possible_target} ->
      (modeler, opatch) = Modeler.tool_use(state.modeler, where, possible_target);
      do send_opatch(opatch);
      if Observable.get_state(state.subjects.selection.this) == modeler.scene.selection then
        { set={ state with ~modeler } }
      else
        set(reset_subjects(modeler))
    | {modeler_change_tool=new_tool} ->
      modeler = Modeler.tool_change(state.modeler, new_tool);
      set(apply_changes(modeler, [{tool}]))
    | {modeler_change_scene_selection_color; ~new_color} ->
      (modeler, opatch) = Modeler.scene_change_selection_color(state.modeler, new_color);
      do send_opatch(opatch);
      set(apply_changes(modeler, [{selection=[{color}]}]))
    | {modeler_apply_possible_move; ~where; ~switch } ->
      (modeler, opatch) = Modeler.do_possible_move(state.modeler, where, switch);
      do send_opatch(opatch);
      { set={ state with ~modeler } }
    | {modeler_delete_scene_selection} ->
      (modeler, opatch) = Modeler.scene_delete_selection(state.modeler);
      do send_opatch(opatch);
      set(reset_subjects(modeler))
    end ;

  setup_menu(parent_sel, channel, s_tool) : void =
    f(some_tool) = match some_tool with
      | {selection} -> (↖ Select, ❑ Cube)
      | {add_cube} -> (↖ Select, ❑ Cube)
      end ;
    (id_a, id_b) = (Random.string(7), Random.string(7));
    on_tool_change =
      then_do(new_tool) =
        (text_a, text_b) = f(new_tool);
        Dom.transform([#{id_a}<- text_a, #{id_b}<- text_b]);
      { si=(-> not(Dom.is_empty(#{id_a})) && not(Dom.is_empty(#{id_b}))); ~then_do; else_autoclean };
    do Observable.register(on_tool_change, s_tool);
    menu =
      s(x) = (_ -> Session.send(channel, {modeler_change_tool=x}));

;
    ignore(Dom.put_at_start(parent_sel, Dom.of_xhtml(menu)));

  setup_selection_view(parent_sel, channel, s_selection) : void =
    id = Random.string(7);
    do
      f(some_selection) = match some_selection with
        | {none} -> <></>
        | {some=_} ->
          id = Random.string(7);
          config =
            style = { WColorpicker.default_config_style with preview=WStyler.make_style(css { height:10px; width:10px; border:1px double black;}) };
            s(a_color) =
              Session.send(channel, {modeler_change_scene_selection_color; new_color=ColorFloat.from_Color_color(a_color)});
            { WColorpicker.default_config with on_select=s; ~style };
          do
            on_sel_color_change =
              then_do(f_new_color) =
                new_color = ColorFloat.to_Color_color(f_new_color);
                WColorpicker.set_color(id, config, new_color, false);
              { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
            Observable.register(on_sel_color_change, s_selection.color);

            Cube

Color:
{ WColorpicker.html(id, config) }

 Session.send(channel, {modeler_delete_scene_selection})}>Remove me

        end ;
      on_selection_change =
        then_do(new_selection) = Dom.transform([#{id}<- f(new_selection)]);
        { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
      Observable.register(on_selection_change, s_selection.this);
    sel =

;
    ignore(Dom.put_at_end(parent_sel, Dom.of_xhtml(sel)));

  init(scene_url, client_id, parent_sel:dom, width, height) : void =
    id_canvas_canvas = "canvas_canvas" ;
    fail_msg =
      <>It seems that your browser and/or graphics card are incompatible with Webgl.Learn a little more about webgl support</> ;
    and_do(_) =
      (channel, get_scene, get_subjects, get_camera_setting) =
        (channel, sync_channel, get_state) = SessionExt.make_2_side(empty(scene_url, client_id, width, height), on_message, Sync.on_message);
        do Session.send(central_modelers, {register; ~scene_url; ~sync_channel; ~client_id});
        (channel, (-> get_state().modeler.scene), (->get_state().subjects), (->get_state().modeler.views)) ;
      support_gpu_picking = Mutable.make(Option.none);
      mouse_listener =
        epsilon_sensi = 200;
        rec on_message_mouse_listener(state_mouse, msg) =
          match msg with
          | { mousedown; event=_; ~abs_full_pos; ~gl_pos; ~now } ->
            f(gpu_picker_regsiter) =
              sub(possible_target) =
                do Session.send(channel, {click_on_scene; where=gl_pos; ~possible_target});
                Session.send(channel_mouse_listener, { _internal; done_down });
              gpu_picker_regsiter((abs_full_pos, sub)) ;
            do Option.iter(f, support_gpu_picking.get());
            { set={ running_down=List.empty; time_tag=now } }
          | { mouseup; event=_; ~gl_pos; ~switch; now=when_up } ->
            f() =
              msg = {modeler_apply_possible_move; where=gl_pos; ~switch };
              Session.send(channel, msg);
            match state_mouse with
            | { nothing; ~time_tag } ->
              do if (when_up - time_tag) >= epsilon_sensi then f() else void;
              { unchanged }
            | { running_down=l; ~time_tag } ->
              { set={ running_down=List.cons((when_up, f), l); ~time_tag } }
            end
          | { _internal; done_down } -> match state_mouse with
            | { nothing; time_tag=_ } -> { unchanged }
            | { running_down=l; ~time_tag } ->
              g((when_up, f)) =
                if (when_up - time_tag) >= epsilon_sensi then f() else void;
              do List.iter(g, l); { set={ nothing; ~time_tag } }
            end
          end
        and val channel_mouse_listener = Session.make({nothing; time_tag=0}, on_message_mouse_listener);
        (msg -> Session.send(channel_mouse_listener, msg));
      res = initGL(#{id_canvas_canvas}, width, height, get_scene, get_camera_setting, mouse_listener, support_gpu_picking) ;
      if Outcome.is_failure(res) then ignore(Dom.put_replace(parent_sel, Dom.of_xhtml(fail_msg)))
      else
        the_subjects = get_subjects();
        do setup_menu(parent_sel, channel, the_subjects.tool);
        setup_selection_view(parent_sel, channel, the_subjects.selection);
    base =

<canvas width={width} height={height} id=#{id_canvas_canvas} onready={and_do} ></canvas>
;
    ignore(Dom.put_inside(parent_sel, Dom.of_xhtml(base)));

}}

import stdlib.widgets.colorpicker
import stdlib.widgets.core

type GuiModeler.t = {
  modeler: Modeler.modeler;
  subjects: {
    tool: subject(Modeler.tool);
    selection: { this: subject(option(Scene.objects)); color: subject(ColorFloat.color) }
    };
  width: int; height: int
} ;

type subjects_changes = list({tool} / { selection: list({this} / {color}) });

apply_changes(state : GuiModeler.t, changes : subjects_changes) =
  modeler = state.modeler;
  a(tmp_a, sub) = match tmp_a with
    | {tool} -> { sub with tool=Observable.change_state(modeler.tool, sub.tool) }
    | {selection=chan_sel} ->
      b(tmp_b, sel) = match tmp_b with
        | {this} -> { sel with this=Observable.change_state(modeler.scene.selection, sel.this) }
        | {color} ->
          tmp = Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection);
          { sel with color=Observable.change_state(tmp , sel.color) }
        end;
      { sub with selection=List.fold(b, chan_sel, sub.selection) }
    end;
  { state with subjects=List.fold(a, changes : subjects_changes, state.subjects) } : GuiModeler.t;

@client GuiModeler = {{

  Sync = {{
    on_message(state : GuiModeler.t, message) =
      set_modeler(modeler) = { set={ state with ~modeler } } ;
      set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
      match message : Central.Modelers.sync.message with
      | {load=a_scene} ->
        modeler = Modeler.load(`Scene.Client`.load(a_scene, state.modeler.client_id), state.modeler.address, state.modeler.client_id, float_of_int(state.height) / float_of_int(state.width));
        set(apply_changes({state with ~modeler}, [{selection=[{this},{color}]}]))
      | { write_patch; ~patch } ->
        modeler = Modeler.write_patch(state.modeler, patch);
        set_modeler(modeler)
      end ;
  }}

  empty(scene_url, client_id, width, height) : GuiModeler.t =
    modeler = Modeler.empty(scene_url, client_id, float_of_int(height) / float_of_int(width));
    subjects = { tool=Observable.make(modeler.tool); selection={ this=Observable.make(modeler.scene.selection); color=Observable.make( Option.switch((o->o.color), ColorFloat.random(), modeler.scene.selection) ) } };
    { ~modeler; ~subjects; ~width; ~height };

  @private on_message(state : GuiModeler.t, message) =
    set(new_state) = { set={ state with subjects=new_state.subjects; modeler=new_state.modeler } };
    send_opatch(opatch) =
      m(patch) = { apply_patch; ~patch; address=state.modeler.address };
      Option.iter((p -> Session.send(central_modelers, m(p))), opatch);
    apply_changes(modeler, cs) = apply_changes({ state with ~modeler }, cs);
    reset_subjects(modeler) = apply_changes(modeler, [{selection=[{this}, {color}] }]);
    match message with
    | {click_on_scene; ~where; ~possible_target} ->
      (modeler, opatch) = Modeler.tool_use(state.modeler, where, possible_target);
      do send_opatch(opatch);
      if Observable.get_state(state.subjects.selection.this) == modeler.scene.selection then
        { set={ state with ~modeler } }
      else
        set(reset_subjects(modeler))
    | {modeler_change_tool=new_tool} ->
      modeler = Modeler.tool_change(state.modeler, new_tool);
      set(apply_changes(modeler, [{tool}]))
    | {modeler_change_scene_selection_color; ~new_color} ->
      (modeler, opatch) = Modeler.scene_change_selection_color(state.modeler, new_color);
      do send_opatch(opatch);
      set(apply_changes(modeler, [{selection=[{color}]}]))
    | {modeler_apply_possible_move; ~where; ~switch } ->
      (modeler, opatch) = Modeler.do_possible_move(state.modeler, where, switch);
      do send_opatch(opatch);
      { set={ state with ~modeler } }
    | {modeler_delete_scene_selection} ->
      (modeler, opatch) = Modeler.scene_delete_selection(state.modeler);
      do send_opatch(opatch);
      set(reset_subjects(modeler))
    end ;

  setup_menu(parent_sel, channel, s_tool) : void =
    f(some_tool) = match some_tool with
      | {selection} -> (Select, Cube)
      | {add_cube} -> (Select, Cube)
      end ;
    (id_a, id_b) = (Random.string(7), Random.string(7));
    on_tool_change =
      then_do(new_tool) =
        (text_a, text_b) = f(new_tool);
        Dom.transform([#{id_a}<- text_a, #{id_b}<- text_b]);
      { si=(-> not(Dom.is_empty(#{id_a})) && not(Dom.is_empty(#{id_b}))); ~then_do; else_autoclean };
    do Observable.register(on_tool_change, s_tool);
    menu =
      s(x) = (_ -> Session.send(channel, {modeler_change_tool=x}));

;
    ignore(Dom.put_at_start(parent_sel, Dom.of_xhtml(menu)));

  setup_selection_view(parent_sel, channel, s_selection) : void =
    id = Random.string(7);
    do
      f(some_selection) = match some_selection with
        | {none} -> <></>
        | {some=_} ->
          id = Random.string(7);
          config =
            style = { WColorpicker.default_config_style with preview=WStyler.make_style(css { height:10px; width:10px; border:1px double black;}) };
            s(a_color) =
              Session.send(channel, {modeler_change_scene_selection_color; new_color=ColorFloat.from_Color_color(a_color)});
            { WColorpicker.default_config with on_select=s; ~style };
          do
            on_sel_color_change =
              then_do(f_new_color) =
                new_color = ColorFloat.to_Color_color(f_new_color);
                WColorpicker.set_color(id, config, new_color, false);
              { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
            Observable.register(on_sel_color_change, s_selection.color);

            Cube

Color:
{ WColorpicker.html(id, config) }

 Session.send(channel, {modeler_delete_scene_selection})}>Remove me

        end ;
      on_selection_change =
        then_do(new_selection) = Dom.transform([#{id}<- f(new_selection)]);
        { si=(->not(Dom.is_empty(#{id}))); ~then_do; else_autoclean };
      Observable.register(on_selection_change, s_selection.this);
    sel =

;
    ignore(Dom.put_at_end(parent_sel, Dom.of_xhtml(sel)));

  init(scene_url, client_id, parent_sel:dom, width, height) : void =
    id_canvas_canvas = "canvas_canvas" ;
    fail_msg =
      <>It seems that your browser and/or graphics card are incompatible with Webgl.Learn a little more about webgl support</> ;
    and_do(_) =
      (channel, get_scene, get_subjects, get_camera_setting) =
        (channel, sync_channel, get_state) = SessionExt.make_2_side(empty(scene_url, client_id, width, height), on_message, Sync.on_message);
        do Session.send(central_modelers, {register; ~scene_url; ~sync_channel; ~client_id});
        (channel, (-> get_state().modeler.scene), (->get_state().subjects), (->get_state().modeler.views)) ;
      support_gpu_picking = Mutable.make(Option.none);
      mouse_listener =
        epsilon_sensi = 200;
        rec on_message_mouse_listener(state_mouse, msg) =
          match msg with
          | { mousedown; event=_; ~abs_full_pos; ~gl_pos; ~now } ->
            f(gpu_picker_regsiter) =
              sub(possible_target) =
                do Session.send(channel, {click_on_scene; where=gl_pos; ~possible_target});
                Session.send(channel_mouse_listener, { _internal; done_down });
              gpu_picker_regsiter((abs_full_pos, sub)) ;
            do Option.iter(f, support_gpu_picking.get());
            { set={ running_down=List.empty; time_tag=now } }
          | { mouseup; event=_; ~gl_pos; ~switch; now=when_up } ->
            f() =
              msg = {modeler_apply_possible_move; where=gl_pos; ~switch };
              Session.send(channel, msg);
            match state_mouse with
            | { nothing; ~time_tag } ->
              do if (when_up - time_tag) >= epsilon_sensi then f() else void;
              { unchanged }
            | { running_down=l; ~time_tag } ->
              { set={ running_down=List.cons((when_up, f), l); ~time_tag } }
            end
          | { _internal; done_down } -> match state_mouse with
            | { nothing; time_tag=_ } -> { unchanged }
            | { running_down=l; ~time_tag } ->
              g((when_up, f)) =
                if (when_up - time_tag) >= epsilon_sensi then f() else void;
              do List.iter(g, l); { set={ nothing; ~time_tag } }
            end
          end
        and val channel_mouse_listener = Session.make({nothing; time_tag=0}, on_message_mouse_listener);
        (msg -> Session.send(channel_mouse_listener, msg));
      res = initGL(#{id_canvas_canvas}, width, height, get_scene, get_camera_setting, mouse_listener, support_gpu_picking) ;
      if Outcome.is_failure(res) then ignore(Dom.put_replace(parent_sel, Dom.of_xhtml(fail_msg)))
      else
        the_subjects = get_subjects();
        do setup_menu(parent_sel, channel, the_subjects.tool);
        setup_selection_view(parent_sel, channel, the_subjects.selection);
    base =

<canvas width={width} height={height} id=#{id_canvas_canvas} onready={and_do} ></canvas>
;
    ignore(Dom.put_inside(parent_sel, Dom.of_xhtml(base)));

}}


a get follow by a quick set will trigger an false warning

type notify('a) =
  { si : (-> bool); then_do : ('a -> void); else_autoclean } ;

@abstract type subject('a) =
  { state : 'a
  ; viewers : channel( {attach : notify('a)} / {propagate : 'a} / {check : 'a})
  ; id : int
  } ;

Observable =

  sl(g : -> void) = sleep(1, g) ;

  make(a : 'a) = (
    randomize_list(l : list) : list = List.sort_by((_ -> Random.int(300)), l);
    id = Random.int(100000) ;
    f(state : 'state, msg : 'msg) : Session.instruction('state) = (
      match msg with
      | { check=fun_val_a } ->
        {} =
          if (fun_val_a != state.recall) then
            Log.warning("TT", "(#17982) WARNING in Observable, a 'functional' subject is out of synchronisation with his session state") ;
        { unchanged }
      | { attach=f } ->
        // here we do not test if the notifier exists
        {} = sl(-> f.then_do(state.recall)) ;
        { set={ state with stack = List.cons(f, state.stack) } }
      | { propagate=na } ->
        //{} = ttlog(">> propagate id='{id}'") ;
        {} =
          state =
            new_stack =
              filter(z : notify) = if z.si() then Option.some(z) else Option.none ;
              List.filter_map(filter, state.stack) ;
            { state with stack=new_stack } ;
          tmp() =
            h_iter(notify) = (
              //{} = ttlog("< propagate id='{id}'");
              notify.then_do(na)) ;
            List.iter(h_iter, randomize_list(state.stack)) ;
          sl(tmp) ;
        { set={ state with recall=na } }
      end );
    viewers = Session.make({ stack=List.empty; recall=a }, f) ;
    { state=a; ~viewers; ~id } : subject );

  get_state(c : subject('a)) : 'a =
    tmp = c.state ;
    {} = Session.send((c).viewers, {check=tmp})
    tmp ;

  register(obs : notify('a), c : subject('a)) : void =
    Session.send((c).viewers, { attach=obs }) ;

  change_state(x : 'a, c : subject('a)) : subject('a) = (
    //{} = ttlog("change state to '{magic_serialize(x)}'  ;  \"id={magic_serialize(c.id)}\"") ;
    {} = Session.send((c).viewers, { propagate=x }) ;
    { c with state=x } : subject );

  get_id(c : subject('a)) : int = (c).id ;

  { ~make
  ; ~get_state
  ; ~register
  ; ~change_state
  ; ~get_id
  } ;
// end of Observable

type notify('a) =
  { si : (-> bool); then_do : ('a -> void); else_autoclean } ;

@abstract type subject('a) =
  { state : 'a
  ; viewers : channel( {attach : notify('a)} / {propagate : 'a} / {check : 'a})
  ; id : int
  } ;

Observable =

  sl(g : -> void) = sleep(1, g) ;

  make(a : 'a) = (
    randomize_list(l : list) : list = List.sort_by((_ -> Random.int(300)), l);
    id = Random.int(100000) ;
    f(state : 'state, msg : 'msg) : Session.instruction('state) = (
      match msg with
      | { check=fun_val_a } ->
        {} =
          if (fun_val_a != state.recall) then
            Log.warning("TT", "(#17982) WARNING in Observable, a 'functional' subject is out of synchronisation with his session state") ;
        { unchanged }
      | { attach=f } ->
        // here we do not test if the notifier exists
        {} = sl(-> f.then_do(state.recall)) ;
        { set={ state with stack = List.cons(f, state.stack) } }
      | { propagate=na } ->
        //{} = ttlog(">> propagate id='{id}'") ;
        {} =
          state =
            new_stack =
              filter(z : notify) = if z.si() then Option.some(z) else Option.none ;
              List.filter_map(filter, state.stack) ;
            { state with stack=new_stack } ;
          tmp() =
            h_iter(notify) = (
              //{} = ttlog("< propagate id='{id}'");
              notify.then_do(na)) ;
            List.iter(h_iter, randomize_list(state.stack)) ;
          sl(tmp) ;
        { set={ state with recall=na } }
      end );
    viewers = Session.make({ stack=List.empty; recall=a }, f) ;
    { state=a; ~viewers; ~id } : subject );

  get_state(c : subject('a)) : 'a =
    tmp = c.state ;
    {} = sleep(33, (-> Session.send((c).viewers, {check=tmp}))) ;
    tmp ;

  register(obs : notify('a), c : subject('a)) : void =
    Session.send((c).viewers, { attach=obs }) ;

  change_state(x : 'a, c : subject('a)) : subject('a) = (
    //{} = ttlog("change state to '{magic_serialize(x)}'  ;  \"id={magic_serialize(c.id)}\"") ;
    {} = Session.send((c).viewers, { propagate=x }) ;
    { c with state=x } : subject );

  get_id(c : subject('a)) : int = (c).id ;

  { ~make
  ; ~get_state
  ; ~register
  ; ~change_state
  ; ~get_id
  } ;
// end of Observable


[clean] removed some logs

[clean] removed some logs

/*  A simple, one-room, scalable real-time web chat

    Copyright (C) 2010-2011  MLstate

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import stdlib.core.date
import stdlib.web.client
import stdlib.system

GITHUB_URL = "https://github.com/Aqua-Ye/OpaChat"

type user = (int, string) // user_id, user_name
type author = { system } / { author : user }
type message = {
  author : author
  text : string
  date : Date.date
  event : string
}
type client_channel = channel(void)
type msg = {message : message} / {connection : (user, client_channel)} / {disconnection : (user, client_channel)} / {stats}

db /history : intmap(message)

@publish room = Network.cloud("room") : Network.network(msg)

@private
users = Mutable.make([]:list(user))

@private
launch_date = Date.now()

@server
server_observe(message) =
  match message
  {connection=(user, client_channel)} ->
    do users.set(List.add(user, users.get()))
    do Network.broadcast({stats}, room)
    do Session.on_remove(client_channel, (->
      do server_observe({disconnection=(user, client_channel)})
      void
    ))
    void
  {disconnection=(user, _client_channel)} ->
    do users.set(List.remove(user, users.get()))
    do Network.broadcast({stats}, room)
    void
  _ -> void

@server
_ = Network.observe(server_observe, room)

@client
update_stats((uptime, mem)) =
  do Dom.transform([#uptime <- <>Uptime: {Date.to_string_time_only(uptime)}</>])
  do Dom.transform([#memory <- <>Memory: {mem} Mo</>])
  void

@client
update_users(users) =
  list = List.fold((elt, acc -> <>{acc}
{elt.f2}
</>), users, <></>)
  do Dom.transform([#users <- <>Users: {List.length(users)}</>])
  do Dom.transform([#user_list <-
{list}
])
  void

@client
message_update(stats)(messages:list(message)) =
  do update_stats(stats)
  do List.iter(message->
    line =

              {Date.to_string_time_only(message.date)}
              { match message.author with
                | {system} ->
                | {~author} -> {author.f2} }
              {message.text}

    do Dom.transform([#conversation +<- line])
    void
  , messages)
  Dom.set_scroll_top(Dom.select_window(), Dom.get_scrollable_size(#content).y_px)

@server
broadcast(author, event, text) =
  message = {~author ~text date=Date.now() ~event}
  do /history[?] <- message
  Network.broadcast({message = message}, room)

@server
build_page(header, content) =

{header}

{content}

@client
send_message(broadcast) =
  _ = broadcast("", Dom.get_value(#entry))
  Dom.clear_value(#entry)

@client
do_broadcast(broadcast)(_) = send_message(broadcast)

@server
mem() = System.get_memory_usage()/(1024*1024)

compute_stats() =
  uptime_duration = Date.between(launch_date, Date.now())
  uptime = Date.of_duration(uptime_duration)
  uptime = Date.shift_backward(uptime, Date.to_duration(Date.milliseconds(3600000))) // 1 hour shift
  (uptime, mem())

@server
client_observe(msg) =
  match msg
  {~message} -> message_update(compute_stats())([message])
  {stats} ->
    do update_stats(compute_stats())
    do update_users(users.get())
    void
  _ -> void

@server
init_client(user, client_channel) =
  obs = Network.observe(client_observe, room)
  _ = Network.broadcast({connection=(user, client_channel)}, room)
  do Dom.bind_beforeunload_confirmation(_ ->
    do Network.broadcast({disconnection=(user, client_channel)}, room)
    do Network.unobserve(obs)
    {none}
  )
  history_list = IntMap.To.val_list(/history)
  len = List.length(history_list)
  history = List.drop(len-20, history_list)
  do message_update(compute_stats())(history)
  void

@server
launch_chat(user:user, client_channel) =
  broadcast = broadcast({author=user}, _, _)
  build_page(
    Fork me on GitHub !,

 init_client(user, client_channel)}/>

      <input id=#entry onready={_ -> Dom.give_focus(#entry)} onnewline={do_broadcast(broadcast)}/>
      Send

  )

@client
do_join(user_id, launch)(_) =
  user = (user_id, Dom.get_value(#author))
  client_channel = Session.make_callback(ignore)
  Dom.transform([
    #main <- <>Loading chat...</>,
    #main <- launch(user, client_channel)
  ])

@server
main() =
  user_id = Random.int(65536) // a user identifier

{
    build_page(

Wecome to OpaChat
,
      Choose your name:
      <input id=#author onready={_ -> Dom.give_focus(#author)} onnewline={do_join(user_id, launch_chat)}/>
      Join
    )
  }

server =
  Server.one_page_bundle(
    "OpaChat - a chat built with Opa",
    [@static_resource_directory("resources")], // include resources directory
    ["resources/style.css"], // web application CSS
    main
  )

/*  A simple, one-room, scalable real-time web chat

    Copyright (C) 2010-2011  MLstate

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import stdlib.core.date
import stdlib.web.client
import stdlib.system

GITHUB_URL = "https://github.com/Aqua-Ye/OpaChat"

type user = (int, string) // user_id, user_name
type author = { system } / { author : user }
type message = {
  author : author
  text : string
  date : Date.date
  event : string
}
type client_channel = channel(void)
type msg = {message : message} / {connection : (user, client_channel)} / {disconnection : (user, client_channel)} / {stats}

db /history : intmap(message)

@publish room = Network.cloud("room") : Network.network(msg)

@private
users = Mutable.make([]:list(user))

@private
launch_date = Date.now()

@server
server_observe(message) =
  match message
  {connection=(user, client_channel)} ->
    do jlog("connection {user}")
    do users.set(List.add(user, users.get()))
    do Network.broadcast({stats}, room)
    do Session.on_remove(client_channel, (->
      do jlog("auto disconnection {user}")
      do server_observe({disconnection=(user, client_channel)})
      void
    ))
    void
  {disconnection=(user, _client_channel)} ->
    do jlog("disconnection {user}")
    do users.set(List.remove(user, users.get()))
    do Network.broadcast({stats}, room)
    void
  _ -> void

@server
_ = Network.observe(server_observe, room)

@client
update_stats((uptime, mem)) =
  do Dom.transform([#uptime <- <>Uptime: {Date.to_string_time_only(uptime)}</>])
  do Dom.transform([#memory <- <>Memory: {mem} Mo</>])
  void

@client
update_users(users) =
  list = List.fold((elt, acc -> <>{acc}
{elt.f2}
</>), users, <></>)
  do Dom.transform([#users <- <>Users: {List.length(users)}</>])
  do Dom.transform([#user_list <-
{list}
])
  void

@client
message_update(stats)(messages:list(message)) =
  do update_stats(stats)
  do List.iter(message->
    line =

              {Date.to_string_time_only(message.date)}
              { match message.author with
                | {system} ->
                | {~author} -> {author.f2} }
              {message.text}

    do Dom.transform([#conversation +<- line])
    void
  , messages)
  Dom.set_scroll_top(Dom.select_window(), Dom.get_scrollable_size(#content).y_px)

@server
broadcast(author, event, text) =
  message = {~author ~text date=Date.now() ~event}
  do /history[?] <- message
  Network.broadcast({message = message}, room)

@server
build_page(header, content) =

{header}

{content}

@client
send_message(broadcast) =
  _ = broadcast("", Dom.get_value(#entry))
  Dom.clear_value(#entry)

@client
do_broadcast(broadcast)(_) = send_message(broadcast)

@server
mem() = System.get_memory_usage()/(1024*1024)

compute_stats() =
  uptime_duration = Date.between(launch_date, Date.now())
  uptime = Date.of_duration(uptime_duration)
  uptime = Date.shift_backward(uptime, Date.to_duration(Date.milliseconds(3600000))) // 1 hour shift
  (uptime, mem())

@server
client_observe(msg) =
  match msg
  {~message} -> message_update(compute_stats())([message])
  {stats} ->
    do update_stats(compute_stats())
    do update_users(users.get())
    void
  _ -> void

@server
init_client(user, client_channel) =
  obs = Network.observe(client_observe, room)
  _ = Network.broadcast({connection=(user, client_channel)}, room)
  do Dom.bind_beforeunload_confirmation(_ ->
    do Network.broadcast({disconnection=(user, client_channel)}, room)
    do Network.unobserve(obs)
    {none}
  )
  history_list = IntMap.To.val_list(/history)
  len = List.length(history_list)
  history = List.drop(len-20, history_list)
  do message_update(compute_stats())(history)
  void

@server
launch_chat(user:user, client_channel) =
  broadcast = broadcast({author=user}, _, _)
  build_page(
    Fork me on GitHub !,

 init_client(user, client_channel)}/>

      <input id=#entry onready={_ -> Dom.give_focus(#entry)} onnewline={do_broadcast(broadcast)}/>
      Send

  )

@client
do_join(user_id, launch)(_) =
  user = (user_id, Dom.get_value(#author))
  client_channel = Session.make_callback(ignore)
  Dom.transform([
    #main <- <>Loading chat...</>,
    #main <- launch(user, client_channel)
  ])

@server
main() =
  user_id = Random.int(65536) // a user identifier

{
    build_page(

Wecome to OpaChat
,
      Choose your name:
      <input id=#author onready={_ -> Dom.give_focus(#author)} onnewline={do_join(user_id, launch_chat)}/>
      Join
    )
  }

server =
  Server.one_page_bundle(
    "OpaChat - a chat built with Opa",
    [@static_resource_directory("resources")], // include resources directory
    ["resources/style.css"], // web application CSS
    main
  )


[CSS] attach stylesheet to scene

[CSS] attach stylesheet to scene

@private id_work_area = "work_area" ;
@private id_gui_area = "gui_area" ;

default_css = css
#{id_gui_area} {
}
;

Css = [ default_css ] ;

_ = Random.random_init();

/*
ref = Mutable.make(4)
rec val timer42 = Scheduler.make_timer(1000, (->
  do jlog("a")
  if ref.get() == 0 then timer42.stop()
))
do timer42.start()
*/

server_modeler_static_page(scene_url) =
  width = 500;
  height = 500;

 GuiModeler.init(scene_url, SHF(), #{id_gui_area}, width, height)}/>

 ;

resources = Rule.of_map(@static_include_directory("./src/css"))

urls =
  parser
  | "/" r=resources -> r
  | "/scene/" scene_url=(.*) ->
    Resource.styled_page("3D creation", ["/src/css/style.css"],server_modeler_static_page(Text.to_string(scene_url)))
  | (.*) ->
    html("Welcome", server_welcome_static_page())
  end ;

server = simple_server(urls) ;

@private id_work_area = "work_area" ;
@private id_gui_area = "gui_area" ;

default_css = css
#{id_gui_area} {
   background-color: #D000D0;
}
;

css = [ default_css ] ;

_ = Random.random_init();

/*
ref = Mutable.make(4)
rec val timer42 = Scheduler.make_timer(1000, (->
  do jlog("a")
  if ref.get() == 0 then timer42.stop()
))
do timer42.start()
*/

server_modeler_static_page(scene_url) =
  width = 500;
  height = 500;

 GuiModeler.init(scene_url, SHF(), #{id_gui_area}, width, height)}/>

 ;

urls =
  parser
  | "/scene/" scene_url=(.*) ->
    html("3D creation", server_modeler_static_page(Text.to_string(scene_url)))
  | (.*) ->
    html("Welcome", server_welcome_static_page())
  end ;

server = simple_server(urls) ;


hack, bug fix, removing a webgl warning

hack, bug fix, removing a webgl warning

type engine.color = ColorFloat.color;
type engine.position = vec3;

type object.pickable = {
  vertexPositions: Webgl.WebGLBuffer;
  itemSize: int;
  numItems: int;
  vertexNormals: Webgl.WebGLBuffer;
  vertexIndexs: Webgl.WebGLBuffer;
  picking_color: engine.color;
  id: hidden_id;
  color: engine.color;
  position: engine.position;
  is_selected: bool;
  beginMode: Webgl.GLenum;
  numIndexs: int
}

type object.simple = {
  positions: Webgl.WebGLBuffer;
  itemSize: int; numItems: int;
  beginMode: Webgl.GLenum;
  color: engine.color;
};

type object = object.pickable;

display_simple(eng, object : object.simple) =
  rec gl = eng.context and color = object.color and shaderProgram = eng.shaderProgram;
  do Webgl.uniform3f(gl, shaderProgram.ambientColorUniform, color.f1, color.f2, color.f3);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), object.positions);
  do Webgl.vertexAttribPointer(gl, shaderProgram.vertexPositionAttribute, object.itemSize, Webgl.FLOAT(gl), false, 0, 0);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), object.positions);
  do Webgl.vertexAttribPointer(gl, shaderProgram.vertexNormalAttribute, object.itemSize, Webgl.FLOAT(gl), false, 0, 0);
  do Webgl.drawArrays(gl, Webgl.LINES(gl), 0, object.numItems);
  void ;

Lines = {{
  create(gl, vertices, color) =
    vertexPositionBuffer = Webgl.createBuffer(gl);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), vertexPositionBuffer);
    do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl),
      Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertices)),
      Webgl.STATIC_DRAW(gl));
    itemSize = 3;
    do Debug.assert(mod(List.length(vertices), itemSize) == 0, (-> "Lines.create : the list got an incorrec size"));
    { positions=vertexPositionBuffer; ~itemSize; numItems=List.length(vertices) / itemSize; beginMode=Webgl.LINES(gl); ~color }
}}

display_pickable(eng, pMatrix, mvMatrix, object : object.pickable, overide_color_for_picking) =
  gl = eng.context; shaderProgram = eng.shaderProgram;
  color =
    if overide_color_for_picking then object.picking_color
    else if object.is_selected then (0.7, 0., 0.) else object.color;
  do Webgl.uniform3f(gl, shaderProgram.ambientColorUniform, color.f1, color.f2, color.f3);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), object.vertexPositions);
  do Webgl.vertexAttribPointer(gl, shaderProgram.vertexPositionAttribute, object.itemSize, Webgl.FLOAT(gl), false, 0, 0);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), object.vertexNormals);
  do Webgl.vertexAttribPointer(gl, shaderProgram.vertexNormalAttribute, object.itemSize, Webgl.FLOAT(gl), false, 0, 0);
  do Webgl.bindBuffer(gl, Webgl.ELEMENT_ARRAY_BUFFER(gl), object.vertexIndexs);

  mvMatrix = Stack.update_and_push(mvMatrix, (o, n -> mat4.translate(o, vec3.from_public(object.position), n))) ;
  do setMatrixUniforms(gl, shaderProgram, pMatrix, Stack.peek(mvMatrix));
  do Webgl.drawElements(gl, object.beginMode, object.numIndexs, Webgl.UNSIGNED_SHORT(gl), 0);
  void
;

Cube = {{
  create(gl, m_object : Scene.objects, is_selected) : object =
    //define our vertex positions as a list
    vertices = [
        // Front face
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0,
         1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,

        // Back face
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0, -1.0, -1.0,

        // Top face
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,

        // Right face
         1.0, -1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0,  1.0,  1.0,
         1.0, -1.0,  1.0,

        // Left face
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
      ];
    vertexNormals = [
      // Front
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,

      // Back
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,

      // Top
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,

      // Bottom
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,

      // Right
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,

      // Left
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0
    ];
    cubeVertexIndices = [
              0, 1, 2,      0, 2, 3,    // Front face
              4, 5, 6,      4, 6, 7,    // Back face
              8, 9, 10,     8, 10, 11,  // Top face
              12, 13, 14,   12, 14, 15, // Bottom face
              16, 17, 18,   16, 18, 19, // Right face
              20, 21, 22,   20, 22, 23  // Left face
          ];
    //global variable to hold the cube buffer
    //We create a buffer for the cube's vertex positions.
    cubeVertexPositionBuffer = Webgl.createBuffer(gl);
    //tells WebGL that any following operations that act on buffers should use the one we specify
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), cubeVertexPositionBuffer);
    //Now, we create a Float32Array object based on our list,
    //and tell WebGL to use it to fill the current buffer
    do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl), Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertices)), Webgl.STATIC_DRAW(gl));
    cubeVertexNormalBuffer = Webgl.createBuffer(gl);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), cubeVertexNormalBuffer);
    do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl), Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertexNormals)), Webgl.STATIC_DRAW(gl));
    cubeVertexIndexBuffer = Webgl.createBuffer(gl);
    do Webgl.bindBuffer(gl, Webgl.ELEMENT_ARRAY_BUFFER(gl), cubeVertexIndexBuffer);
    do Webgl.bufferData(gl, Webgl.ELEMENT_ARRAY_BUFFER(gl), Webgl.Uint16Array.to_ArrayBuffer(Webgl.Uint16Array.from_int_list(cubeVertexIndices)), Webgl.STATIC_DRAW(gl));
    { vertexPositions=cubeVertexPositionBuffer; itemSize=3; numItems=24; vertexNormals=cubeVertexNormalBuffer; vertexIndexs=cubeVertexIndexBuffer; picking_color=ColorFloat.random(); id=m_object.id; color=m_object.color
    ; ~is_selected; position=m_object.cube; beginMode=Webgl.TRIANGLES(gl); numIndexs=36 } : object
  ;

}}

type engine.color = ColorFloat.color;
type engine.position = vec3;

type object.pickable = {
  vertexPositions: Webgl.WebGLBuffer;
  itemSize: int;
  numItems: int;
  vertexNormals: Webgl.WebGLBuffer;
  vertexIndexs: Webgl.WebGLBuffer;
  picking_color: engine.color;
  id: hidden_id;
  color: engine.color;
  position: engine.position;
  is_selected: bool;
  beginMode: Webgl.GLenum;
  numIndexs: int
}

type object.simple = {
  positions: Webgl.WebGLBuffer;
  itemSize: int; numItems: int;
  beginMode: Webgl.GLenum;
  color: engine.color;
};

type object = object.pickable;

display_simple(eng, object : object.simple) =
  rec gl = eng.context and color = object.color and shaderProgram = eng.shaderProgram;
  do Webgl.uniform3f(gl, shaderProgram.ambientColorUniform, color.f1, color.f2, color.f3);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), object.positions);
  do Webgl.vertexAttribPointer(gl, shaderProgram.vertexPositionAttribute, object.itemSize, Webgl.FLOAT(gl), false, 0, 0);
  do Webgl.drawArrays(gl, Webgl.LINES(gl), 0, object.numItems);
  void ;

Lines = {{
  create(gl, vertices, color) =
    vertexPositionBuffer = Webgl.createBuffer(gl);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), vertexPositionBuffer);
    do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl),
      Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertices)),
      Webgl.STATIC_DRAW(gl));
    itemSize = 3;
    do Debug.assert(mod(List.length(vertices), itemSize) == 0, (-> "Lines.create : the list got an incorrec size"));
    { positions=vertexPositionBuffer; ~itemSize; numItems=List.length(vertices) / itemSize; beginMode=Webgl.LINES(gl); ~color }
}}

display_pickable(eng, pMatrix, mvMatrix, object : object.pickable, overide_color_for_picking) =
  gl = eng.context; shaderProgram = eng.shaderProgram;
  color =
    if overide_color_for_picking then object.picking_color
    else if object.is_selected then (0.7, 0., 0.) else object.color;
  do Webgl.uniform3f(gl, shaderProgram.ambientColorUniform, color.f1, color.f2, color.f3);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), object.vertexPositions);
  do Webgl.vertexAttribPointer(gl, shaderProgram.vertexPositionAttribute, object.itemSize, Webgl.FLOAT(gl), false, 0, 0);
  do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), object.vertexNormals);
  do Webgl.vertexAttribPointer(gl, shaderProgram.vertexNormalAttribute, object.itemSize, Webgl.FLOAT(gl), false, 0, 0);
  do Webgl.bindBuffer(gl, Webgl.ELEMENT_ARRAY_BUFFER(gl), object.vertexIndexs);

  mvMatrix = Stack.update_and_push(mvMatrix, (o, n -> mat4.translate(o, vec3.from_public(object.position), n))) ;
  do setMatrixUniforms(gl, shaderProgram, pMatrix, Stack.peek(mvMatrix));
  do Webgl.drawElements(gl, object.beginMode, object.numIndexs, Webgl.UNSIGNED_SHORT(gl), 0);
  void
;

Cube = {{
  create(gl, m_object : Scene.objects, is_selected) : object =
    //define our vertex positions as a list
    vertices = [
        // Front face
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0,
         1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,

        // Back face
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0, -1.0, -1.0,

        // Top face
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,

        // Right face
         1.0, -1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0,  1.0,  1.0,
         1.0, -1.0,  1.0,

        // Left face
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
      ];
    vertexNormals = [
      // Front
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,
       0.0,  0.0,  1.0,

      // Back
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,
       0.0,  0.0, -1.0,

      // Top
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,
       0.0,  1.0,  0.0,

      // Bottom
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,
       0.0, -1.0,  0.0,

      // Right
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,
       1.0,  0.0,  0.0,

      // Left
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0,
      -1.0,  0.0,  0.0
    ];
    cubeVertexIndices = [
              0, 1, 2,      0, 2, 3,    // Front face
              4, 5, 6,      4, 6, 7,    // Back face
              8, 9, 10,     8, 10, 11,  // Top face
              12, 13, 14,   12, 14, 15, // Bottom face
              16, 17, 18,   16, 18, 19, // Right face
              20, 21, 22,   20, 22, 23  // Left face
          ];
    //global variable to hold the cube buffer
    //We create a buffer for the cube's vertex positions.
    cubeVertexPositionBuffer = Webgl.createBuffer(gl);
    //tells WebGL that any following operations that act on buffers should use the one we specify
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), cubeVertexPositionBuffer);
    //Now, we create a Float32Array object based on our list,
    //and tell WebGL to use it to fill the current buffer
    do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl), Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertices)), Webgl.STATIC_DRAW(gl));
    cubeVertexNormalBuffer = Webgl.createBuffer(gl);
    do Webgl.bindBuffer(gl, Webgl.ARRAY_BUFFER(gl), cubeVertexNormalBuffer);
    do Webgl.bufferData(gl, Webgl.ARRAY_BUFFER(gl), Webgl.Float32Array.to_ArrayBuffer(Webgl.Float32Array.from_float_list(vertexNormals)), Webgl.STATIC_DRAW(gl));
    cubeVertexIndexBuffer = Webgl.createBuffer(gl);
    do Webgl.bindBuffer(gl, Webgl.ELEMENT_ARRAY_BUFFER(gl), cubeVertexIndexBuffer);
    do Webgl.bufferData(gl, Webgl.ELEMENT_ARRAY_BUFFER(gl), Webgl.Uint16Array.to_ArrayBuffer(Webgl.Uint16Array.from_int_list(cubeVertexIndices)), Webgl.STATIC_DRAW(gl));
    { vertexPositions=cubeVertexPositionBuffer; itemSize=3; numItems=24; vertexNormals=cubeVertexNormalBuffer; vertexIndexs=cubeVertexIndexBuffer; picking_color=ColorFloat.random(); id=m_object.id; color=m_object.color
    ; ~is_selected; position=m_object.cube; beginMode=Webgl.TRIANGLES(gl); numIndexs=36 } : object
  ;

}}