module Viewer using Blink using Colors using Printf using Main.Engine export ConstructionViewer, display!, opentools!, closetools! # === Blink utilities === append_to_head!(w, type, content) = @js w begin @var element = document.createElement($type) element.appendChild(document.createTextNode($content)) document.head.appendChild(element) end style!(w, stylesheet) = append_to_head!(w, "style", stylesheet) script!(w, code) = append_to_head!(w, "script", code) # === construction viewer === mutable struct ConstructionViewer win::Window function ConstructionViewer() # create window and open developer console win = Window(Blink.Dict(:width => 620, :height => 830)) # set stylesheet style!(win, """ body { background-color: #ccc; } /* the maximum dimensions keep Ganja from blowing up the canvas */ #view { display: block; width: 600px; height: 600px; margin-top: 10px; margin-left: 10px; border-radius: 10px; background-color: #f0f0f0; } #control-panel { width: 600px; height: 200px; box-sizing: border-box; padding: 5px 10px 5px 10px; margin-top: 10px; margin-left: 10px; overflow-y: scroll; border-radius: 10px; background-color: #f0f0f0; } #control-panel > div { margin-top: 5px; padding: 4px; border-radius: 5px; border: solid; font-family: monospace; } """) # load Ganja.js. for an automatically updated web-hosted version, load from # # https://unpkg.com/ganja.js # # instead loadjs!(win, "http://localhost:8000/ganja-1.0.204.js") # create global functions and variables script!(win, """ // create algebra var CGA3 = Algebra(4, 1); // initialize element list and palette var elements = []; var palette = []; // declare handles for the view and its options var view; var viewOpt; // declare handles for the controls var controlPanel; var visToggles; // create scene function function scene() { commands = []; for (let n = 0; n < elements.length; ++n) { if (visToggles[n].checked) { commands.push(palette[n], elements[n]); } } return commands; } function updateView() { requestAnimationFrame(view.update.bind(view, scene)); } """) @js win begin # create view viewOpt = Dict( :conformal => true, :gl => true, :devicePixelRatio => window.devicePixelRatio ) view = CGA3.graph(scene, viewOpt) view.setAttribute(:id, "view") view.removeAttribute(:style) document.body.replaceChildren(view) # create control panel controlPanel = document.createElement(:div) controlPanel.setAttribute(:id, "control-panel") document.body.appendChild(controlPanel) end new(win) end end mprod(v, w) = v[1]*w[1] + v[2]*w[2] + v[3]*w[3] + v[4]*w[4] - v[5]*w[5] function display!(viewer::ConstructionViewer, elements::Matrix) # load elements elements_full = [] for elt in eachcol(Engine.unmix * elements) if mprod(elt, elt) < 0.5 elt_full = [0; elt; fill(0, 26)] else # `elt` is a spacelike vector, representing a generalized sphere, so we # take its Hodge dual before passing it to Ganja.js. the dual represents # the same generalized sphere, but Ganja.js only displays planes when # they're represented by vectors in grade 4 rather than grade 1 elt_full = [fill(0, 26); -elt[5]; -elt[4]; elt[3]; -elt[2]; elt[1]; 0] end push!(elements_full, elt_full) end @js viewer.win elements = $elements_full.map((elt) -> @new CGA3(elt)) # generate palette. this is Gadfly's `default_discrete_colors` palette, # available under the MIT license palette = distinguishable_colors( length(elements_full), [LCHab(70, 60, 240)], transform = c -> deuteranopic(c, 0.5), lchoices = Float64[65, 70, 75, 80], cchoices = Float64[0, 50, 60, 70], hchoices = range(0, stop=330, length=24) ) palette_packed = [RGB24(c).color for c in palette] @js viewer.win palette = $palette_packed # create visibility toggles @js viewer.win begin controlPanel.replaceChildren() visToggles = [] end for (elt, c) in zip(eachcol(elements), palette) vec_str = join(map(t -> @sprintf("%.3f", t), elt), ", ") color_str = "#$(hex(c))" style_str = "background-color: $color_str; border-color: $color_str;" @js viewer.win begin @var toggle = document.createElement(:div) toggle.setAttribute(:style, $style_str) toggle.checked = true toggle.addEventListener( "click", () -> begin toggle.checked = !toggle.checked toggle.style.backgroundColor = toggle.checked ? $color_str : "inherit"; updateView() end ) toggle.appendChild(document.createTextNode($vec_str)) visToggles.push(toggle); controlPanel.appendChild(toggle); end end # update view @js viewer.win updateView() end function opentools!(viewer::ConstructionViewer) size(viewer.win, 1240, 830) opentools(viewer.win) end function closetools!(viewer::ConstructionViewer) closetools(viewer.win) size(viewer.win, 620, 830) end end # ~~~ sandbox setup ~~~ # in the default view, e4 + e5 is the point at infinity elements = Engine.nullmix * sqrt(0.5) * BigFloat[ 1 1 -1 -1 0; 1 -1 1 -1 0; 1 -1 -1 1 0; 0 0 0 0 -sqrt(6); 1 1 1 1 2 ] # show construction viewer = Viewer.ConstructionViewer() Viewer.display!(viewer, elements)