$\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.
(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)