comment {
  Minimal subset of UF's Standard.uxf written from UF's published transform
  algorithms. Paste your UF install's real Standard.uxf into UF_formulas/
  for the full set (Sphere/Egg variants of 3d-mapping, less common entries).
}

Inverse {
; Inverts the complex plane through a circle of @radius around @center.
; Maps z -> center + radius / (z - center). With center=0, radius=1 this
; is just z -> 1/z. Used by InverseLoop animation sample.
transform:
  if @usescreen
    #pixel = #center + @radius / (#pixel - #center)
  else
    #pixel = @center + @radius / (#pixel - @center)
  endif
default:
  title = "Inverse"
  param radius
    caption = "Radius"
    default = 1.0
    min = 0
  endparam
  complex param center
    caption = "Center"
    default = (0, 0)
  endparam
  param usescreen
    caption = "Use Screen Center"
    default = false
  endparam
}

Aspect {
; Forces the displayed fractal aspect ratio to @ar regardless of the
; rendered image's pixel aspect. At @ar=1 the fractal is rendered with
; square pixels (so a wide image stretches the fractal horizontally).
transform:
  #pixel = real(#pixel) * #height / (#width * @ar) + flip(imag(#pixel))
default:
  title = "Aspect Ratio"
  param ar
    caption = "Aspect Ratio"
    default = 1.0
    min = 0.0001
  endparam
}

Twist {
; Twists the plane around @center, with rotation angle @twist (radians)
; at the center decaying as a Gaussian with rate @dec.
transform:
  complex p = #pixel - @center
  float r = cabs(p)
  float ang = @twist * exp(-r * r * @dec)
  p = p * exp(flip(ang))
  #pixel = p + @center
default:
  title = "Twist"
  complex param center
    caption = "Center"
    default = (0, 0)
  endparam
  param twist
    caption = "Amount"
    default = 1.0
  endparam
  param dec
    caption = "Decay"
    default = 1.0
    min = 0
  endparam
}

