Quatrefoil

Render Three.js with Respo style code(experimental).

Demo(Chrome with a touch screen) http://r.tiye.me/Quatrefoil-GL/quatrefoil/

JS methods

some helpers are split in npm package:

yarn add @quamolit/quatrefoil-utils

Develop

Relies on https://calcit-lang.org .

yarn

caps # get calcit deps in `~/.config/calcit/modules/`

cr -1 js #  compile js once

yarn vite

License

MIT

Touch Control

based on tool of https://github.com/Quatrefoil-GL/touch-control , which is designed for touch screens.

Shortcuts for viewport movememnt

Keyword shortcuts for viewport moving:

  • w move forward
  • s move backward
  • a move left
  • d move left
  • "Up" move up
  • "Down" move down
  • "Shift Up" rotate to view up
  • "Shift Down" rotate to view down
  • "Left" rotate to view left
  • "Right" rotate to view right

Materials

  • Material, currently :line-basic, :line-dashed, :mesh-basic, :mesh-lambert, :mesh-standard.
{} (:kind :mesh-standard)
  :opacity 0.9
  :transparent true
  :roughness 0.5
  :metalness 0.9
  :color 0x9050c0
{} (:kind :mesh-lambert)
  :color 0x808080
  :opacity 0.6
{} (:kind :mesh-basic)
  :color 0xffff55
  :opacity 0.8
  :transparent true
{} (:kind :line-dashed)
  :color 0xccccff
  :opacity 0.3
  :transparent true
  :linewidth 2
  :gapSize 0.5
  :dashSize 0.5
{} (:kind :line-basic)
  :color 0xaaaaff
  :opacity 0.9
  :transparent true

For MeshLine, there's an extra material.

Color

Hex color in number format:

do 0x0000ff

or generate from HSL color:

quatrefoil.core/hslx 200 80 80

HSLuv color based on hsluv:

quatrefoil.core/hsluvx 300 70 90

HCL color is also supported based on d3-color:

quatrefoil.core/hclx 300 70 90

Geometries

a list of geometries

box

  • box
box $ {}
  :width 10
  :height 10
  :depth 10
  :material Material
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

buffer-object

  • buffer-object based on BufferGeometry

and flat-values is a macro for turning lists into a flat one.

buffer-object $ {}
  :vertices $ flat-values
    0 0 0
    10 0 0
    5 0 8
    5 8 0
  :indices $ flat-values
    0 1 2
    0 2 3
    1 2 3

group

  • group, takes children elements:
group $ {}
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1

line

line $ {}
  :points $ [][]
    10 20 10
    100 140 0
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

MeshLine

Based on https://github.com/spite/THREE.MeshLine

Extra npm module is required:

yarn add meshline

to render mesh line:

mesh-line $ {}
  :points $ [] ([] 0 0 0) ([] 3 3 4) ([] 1 4 6) ([] -2 8 0) ([] 2 5 1)
  :position $ [] 5 -10 0
  :material $ {} (:kind :mesh-line) (:color 0xaaaaff)
    :transparent true
    :opacity 0.4
    :depthTest true
    :lineWidth 0.5

implemented with module meshline.

line-segments

Based on https://threejs.org/docs/#api/en/objects/LineSegments.

Pass points in pairs:

line-segments $ {}
  :segments $ []
    [] ([] 0 0 0) ([] 3 3 4)
    [] ([] 1 4 6) ([] -2 8 0)
  :position $ [] 10 -10 0
  :material $ {} (:kind :line-dashed)
    :color 0xaaaaff)
    :opacity 0.9
    :transparent true
    :linewidth 4
    :gapSize 0.5
    :dashSize 0.5

or pass even number of points:

line-segments $ {}
  :points $ [] ([] 0 0 0) ([] 3 3 4) ([] 1 4 6) ([] -2 8 0)
  :position $ [] 15 -10 0
  :material $ {} (:kind :line-dashed)
    :color 0xaaaaff
    :opacity 0.9
    :transparent true
    :linewidth 4
    :gapSize 0.5
    :dashSize 0.5

sphere

sphere $ {}
  :radius 10
  :width-segments 10
  :height-segments 10
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

parametric

  • parametric for parametric geometry
parametric $ {}
  :func $ fn (u v)
    [] 0 0 0
  :slices 10
  :stacks 10

plane-reflector

plane-reflector $ {}
  :width 40
  :height 40
  :color 0xffaaaa
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0

polyhedron

polyhedron $ {}
  :vertices $ [][] (8 4 -2)
    4 9 -3
    -5 -2 -4
    4 2 8
  :indices $ [][] (0 1 2)
    0 1 3
    0 2 3
    1 2 3
  :radius 10
  :detail 0
  :position $ [] 20 10 10
  :material Material

scene

  • scene, which refers to a global scene element:
scene ({})

shape

shape $ {}
  :path $ [][]
    :move-to 0 0
    :line-to 10 10
    :line-to 10 5
    :line-to 0 0
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

spline

spline $ {}
  :points $ [][]
    10 20 10
    100 140 0
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

text

text $ {}
  :text |Demo
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

  :size 4
  :depth 0.5

torus

torus $ {}
  :r1 10
  :r2 2
  :s1 20
  :s2 40
  :arc $ * 2 &PI
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

