AKTIVE

Artifact [3563bc5249]
Login

Artifact [3563bc5249]

Artifact 3563bc5249b345e9890b64c048afe2439ffce88f761db3e415b01680c5509ad6:


## -*- mode: tcl ; fill-column: 90 -*-
# # ## ### ##### ######## ############# #####################
## Generators -- signed distance fields

#
# SDF manipulations .............................
#
# Example elements:
#
# 1. set box    [aktive image sdf box-rounded width 1000 height 1000 ewidth 400 eheight 200 center {500 500} downrightradius 100]
# 2. set circle [aktive image sdf circle width 1000 height 1000 radius 200 center {500 300}]
#
# Singular manipulations ........................
#
#  <> is the place holder for the operation converting the SDF into a regular image (not `fit`).
#
# (a) image = (1).<>                          -> filled box
# (b)       = (1).abs.<>                      -> outlined box
# (c)       = (1).ring(15).<>                 -> thick outlined box
# (d)       = (1).ring(15).abs.<>             -> double outlined box
# (e)       = (1).abs.ring(15).<>             -> See (c), abs has no effect
# (f)       = (1).ring(15).abs.ring(2).<>     -> double thick outlined box
# (g)       = (1).ring(15).abs.ring(2).abs.<> -> quadruple outlined box
#
# Combining SDFs ................................
#
#   Basic combiners:
#
#     min                   -> or, union
#     max                   -> and, intersection
#     neg                   -> not, inversion
#
#     and (a, not b)        -> sub/traction
#     sub (a or b, a and b) -> xor, symmetric difference
#
#   abs  or not before combining, per element
#   ring or not before combining, per element
#
#   abs         after combining -- See above, singular manipulations
#   ring        after combining -- See above, singular manipulations
#
#     (1) (2) combiner
# -------+---+----
# (a)         min -> union  filled   circle, filled   box
# (b) abs     min -> union  filled   circle, outlined box
# (c)     abs min -> union  outlined circle, filled   box
# (d) abs abs min -> union  outlined circle, outlined box
#
# (e)         max -> isect  filled   circle, filled   box
# (f) abs     max -> isect  filled   circle, outlined box (box outline in circle)
# (g)     abs max -> isect  outlined circle, filled   box (circle outline in box)
# (h) abs abs max -> isect  outlined circle, outlined box (points, circle/box crossings)
#
# (i)         add -> (*)
# (j) abs     add -> (*)
# (k)     abs add -> (*)
# (l) abs abs add -> See (h)
#
# (m)         sub -> (*)
# (n) abs     sub -> (*)
# (o)     abs sub -> (*)
# (p) abs abs sub -> (*)
#
# (*) Weird gradient? shapes
#
# Note that SDF make interesting images of their own when fit/compressed into the [0..1]
# range. Terrain pseudo 3D display through shading.
#
# --> operations for and, or, not, xor, outlining, rounding, ...
#

# # ## ### ##### ######## ############# #####################
## SDF primitives

operator [sdf-known image::sdf::] {
    section generator virtual sdf

    op -> _ mode sdf

    def element [sdf-label $sdf]

    note Returns an image with the given dimensions and location, \
	containing the signed distance field of a $element.

    ref https://iquilezles.org/articles/distfunctions2d

    import sdf/note.tcl

    note See also "<!xref: aktive op draw $sdf on>" \
	and "<!xref: aktive image draw ${sdf}>."

    sdf-common-params

    import sdf/parameter/$sdf.tcl
    import sdf/coding/$sdf.tcl
}

# # ## ### ##### ######## ############# #####################
## Supporting operators I -- boolean combiners/modifiers

operator op::sdf::not {
    section transform sdf

    # !xref-mark sdf
    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	@1 | ; sdf-fit ; sdf-smooth ; sdf-pixelated
    }
    # !xref-mark /end

    note Returns the inverted input SDF, where inside and outside changed places. \
	This is defined as `1 - SRC`.

    ref https://iquilezles.org/articles/distfunctions2d

    input

    body {
	aktive op math1 neg $src
    }
}

operator op::sdf::or {
    section transform sdf combiner

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	@1 @2 | sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Returns the union (`+`, `min`) of all input SDFs.

    ref https://iquilezles.org/articles/distfunctions2d

    input...

    body {
	aktive op math min {*}$args
    }
}

operator op::sdf::and {
    section transform sdf combiner

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	@1 @2 | sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Returns the intersection (`*`, `max`) of all input SDFs.

    ref https://iquilezles.org/articles/distfunctions2d

    input...

    body {
	aktive op math max {*}$args
    }
}