Lake {
; Reflects pixels below the water line at @wl with a y-displacement of
; @a * dy * sin(@f * dy^0.2), where dy is the distance below the line.
; Amplitude grows linearly with depth; frequency rises slowly via dy^0.2.
; Sylvie Gallet's algorithm, shipped with UF's Standard.uxf.
transform:
  complex centr = @wl
  if @use_sc
    centr = #center
  endif
  float ang = @rot * #pi / 180
  if @use_la
    ang = #angle
  endif
  if ang != 0
    #pixel = (#pixel - centr) * exp(-flip(ang)) + centr
  endif
  float y = imag(#pixel)
  if y < imag(centr)
    float dy = imag(centr) - y
    y = 2 * imag(centr) - y
    y = y + @a * dy * sin(@f * dy^0.2)
    #pixel = real(#pixel) + flip(y)
  endif
  if ang != 0
    #pixel = (#pixel - centr) * exp(flip(ang)) + centr
  endif
default:
  title = "Lake"
  complex param wl
    caption = "Water level"
    default = (0, 0)
  endparam
  param use_sc
    caption = "Use screen center"
    default = true
  endparam
  param rot
    caption = "Angle"
    default = 0.0
  endparam
  param use_la
    caption = "Use Location tab angle"
    default = true
  endparam
  param a
    caption = "Amplitude"
    default = 0.2
  endparam
  param f
    caption = "Frequency"
    default = 300.0
    min = 0
  endparam
}

Ripples {
; Radial ripple distortion around @twirlcenter, fading to identity at
; distance @twirlfade. Four flavours via @twirltype. Algorithm matches
; dmj-Twirl from dmj.uxf, which UF's Standard.uxf bundles as "Ripples".
transform:
  complex c = @twirlcenter
  if @centermove
    c = #center
  endif
  float d = cabs(#pixel - c)
  if d < @twirlfade
    float fade = (1 - d / @twirlfade) * (1 - d / @twirlfade)
    if @twirltype == 0
      #pixel = (#pixel - c) * (0,1) ^ (cos(d * @twirlpitch * #pi) * @twirlscale * fade) + c
    elseif @twirltype == 1
      #pixel = (#pixel - c) * (1 - cos(d * @twirlpitch * #pi) * @twirlscale * fade) + c
    elseif @twirltype == 2
      float a = atan(imag(#pixel - c) / real(#pixel - c))
      if real(#pixel - c) < 0
        a = a + #pi
      endif
      if a < 0
        a = a + 2 * #pi
      endif
      #pixel = (#pixel - c) * (1 - cos(a * @twirlpitch) * @twirlscale * fade) + c
    else
      float a = atan(imag(#pixel - c) / real(#pixel - c))
      if real(#pixel - c) < 0
        a = a + #pi
      endif
      if a < 0
        a = a + 2 * #pi
      endif
      #pixel = (#pixel - c) * (0,1) ^ (cos(a * @twirlpitch) * @twirlscale * fade) + c
    endif
  endif
default:
  title = "Ripples"
  complex param twirlcenter
    caption = "Ripple Center"
    default = (0, 0)
  endparam
  param centermove
    caption = "Use Screen Center"
    default = true
  endparam
  param twirlscale
    caption = "Ripple Strength"
    default = 0.25
  endparam
  param twirlpitch
    caption = "Ripple Frequency"
    default = 10.0
    min = 0
  endparam
  param twirlfade
    caption = "Ripple Fade"
    default = 1.0
    min = 0.00000001
  endparam
  param twirltype
    caption = "Ripple Type"
    enum = "Side to Side" "Forward and Back" "In and Out" "Back and Forth"
    default = 1
  endparam
}

Clipping {
; Marks pixels inside (or outside) a circle / ellipse / square / rectangle
; as #solid. Algorithm matches dmj-Clipping from dmj.uxf.
transform:
  complex p = (0,0)
  complex r = (0,0)
  float re = 0.0
  float te = 0.0
  if @relative
    p = real(#screenpixel) / #width + flip(imag(#screenpixel) / #height)
  else
    p = #pixel
  endif
  if @rotation
    re = cabs(@sciright - @scicenter)
    r = conj(@sciright - @scicenter) / re
  elseif @relative
    re = abs(real(@sciright) - real(@scicenter))
    r = (1, 0)
  else
    r = conj(exp(flip(#angle)))
    re = abs(real((@sciright - @scicenter) * r))
  endif
  te = abs(imag((@scitop - @scicenter) * r))
  p = (p - @scicenter) * r
  bool in = (@sciinside == 1)
  if @scishape == 0
    if |p| < sqr(re)
      in = !in
    endif
  elseif @scishape == 1
    if sqr(real(p)) / sqr(re) + sqr(imag(p)) / sqr(te) < 1.0
      in = !in
    endif
  elseif @scishape == 2
    if abs(real(p)) < re && abs(imag(p)) < re
      in = !in
    endif
  elseif @scishape == 3
    if abs(real(p)) < re && abs(imag(p)) < te
      in = !in
    endif
  endif
  #solid = in
default:
  title = "Clipping"
  complex param scicenter
    caption = "Clipping Center"
    default = (0, 0)
  endparam
  complex param sciright
    caption = "Clipping Right Edge"
    default = (0, 0)
  endparam
  complex param scitop
    caption = "Clipping Top Edge"
    default = (0, 0)
  endparam
  param scishape
    caption = "Clipping Shape"
    enum = "circle" "ellipse" "square" "rectangle"
    default = 2
  endparam
  param rotation
    caption = "Allow Rotation"
    default = false
  endparam
  param sciinside
    caption = "Clipping Region"
    enum = "inside" "outside"
    default = 1
  endparam
  param relative
    caption = "Screen-Relative"
    default = false
  endparam
}

Kaleidoscope {
; N-fold rotational symmetry around @symcenter, with optional reflection
; per wedge. Wedge size = 2*pi/@symorder; reflective folds each wedge
; into a half-wedge mirror. Algorithm matches dmj-Kaleidoscope from
; dmj.uxf, which is the canonical UF kaleidoscope.
transform:
  complex c = @symcenter
  if @centermove
    c = #center
  endif
  complex rot = (0,1) ^ (@angle / 90)
  complex z2 = #pixel - c
  float a = atan2(z2)
  if a < 0
    a = a + 2 * #pi
  endif
  float d = cabs(z2)
  if @symorder > 0
    if @symreflect == 0
      a = a - floor(a * @symorder / #pi / 2) * #pi * 2 / @symorder
      if a > #pi / @symorder
        a = #pi * 2 / @symorder - a
      endif
    elseif @symreflect == 1
      a = a - floor(a * @symorder / #pi / 2) * #pi * 2 / @symorder
    elseif @symreflect == 2
      a = a - floor(a * @symorder / #pi / 2) * #pi * 2 / @symorder
      a = #pi * 2 / @symorder - a
    elseif @symreflect == 3
      if a > #pi * 2 / @symorder
        a = 0
        #solid = true
      endif
    endif
    #pixel = (cos(a) * d + flip(sin(a) * d)) / rot + c
  endif
default:
  title = "Kaleidoscope"
  param symorder
    caption = "Symmetry Order"
    default = 8.0
  endparam
  param symreflect
    caption = "Symmetry Mode"
    enum = "reflective" "left" "right" "slice only"
    default = 0
  endparam
  complex param symcenter
    caption = "Symmetry Center"
    default = (0, 0)
  endparam
  param centermove
    caption = "Use Screen Center"
    default = true
  endparam
  param angle
    caption = "Rotation angle"
    default = 0.0
  endparam
}

Mirror {
; Reflects the plane across a mirror line through @refcenter rotated by
; @angle degrees. axis="horizontal" means the flip is left/right (mirror
; line is vertical); axis="vertical" means top/bottom. With angle=0 and
; centermove=no on a screen centered at @refcenter the result is a
; horizontal flip about that point.
transform:
  complex orig = @refcenter
  if @centermove
    orig = #center
  endif
  complex p = #pixel - orig
  p = p * exp(-flip(@angle * #pi / 180))
  if @axis == 0
    p = -real(p) + flip(imag(p))
  else
    p = real(p) - flip(imag(p))
  endif
  p = p * exp(flip(@angle * #pi / 180))
  #pixel = p + orig
default:
  title = "Mirror"
  param axis
    caption = "Axis"
    enum = "horizontal" "vertical"
    default = 0
  endparam
  param angle
    caption = "Angle"
    default = 0.0
  endparam
  complex param refcenter
    caption = "Reference Center"
    default = (0, 0)
  endparam
  param centermove
    caption = "Center Moves"
    default = false
  endparam
}

3d-mapping {
; Casts the screen pixel as a ray (x,y,4) from camera at (-transx,-transy,
; -transz), rotates the ray by (rotx,roty,rotz) degrees (Z then Y then X),
; intersects with the chosen 3D shape (Plane / Sphere / Egg), and uses the
; hit point's texture coordinates as the new #pixel. Direct port from UF
; Standard.uxf.
transform:
  float sx = -@transx
  float sy = -@transy
  float sz = -@transz
  float dx = real(#pixel)
  float dy = imag(#pixel)
  float dz = 4
  float angx = @rotx * #pi / 180
  float angy = @roty * #pi / 180
  float angz = @rotz * #pi / 180
  float tt = 0
  tt = cos(angz) * sy - sin(angz) * sx
  sx = cos(angz) * sx + sin(angz) * sy
  sy = tt
  tt = cos(angz) * dy - sin(angz) * dx
  dx = cos(angz) * dx + sin(angz) * dy
  dy = tt
  tt = cos(angy) * sx - sin(angy) * sz
  sz = cos(angy) * sz + sin(angy) * sx
  sx = tt
  tt = cos(angy) * dx - sin(angy) * dz
  dz = cos(angy) * dz + sin(angy) * dx
  dx = tt
  tt = cos(angx) * sz - sin(angx) * sy
  sy = cos(angx) * sy + sin(angx) * sz
  sz = tt
  tt = cos(angx) * dz - sin(angx) * dy
  dy = cos(angx) * dy + sin(angx) * dz
  dz = tt
  float lambda = 0
  float u = 0
  float v = 0
  ; Hit-point in 3D: UF source uses bare x/y/z but z collides with the
  ; formula iteration variable in our translator, so name them px/py/pz.
  float px = 0
  float py = 0
  float pz = 0
  if @shape == 0
    lambda = -sy / dy
    if lambda <= 0
      #solid = true
    else
      u = sx + lambda * dx
      v = sz + lambda * dz
    endif
  elseif @shape == 1
    float a = sqr(dx) + sqr(dy) + sqr(dz)
    float b = 2 * sx * dx + 2 * sy * dy + 2 * sz * dz
    float c = sqr(sx) + sqr(sy) + sqr(sz) - 0.25
    float d = sqr(b) - 4 * a * c
    if d < 0
      #solid = true
    else
      if a > 0
        lambda = (-b - sqrt(d)) / (2 * a)
      else
        lambda = (-b + sqrt(d)) / (2 * a)
      endif
      if lambda <= 0
        #solid = true
      else
        px = sx + lambda * dx
        py = sy + lambda * dy
        pz = sz + lambda * dz
        if pz == 0
          if px > 0
            u = 2
          else
            u = -2
          endif
        else
          u = (4 / #pi) * atan(px / abs(pz))
        endif
        v = (4 / #pi) * atan(py / sqrt(sqr(px) + sqr(pz)))
      endif
    endif
  elseif @shape == 2
    float x1 = 0
    float y1 = 0
    float z1 = 0
    float x2 = 0
    float y2 = 0
    float z2 = 0
    bool solid = false
    bool solid1 = false
    bool solid2 = false
    sy = sy * 6/7
    dy = dy * 6/7
    float a = sqr(dx) + sqr(dy) + sqr(dz)
    float b = 2 * sx * dx + 2 * sy * dy + 2 * sz * dz
    float c = sqr(sx) + sqr(sy) + sqr(sz) - 0.25
    float d = sqr(b) - 4 * a * c
    if d < 0
      solid1 = true
    else
      if a > 0
        lambda = (-b - sqrt(d)) / (2 * a)
      else
        lambda = (-b + sqrt(d)) / (2 * a)
      endif
      if lambda <= 0
        solid1 = true
      else
        x1 = sx + lambda * dx
        y1 = sy + lambda * dy
        z1 = sz + lambda * dz
      endif
    endif
    sy = sy * 4/5
    dy = dy * 4/5
    float a = sqr(dx) + sqr(dy) + sqr(dz)
    float b = 2 * sx * dx + 2 * sy * dy + 2 * sz * dz
    float c = sqr(sx) + sqr(sy) + sqr(sz) - 0.25
    float d = sqr(b) - 4 * a * c
    if d < 0
      solid2 = true
    else
      if a > 0
        lambda = (-b - sqrt(d)) / (2 * a)
      else
        lambda = (-b + sqrt(d)) / (2 * a)
      endif
      if lambda <= 0
        solid2 = true
      else
        x2 = sx + lambda * dx
        y2 = sy + lambda * dy
        z2 = sz + lambda * dz
      endif
    endif
    if solid1
      if y2 < 0
        solid = true
      else
        px = x2
        py = y2
        pz = z2
        solid = solid2
      endif
    else
      if y2 > 0
        px = x2
        py = y2
        pz = z2
      else
        px = x1
        py = y1
        pz = z1
      endif
    endif
    #solid = solid
    if !solid
      if pz == 0
        if px > 0
          u = 2
        else
          u = -2
        endif
      else
        u = (4 / #pi) * atan(px / abs(pz))
      endif
      v = (4 / #pi) * atan(py / sqrt(sqr(px) + sqr(pz)))
    endif
  endif
  #pixel = u + flip(v)
  #pixel = @fraccenter + #pixel * exp(flip(@fracangle * #pi / 180)) / @fracmagn
default:
  title = "3D Mapping"
  param shape
    caption = "Shape"
    enum = "Plane" "Sphere" "Egg"
    default = 0
  endparam
  param rotx
    caption = "X Rotation"
    default = 0.0
  endparam
  param roty
    caption = "Y Rotation"
    default = 0.0
  endparam
  param rotz
    caption = "Z Rotation"
    default = 0.0
  endparam
  param transx
    caption = "X Translation"
    default = 0.0
  endparam
  param transy
    caption = "Y Translation"
    default = -0.5
  endparam
  param transz
    caption = "Z Translation"
    default = 2.0
  endparam
  complex param fraccenter
    caption = "Fractal Center"
    default = (0, 0)
  endparam
  param fracmagn
    caption = "Fractal Magnification"
    default = 1.0
  endparam
  param fracangle
    caption = "Fractal Rotation"
    default = 0.0
  endparam
}

GlassHemisphere {
; Refractive lens centered at @ctr, hemisphere of radius @width with
; refractive index @n. Pixels outside the hemisphere are passed through;
; pixels inside are bent radially toward the centre by the Snell-law
; offset that a sphere of glass produces in projection. Luke Plant's
; algorithm, shipped with UF's Standard.uxf.
transform:
  complex c = @ctr
  if @screencenter
    c = #center
  endif
  complex z2 = #pixel - c
  float x1 = cabs(z2) / @width
  if x1 > 1
    #pixel = #pixel
  else
    float theta1 = acos(x1)
    float y1 = sin(theta1)
    float beta = asin(x1 / @n)
    float x2 = -y1 / tan(theta1 + beta) + x1
    #pixel = z2 / x1 * x2 + c
  endif
default:
  title = "Glass Hemisphere"
  param n
    caption = "Refractive index"
    default = 2.0
  endparam
  param width
    caption = "Width"
    default = 1.0
  endparam
  complex param ctr
    caption = "Center"
    default = (0, 0)
  endparam
  param screencenter
    caption = "Use Screen Center"
    default = false
  endparam
}