tube

tube $ {}
  :points-fn $ fn (radio factor)
    [] (* factor radio) 0 0
  :factor 1
  :radius 0.8
  :tubular-segments 400
  :radial-segments 20
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1
  :material Material

shader-mesh

Element to render RawShaderMaterial. Inspired by:

This feature is experimental and might require future changes since lack of familiarity to shaders.

Usage

shader-mesh $ {}
  :attributes $ []
    {} (:id :position) (:size 3) (:type :f32)
      :buffer $ concat &
        [] ([] -10 -20 0) ([] 50 0 0) ([] 13 30 0)
    {} (:id :color) (:size 4) (:type :u8)
      :buffer $ concat &
        [] ([] 1 0 0 1) ([] 0 1 0 1) ([] 0 0 1 1 )
  :material $ {} (:kind :raw-shader)
    :uniforms $ {}
    :vertexShader $ inline-shader "\"demo.vert"
    :fragmentShader $ inline-shader "\"demo.frag"
    :wireframe false
    :transparent false

Demo of shaders:

// vertex

precision mediump float;
precision mediump int;

uniform mat4 modelViewMatrix; // optional
uniform mat4 projectionMatrix; // optional

attribute vec3 position;
attribute vec4 color;

varying vec3 vPosition;
varying vec4 vColor;

void main() {

  vPosition = position;
  vColor = color;

  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

}

// fragment

precision mediump float;
precision mediump int;

uniform float time;

varying vec3 vPosition;
varying vec4 vColor;

void main() {

  vec4 color = vec4( vColor );
  // color.r += sin( vPosition.x * 10.0 + time ) * 0.5;

  gl_FragColor = color;

  // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);

}

some-object

It actually renders a group element with "value" element inside. If "value" element is not loaded, then nothing got mounted. Actual element is stored in the atom quatrefoil.globals/*loaded-objects with a key.

This feature is currently used in rendering GLTF models created via three.js with loaders.

some-object $ {} (:key :sakura)
  :loaded? $ some? (get @*loaded-objects :sakura)
  :position $ [] 0 -100 -100
  :scale $ [] 200 200 200

the fields

  • key specifies the key of object inside *loaded-objects
  • loaded? is only used to trigger updates, not really being used

Components

with options and logics.

Control Point

move point's visual position on canvas:

quatrefoil.comp.control/comp-pin-point
  {}
    :position $ :p0 state
    :speed 0.1
    :color 0xffaaaa
    :opacity 0.8
    :radius 1
    :label |A
    :text-color $ hslx 20 90 80
  fn (next d!)
    d! cursor $ assoc state :p0 next

change value using a point:

quatrefoil.comp.control/comp-value
  {}
    :radius 1
    :value (:v0 state)
    :position $ [] 10 0 0
    :speed 0.2
    :bound $ [] -2 20
    :color $ hslx 200 90 70
    :text-color $ hslx 20 90 80
    :show-text? false
    :fract-length 2
    :opacity 0.8
    :label |A
  fn (v1 d!)
    d! cursor $ assoc state :v0 v1

change a 2d value using a point:

quatrefoil.comp.control/comp-value-2d
  {}
    :value $ :v1 state
    :position $ [] 0 10 0
    :speed 0.2
    :color 0xccaaff
    :fract-length 2
    :label |A
  fn (v d!)
    d! cursor $ assoc state :v1 v

controls a bool value:

comp-switch
  {} (:label "\"Status") (:color 0xaa88ff)
    :value $ :on? state
    :text-color 0xaa88ff
    :position $ [] 20 0 0
  fn (v d!)
    d! cursor $ assoc state :on? v

Lights

ambient-light

ambient-light $ {}
  :color 0xaaaaff
  :intensity 1

point-light

point-light $ {}
  :color 0xaaaaff
  :intensity $ 1
  :distance 100
  :position $ [] 0 0 0

rect-area-light

rect-area-light $ {}
  :color 0xaaaaff
  :intensity 1
  :width 100
  :height 100
  :look-at $ [] 100 100 100
  :position $ [] 0 0 0
  :rotation $ [] 0 0 0
  :scale $ [] 1 1 1

set-perspective-camera!

Initialize camera:

ns app.main
  :require
    quatrefoil.dsl.object3d-dom :refer $ set-perspective-camera!

set-perspective-camera! $ {} (:fov 45)
  :near 0.1
  :far 1000
  :position $ [] 0 0 100
  :aspect $ / js/window.innerWidth js/window.innerHeight

Math

...

Quaternions

Based on https://threejs.org/docs/#api/en/math/Quaternion

w is the factor:

[] x y z w

Under the scope quatrefoil.math:

  • q+ add multiple quaternions
  • q-
  • q*
  • &q* add 2 quaternions
  • &q+
  • &q-
  • q-inverse
  • q-conjugate
  • q-length
  • q-length2 length without call sqrt
  • q-scale takes a quaternion and a scalar
  • v+ add multiple vectors
  • v-
  • &v+ add 2 vectors
  • &v-
  • v-scale takes a vector and a scalar
  • &c+ add 2 complex numbers
  • &c-
  • &c*
  • c-length
  • c-length2
  • c-conjutate