Boy's Surface

webgl3dgeometry

In 1901, Werner Boy (under direction from David Hilbert) discovered a non-orienting surface which is a self-intersecting immersion of the real projective plane in 3-dimensional space. The surface is obtained by sewing a Möbius strip to the edge of a disk, given by the parametric equations below.

15 Nov 2014 by rm-hull
rm-hull/8891df532d502844ab3e
BOYS-SURFACE.md

$\mathbb{R}^3$ Parametric equations

$x = {\sqrt 2 \cos^2 v \cos (2u) + \cos u \sin (2v) \over 2 - \alpha \sqrt 2 \sin (3u) \sin (2v)}$

$y = {\sqrt 2 \cos^2 v \sin (2u) - \sin u \sin (2v) \over 2 - \alpha \sqrt 2 \sin (3u) \sin (2v)}$

$z = {3 \cos^2 \over 2 - \alpha \sqrt 2 \sin (3u) \sin (2v)}$

for $u \in [-\pi /2, \pi/2]$ and $v \in [0, \pi]$

when $\alpha = 1$, this describes the Boy surface, when $\alpha = 0$, this describes the Roman surface.

By varying $\alpha$ between 0 and 1, a smooth deformation between the Roman surface and Boy surface is demonstrated. Example (at $\alpha = 0.75$)

Try translucent, opaque or shaded styles (these are all a bit slower due to the extra hidden face removal calculations required)


Built using the wireframes library.

boys-surface.cljs
(ns enchilada.boys-surface
  (:require
    [enchilada :refer [canvas ctx value-of canvas-size]]
    [wireframes.renderer.canvas :refer [draw-solid ->canvas]]
    [wireframes.renderer.color :refer [wireframe solid]]
    [wireframes.transform :refer [point combine rotate scale translate degrees->radians]]
    [wireframes.shapes.platonic-solids :refer [cube tetrahedron icosahedron dodecahedron]]
    [wireframes.shapes.primitives :refer [mesh transform-shape center-at-origin]]
    [jayq.core :refer [show]]
    [inkspot.color :refer [coerce]]
    [monet.canvas :refer [get-context fill-rect fill-style]]
    [big-bang.core :refer [big-bang]]))

(def root2 (Math/sqrt 2))
(def Π Math/PI)
(def sin Math/sin)
(def cos Math/cos)

(defn make-boys-surface [divisions alpha]
  (let [i (/ Π divisions)
        d (fn [u v] (- 2 (* alpha root2 (sin (* 3 u)) (sin (* 2 v)))))
        x (fn [u v] (+ (* root2 (cos v) (cos v) (cos (* 2 u))) (* (cos u) (sin (* 2 v)))))
        y (fn [u v] (- (* root2 (cos v) (cos v) (sin (* 2 u))) (* (sin u) (sin (* 2 v)))))
        z (fn [u v] (* 3 (cos v) (cos v)))
        u (range (/ Π -2) (+ (/ Π 2) i) i)
        v (range 0 (+ Π i) i)]
    {:polygons (vec
                (mesh
                  (dec (count u))
                  (dec (count v))))
     :points (vec
               (for [v' v
                     u' u
                     :let [d (d u' v')]]
                 (point
                   (/ (x u' v') d)
                   (/ (y u' v') d)
                   (/ (z u' v') d))))}))

(def rect
  (let [[width height] (canvas-size)]
    {:x 0 :y 0 :w  width :h height}))

(def dimensions
  (mapv rect [:w :h]))

(defn inflate [shape multiplier]
  ((transform-shape (scale multiplier)) shape))

(def divisions
  (js/parseInt (value-of :divisions 40)))

(def alpha
  (js/parseFloat (value-of :alpha 1)))

(def shape
  (->
    (make-boys-surface divisions alpha)
    (center-at-origin)
    (inflate (js/parseFloat (value-of :scale 1)))))

(def style
  (keyword (value-of :style :transparent)))

(def color
  (coerce (value-of :color :white)))

(def color-fn
  (condp = style
    :transparent (wireframe color style)
    :translucent (wireframe color style)
    :opaque (wireframe color style)
    :shaded (solid color)))

(defn render-shape
  "Draws the shape at the given state of the world (the x,y,z rotation angles)"
  [[x y z]]
  (-> ctx
    (fill-style "rgba(255,255,255,0.75")
    (fill-rect rect))
  ((->canvas ctx)
    (partial draw-solid
      {:style style
       :focal-length 3
       :color-fn color-fn
       :shape shape
       :transform (combine
                    (rotate :x (degrees->radians x))
                    (rotate :y (degrees->radians y))
                    (rotate :z (degrees->radians z))
                    (translate 0 0 10))})
    dimensions))

(defn update-state
  "Increment/decrement the rotation angles around the x,y and z axes"
  [event [x y z]]
  [(+ x 0.3) (- y 0.7) (+ z 0.5)])

(->
  ctx
  (fill-style :white)
  (fill-rect rect))

(show canvas)

(big-bang
  :initial-state [0 0 0]
  :on-tick update-state
  :to-draw render-shape)