operator op::sdf::sub {
    section transform sdf combiner

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	@1 @2 | sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Returns the difference `A - B` of the two input SDFs. \
	This is defined as `A * (not B)`.

    ref https://iquilezles.org/articles/distfunctions2d

    input a	SDF A
    input b	SDF B

    body {
	# The difference `A-B is the intersection of `A` and `not B`
	and $a [not $b]
    }
}

operator op::sdf::xor {
    section transform sdf combiner

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	@1 @2 | sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Returns the symmetric difference of all input SDFs.

    note A single input is passed through unchanged.

    ref https://iquilezles.org/articles/distfunctions2d

    input...

    body {
	aktive::aggregate-or-pass {
	    aktive op sdf xor-core
	} $args
    }
}

operator op::sdf::xor-core {
    section transform sdf combiner

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	@1 @2 | sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Returns the symmetric difference of the two input SDFs. \
	This is defined as `(A + B) - (A * B)`.

    ref https://iquilezles.org/articles/distfunctions2d

    input a	SDF A
    input b	SDF B

    body {
	# The symmetric difference is the union minus the intersection
	sub [or $a $b] [and $a $b]
    }
}

# # ## ### ##### ######## ############# #####################
## Supporting operators II -- appearance modulation

operator op::sdf::ring {
    section transform sdf

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	@1 thickness 4 | ; sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Combines outlining and rounding to replace the input SDF with \
	an SDF tracing the border at some thickness and returns the result.

    note The result is annular, i.e. has a ring/onion-like structure.

    note Note that a thickness of zero devolves this operation to a plain \
	outline.

    ref https://iquilezles.org/articles/distfunctions2d

    input

    double thickness Desired border thickness.

    body {
	round [outline $src] radius $thickness
    }
}

operator op::sdf::outline {
    section transform sdf

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	@1 | ; sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Replaces the input SDF with an outlined form, and returns the result.

    note This is implemented by taking the absolute of the input.

    ref https://iquilezles.org/articles/distfunctions2d

    input

    body {
	aktive op math1 abs $src
    }
}

operator op::sdf::round {
    section transform sdf

    example {
	aktive image sdf box center {64 64} width 128 height 128 ewidth 40 eheight 40
	@1 radius 20 | ; sdf-fit ; sdf-smooth ; sdf-pixelated
    }

    note Replaces the input SDF with a more rounded form per the radius, \
	and returns the result.

    note This is implemented by shifting the input SDF down by the radius.

    note For a radius > 0 this expands the SDF, making the encoded element \
	rounder. A radius < 0 conversely shrinks the SDF.

    note To get a rounded SDF at the original size use a pre-shrunken/expanded \
	SDF as the input to compensate the changes made by this operator.

    note A radius of zero is ignored.

    ref https://iquilezles.org/articles/distfunctions2d

    input
    double radius Expansion/Shrinkage radius for the SDF.

    body {
	if {$radius == 0} { return $src }
	aktive op math1 shift $src offset [expr {- $radius}]
    }
}

# # ## ### ##### ######## ############# #####################
## Supporting operators III -- Conversions into image

operator op::sdf::2image::fit {
    section transform sdf

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	aktive op sdf and @1 @2 | sdf-fit
    }

    note Compresses the input SDF into the range 0..1 and returns the resulting grayscale image.

    ref https://iquilezles.org/articles/distfunctions2d

    input

    body {
	aktive op math1 fit min-max $src
    }
}

operator op::sdf::2image::smooth {
    section transform sdf

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	aktive op sdf and @1 @2 | sdf-smooth
    }

    note Converts the SDF into a grey-scale image with anti-aliased element borders.

    ref https://iquilezles.org/articles/distfunctions2d

    input

    body {
	set src [aktive op math1 clamp $src]
	set src [aktive op math1 invert $src]
    }
}

operator op::sdf::2image::pixelated {
    section transform sdf

    example {
	aktive image sdf circle center {80 80} width 128 height 128 radius 40
	aktive image sdf box    center {48 48} width 128 height 128 ewidth 40 eheight 40
	aktive op sdf and @1 @2 | sdf-pixelated
    }

    note Converts the SDF into a black/white image with pixelated element borders.

    ref https://iquilezles.org/articles/distfunctions2d

    input

    body {
	aktive op math1 lt $src threshold 0.5
    }
}

##
# # ## ### ##### ######## ############# #####################
::return