comment { dmj5.ulb 1.0
Base Classes for Ultra Fractal 5
by Damien M. Jones
July 5, 2008

This is my public collection. As much as possible, I will try to keep these backwards-compatible, so that all of your parameters will always render correctly in the future.

This does not apply to anything I've marked as "not recommended". Such classes should be considered "beta" code. While I will try to preserve images created with them, I may choose not to if it will hamper further development. If you are using such a class, you might want to let me know so I can warn you if I do that.

Editing note: tabs are set to 4 for my code. } ; ----------------------------------------------------------------------------------------------------------------------------- ; Stand-alone classes class DMJ_PolyCurve { ; PolyCurve tool class. ; ; PolyCurve is a helper class that you can use to define, ; manage, and render a shape composed of connected lines ; and curves. Each segment may be one of these: ; ; - a straight line ; - a quadratic Bézier curve; these are used to create ; TrueType font shapes, because they are mathematically ; easy to render at any resolution ; - a cubic Bézier curve; these are used in high-end ; vector illustration programs (e.g. Adobe Illustrator) ; and in PostScript fonts, as they are more flexible ; than quadratic curves, but they are harder to render ; ; Cubic Bézier curves are so difficult to render that this ; class actually does not render them directly; instead, ; it converts cubic curves to a set of very small straight ; line segments, and renders from that. Usually this is ; not noticeable, but you should be aware of this if you ; use this class in unusual contexts. ; ; This class is not derived from anything as it does not ; have any user-exposed parameters. Your code must provide ; all the parameters you require. You must call Rasterize() ; before making any queries about the curve, or you will ; get erroneous results. Changing curve data after it has ; been rasterized will not affect any queries. ; ; This code is adapted from DeluxeClipping, which I wrote ; for Janet Parke's course on Masking with Ultra Fractal. ; The cubic Bézier support is new for UF5. The additional ; query modes are also new. public: import "common.ulb" ; $define debug func DMJ_PolyCurve() m_WindingMode = 0 m_Roots = new ComplexArray(3) endfunc ; set the winding mode ; pmode = winding mode: ; 0 = fully self-intersecting (default) (like "invert overlaps" in DeluxeClipping) ; 1 = individual loops not self-intersecting, subsequent loops may intersect ; 2 = entire curve not self-intersecting (like "all inside" in DeluxeClipping) func SetWindingMode(int pmode) m_WindingMode = pmode endfunc ; set the endpoint extension mode ; pmode = extension mode: ; 0 = none (cap endpoints) ; 1 = extend lines ; 2 = extend curves (not implemented) func SetExtensionMode(int pmode) m_EndpointExtensionMode = pmode endfunc ; set the number of curve segments ; you must call this prior to setting any curve data func SetSegmentCount(int ppoints) setLength(m_Anchors, ppoints) setLength(m_Control1, ppoints) setLength(m_Control2, ppoints) setLength(m_Lengths, ppoints) setLength(m_Types, ppoints) setLength(m_Auto, ppoints) m_AnchorNext = 0 m_AnchorHighest = -1 endfunc ; set parameters for a segment ; note: do not manually specify a close point; use the ; auto-close flag instead. Otherwise, your shape may not ; render correctly (it will "leak"). ; ppoint = slot number (use -1 for next slot) ; ptype = segment type: 0 = none (close), 1 = linear, 2 = quadratic (use pcontrol1), 3 = cubic (use pcontrol1 and 2) ; pauto = auto-compute controls: 1 = close, 2 = smooth, 4 = curvature smooth func SetSegment(int ppoint, int ptype, int pauto, complex panchor, complex pcontrol1, complex pcontrol2) if (ppoint == -1) ppoint = m_AnchorNext m_AnchorNext = m_AnchorNext + 1 endif if (ppoint > m_AnchorHighest) m_AnchorHighest = ppoint endif print("segment ", ppoint, ": type ", ptype, " auto ", pauto, " ", panchor, " ", pcontrol1, " ", pcontrol2) m_Anchors[ppoint] = panchor m_Control1[ppoint] = pcontrol1 m_Control2[ppoint] = pcontrol2 m_Types[ppoint] = ptype m_Auto[ppoint] = pauto endfunc ; rasterize a set of curve segments that may contain ; cubic segments into lines and quadratic segments; ; also performs auto-compute at this time ; pflags = extra computation flags: 1 = compute segment lengths (needed for ClosestPoint, DistanceAlongCurve), 2 = compute normals (needed for ClosestCurve) func Rasterize(int pflags) ; count number of straight/quadratic segments we will need ; and perform auto computations int j = 0 int k = 0 int l = 0 int m = 0 int n = 0 int segmentcount = 0 int quadraticcount = 0 int loopstart = 0 int loopnext int loopprevious int looprealstart bool computenormals = (pflags % 4 >= 2) bool computelengths = (pflags % 2 == 1) float s float t float totallength = 0 complex point complex point2 print("Rasterize() trace") while (j <= m_AnchorHighest) print("segment ", j) ; determine next loop segment (auto-close handled here) loopnext = j + 1 if (m_Auto[j] % 2 == 1) loopnext = loopstart segmentcount = segmentcount + 1 print("auto-close to segment ", loopnext) endif ; count the segment if (m_Types[j] < 2) segmentcount = segmentcount + 1 elseif (m_Types[j] == 2) segmentcount = segmentcount + 1 quadraticcount = quadraticcount + 1 if (m_Auto[j] % 4 >= 2) loopprevious = j - 1 if (loopprevious < loopstart) ; auto-smooth at the start of the curve; find the end k = j while (k < length(m_Anchors) && loopprevious < j) if (m_Auto[k] % 2 == 1 || m_Types[k] == 0) loopprevious = k endif k = k + 1 endwhile if (m_Types[loopprevious] < 2) m_Anchors[j] = m_Anchors[loopprevious] else m_Anchors[j] = (m_Control1[j] + m_Control1[loopprevious]) / 2 endif else m_Anchors[j] = (m_Control1[j] + m_Control1[loopprevious]) / 2 endif print("smoothed to ", loopprevious, " ", m_Anchors[j]) endif else segmentcount = segmentcount + 1024 ; **** this number should be dynamic ; **** perform auto here endif if (m_Auto[j] % 2 == 1 || m_Types[j] == 0) loopstart = j + 1 endif j = j + 1 endwhile print("total expanded segments: ", segmentcount) ; create updated arrays and copy over segments setLength(m_RealIsStartPoint, segmentcount) setLength(m_RealIsEndPoint, segmentcount) setLength(m_RealAnchors, segmentcount) setLength(m_RealControls, segmentcount) setLength(m_RealAnchorNormals, segmentcount) setLength(m_RealControlNormals, segmentcount) setLength(m_RealExitNormals, segmentcount) setLength(m_RealNormals, segmentcount) setLength(m_RealLengths, segmentcount) setLength(m_RealSummedLengths, segmentcount) setLength(m_RealTypes, segmentcount) setLength(m_RealA, segmentcount) setLength(m_RealB, segmentcount) if (computelengths) setLength(m_RealSubLengths, quadraticcount*256) setLength(m_RealIndex, segmentcount) endif j = 0 k = 0 l = 0 loopstart = 0 loopnext = 0 looprealstart = 0 while (j <= m_AnchorHighest) ; set up endpoint if (looprealstart == k) m_RealIsStartPoint[k] = true else m_RealIsStartPoint[k] = false endif m_RealIsEndPoint[k] = false ; determine next loop segment (auto-close handled here) loopnext = j + 1 if (m_Auto[j] % 2 == 1 || m_Types[j] == 0) loopnext = loopstart loopstart = j + 1 endif ; copy over segment if (m_Types[j] == 0) ; explicit point (normally these are automatic) print("close point ", m_Anchors[j]) m_RealIsEndPoint[k] = true ; this marks an endpoint m_RealTypes[k] = 0 m_RealAnchors[k] = m_Anchors[j] m_RealSummedLengths[k] = totallength m_RealLengths[k] = 0 k = k + 1 looprealstart = k ; next segment will be a starting point else if (m_Types[j] == 1) print("line segment ", m_Anchors[j], " ", m_Anchors[loopnext]) m_RealTypes[k] = 1 m_RealAnchors[k] = m_Anchors[j] ; precompute length squared used to determine distance m_RealLengths[k] = |m_Anchors[loopnext] - m_Anchors[j]| m_RealSummedLengths[k] = totallength totallength = totallength + m_RealLengths[k] ; print("length: ", m_RealLengths[k]) ; print("summed: ", m_RealSummedLengths[k]) ; print("total: ", totallength) k = k + 1 elseif (m_Types[j] == 2) print("quadratic segment ", m_Anchors[j], " ", m_Control1[j], " ", m_Anchors[loopnext]) m_RealTypes[k] = 2 m_RealAnchors[k] = m_Anchors[j] m_RealControls[k] = m_Control1[j] ; precompute values used to determine distance m_RealA[k] = m_Anchors[j] - 2*m_Control1[j] + m_Anchors[loopnext] m_RealB[k] = -2*m_Anchors[j]+2*m_Control1[j] t = |m_RealA[k]| if (t < 1e-25) ; curve segment is too straight; call it a line! faster and avoids divide-by-zero in ClosestPoint() print("straight quadratic (m = ", t, "), replacing with line") m_RealTypes[k] = 1 m_RealLengths[k] = |m_Anchors[loopnext] - m_Anchors[j]| m_RealSummedLengths[k] = totallength totallength = totallength + m_RealLengths[k] else if (computelengths) m_RealIndex[k] = l*256 m = m_RealIndex[k] n = m + 256 s = 0 t = 0 point = m_Anchors[j] ; $ifdef debug ; print("filling slot ", l, " from ", m, " to ", n) ; $endif while (m < n) t = t + 0.00390625 ; 1/256 point2 = InterpolateQuadratic(t, m_Anchors[j], m_Control1[j], m_Anchors[loopnext]) m_RealSubLengths[m] = cabs( point2-point ) point = point2 s = s + m_RealSubLengths[m] ; $ifdef debug ; if (m % 16 == 0) ; print("sublength: ", m_RealSubLengths[m]) ; print("segment total: ", s) ; endif ; $endif m = m + 1 endwhile l = l + 1 m_RealLengths[k] = s ; total accumulated length m_RealSummedLengths[k] = totallength totallength = totallength + m_RealLengths[k] ; print("length: ", m_RealLengths[k]) ; print("summed: ", m_RealSummedLengths[k]) ; print("total: ", totallength) endif endif k = k + 1 elseif (m_Types[j] == 3) print("cubic segment ", m_Anchors[j], " ", m_Control1[j], " ", m_Control2[j], " ", m_Anchors[loopnext]) ; **** rasterize cubic curve here endif if (loopstart > j) ; this segment closed a loop m_RealIsStartPoint[looprealstart] = false ; make sure start/end points are cleared of endpoint status m_RealIsEndPoint[k] = false m_RealTypes[k] = 0 m_RealAnchors[k] = m_Anchors[loopnext] k = k + 1 looprealstart = k ; next segment will be a starting point endif endif j = j + 1 endwhile m_RealAnchorCount = k if (computenormals) ComputeNormals() endif endfunc ; Compute normals for a curve. ; For each segment, we compute the normal at the beginning, the ; control point, and the end. We then interpolate a "point normal" ; for each endpoint. Since computing normals is expensive, we don't ; automatically compute normals for every curve. func ComputeNormals() int segmentcount = m_RealAnchorCount int j = 0 complex exitnormal = 0 while (j < segmentcount) if (m_RealTypes[j] == 0) m_RealAnchorNormals[j] = exitnormal m_RealControlNormals[j] = exitnormal m_RealExitNormals[j] = exitnormal m_RealNormals[j] = exitnormal elseif (m_RealTypes[j] == 1) m_RealAnchorNormals[j] = m_RealAnchors[j+1] - m_RealAnchors[j] m_RealAnchorNormals[j] = m_RealAnchorNormals[j] / cabs(m_RealAnchorNormals[j]) exitnormal = m_RealAnchorNormals[j] m_RealControlNormals[j] = exitnormal m_RealExitNormals[j] = exitnormal elseif (m_RealTypes[j] == 2) m_RealAnchorNormals[j] = m_RealControls[j] - m_RealAnchors[j] m_RealAnchorNormals[j] = m_RealAnchorNormals[j] / cabs(m_RealAnchorNormals[j]) m_RealControlNormals[j] = m_RealAnchors[j+1] - m_RealAnchors[j] m_RealControlNormals[j] = m_RealControlNormals[j] / cabs(m_RealControlNormals[j]) m_RealExitNormals[j] = m_RealAnchors[j+1] - m_RealControls[j] m_RealExitNormals[j] = m_RealExitNormals[j] / cabs(m_RealExitNormals[j]) exitnormal = m_RealExitNormals[j] endif if (m_RealTypes[j] > 0) if (j > 0 && m_RealTypes[j-1] > 0) ; not the first point, and previous segment has exit normal (not a point) m_RealNormals[j] = m_RealAnchorNormals[j] + m_RealExitNormals[j-1] m_RealNormals[j] = m_RealNormals[j] / cabs(m_RealNormals[j]) else ; first point in a sequence; use starting normal for this segment m_RealNormals[j] = m_RealAnchorNormals[j] endif endif j = j + 1 endwhile endfunc ; query: is a point to the left of a line segment? ; positive value = no, negative value = yes, 0 = on line static float func IsLeftLine(complex pz, complex panchor1, complex panchor2) return -( (real(panchor2) - real(panchor1)) * (imag(pz) - imag(panchor1)) - \ (real(pz) - real(panchor1)) * (imag(panchor2) - imag(panchor1)) ) endfunc ; query: is a point to the left of a parabolic segment? ; note that if the point is outside the triangle enclosing ; the segment, we treat this as the same as if the point ; is to the left of the lines bounding the parabolic arc ; (we extend the arc by straight lines) static float func IsLeftQuadratic(complex pz, complex panchor1, complex pcontrol, complex panchor2) ; see if it's within the triangle float isleft1 = IsLeftLine(pz, panchor1, panchor2) float isleft2 = IsLeftLine(pz, panchor2, pcontrol) float isleft3 = IsLeftLine(pz, pcontrol, panchor1) if ((isleft1 <= 0 && isleft2 < 0 && isleft3 < 0) || (isleft1 >= 0 && isleft2 > 0 && isleft3 > 0)) ; point is inside the triangle; determine if/where the ray ; intersects the parabola. return IsLeftQuadraticCore(pz, panchor1, pcontrol, panchor2) else float isleft5 = IsLeftLine(pcontrol, panchor1, panchor2) if (isleft5 > 0) if (isleft2 > 0 && isleft3 > 0) return -1.0 else return 1.0 endif elseif (isleft5 < 0) if (isleft2 < 0 && isleft3 < 0) return 1.0 else return -1.0 endif else return isleft1 endif endif endfunc ; query: is a point to the left of a parabolic segment? ; this does not care whether the point is inside the enclosing triangle static float func IsLeftQuadraticCore(complex pz, complex panchor1, complex pcontrol, complex panchor2) float isleft4 = 0 ; parabola flag complex c ; work variables for quadratic segments complex q complex s complex t float d float tx float ty float isy q = 2*conj(panchor2-panchor1) / |panchor2-panchor1| ; rotation and scaling vector c = (panchor1 + panchor2) * 0.5 t = (pz - c) * q ; transform pixel so line segment is rotated to X-axis and centered at origin s = (pcontrol - c) * q ; transform control point the same way isy = 1/imag(s) ; precalc tx = real(t) - imag(t)*real(s)*isy ; shear ty = imag(t)*isy ; squash d = 0.5-0.5*sqr(tx) ; height of parabola at this point isleft4 = ty - d ; parabola isleft flag if (imag(s) < 0) isleft4 = -isleft4 endif return -isleft4 endfunc ; query: is a point to the left of a particular curve segment? ; this determines the type of segment automatically float func IsLeft(complex pz, int psegment) while (psegment >= 0) if (m_RealTypes[psegment] == 0) ; point; if we have a previous segment, use it psegment = psegment - 1 elseif (m_RealTypes[psegment] == 1) return IsLeftLine(pz, m_RealAnchors[psegment], m_RealAnchors[psegment+1]) elseif (m_RealTypes[psegment] == 2) return IsLeftQuadratic(pz, m_RealAnchors[psegment], m_RealControls[psegment], m_RealAnchors[psegment+1]) endif endwhile ; no valid segment, just points; answer "no" (not a valid answer) return 0 endfunc ; given a complex number pz, return whether it is inside ; the curve or not; this is the fastest query bool func IsInside(complex pz) int segmentcount = m_RealAnchorCount int j = 0 ; indices for current/next int k = 1 int fullwinding = 0 ; winding numbers int segmentwinding = 0 float isleft1 = 0 ; triangle flags float isleft2 = 0 float isleft3 = 0 float isleft4 = 0 ; parabola flag $ifdef debug int testx = 500 int testy = 480 if (#x == testx && #y == testy) print("IsInside() trace") print("total anchor count: ", m_RealAnchorCount) print("test point: ", testx, " ", testy, " = ", pz) endif $endif while (j < segmentcount) if (m_RealTypes[j] == 0) ; close point; deal with winding numbers if (m_WindingMode == 0) fullwinding = fullwinding + segmentwinding elseif (m_WindingMode == 1) if (segmentwinding != 0) fullwinding = fullwinding + 1 endif else if (segmentwinding != 0) fullwinding = 1 endif endif $ifdef debug if (#x == testx && #y == testy) print("segment ", j, " segmentwinding ", segmentwinding, " fullwinding ", fullwinding) endif $endif segmentwinding = 0 elseif (m_RealTypes[j] == 1) ; line segment; update winding number based solely on line segment ; The winding number method counts all line segments ; that cross the vertical position of the pixel to ; the RIGHT of it. Segments that cross up increment ; the count, segments that cross down decrement it. ; With a closed shape, this will equal zero if the ; point is not inside (regardless of the clockwise/ ; counter-clockwise direction of points). Very ; clever algorithm. Google it. if (imag(m_RealAnchors[j]) <= imag(pz)) ; line segment imag <= point imag if (imag(m_RealAnchors[k]) > imag(pz)) ; upward crossing $ifdef debug if (#x == testx && #y == testy) print("segment ", j, " upward crossing") endif $endif if ( (real(m_RealAnchors[k]) - real(m_RealAnchors[j])) * (imag(pz) - imag(m_RealAnchors[j])) - \ (real(pz) - real(m_RealAnchors[j])) * (imag(m_RealAnchors[k]) - imag(m_RealAnchors[j])) > 0 ) segmentwinding = segmentwinding + 1 $ifdef debug if (#x == testx && #y == testy) print("winding + 1") endif $endif endif endif else if (imag(m_RealAnchors[k]) <= imag(pz)) ; downward crossing $ifdef debug if (#x == testx && #y == testy) print("segment ", j, " downward crossing") endif $endif if ( (real(m_RealAnchors[k]) - real(m_RealAnchors[j])) * (imag(pz) - imag(m_RealAnchors[j])) - \ (real(pz) - real(m_RealAnchors[j])) * (imag(m_RealAnchors[k]) - imag(m_RealAnchors[j])) < 0 ) segmentwinding = segmentwinding - 1 $ifdef debug if (#x == testx && #y == testy) print("winding - 1") endif $endif endif endif endif elseif (m_RealTypes[j] == 2) ; quadratic segment; update winding number based on a parabola ; Just so you know, I worked this out myself. No doubt there's ; source code I could have ripped into a formula, but I wanted ; the satisfaction of knowing I could do it. I am quite sure ; this is not the most optimal method, but it does work. Pay ; particular attention to the use of < vs. <= as if you make a ; mistake, you will have horizontal line segment errors in the ; drawn shape. ; This is essentially the same algorithm as arbitrary polygon, ; but extended so that each segment can be a quadratic Bézier ; curve rather than just a straight line. Each curve is ; described by a triangle, and a parabola inscribed within the ; triangle such that two sides of the triangle are tangents ; to the parabola. Points that do not lie within the triangle ; are treated the same as the arbitrary polygon (only the chord ; cutting across the parabola, the third side of the triangle, ; matters) but for points inside the triangle a determination ; must be made as to whether the point is to the left of the ; parabola or not. There are a few edge cases where the ; parabola loops back on itself and the winding number can be ; changed multiple times for each segment. ; three cases: pixel is below triangle containing curve, above it, or between top and bottom of it ; if either of the first two, don't examine this curve further (it is irrelevant to the winding number) if ((imag(pz) >= imag(m_RealAnchors[j]) && \ (imag(pz) < imag(m_RealAnchors[k]) || imag(pz) < imag(m_RealControls[j]))) || \ (imag(pz) < imag(m_RealAnchors[j]) && \ (imag(pz) >= imag(m_RealAnchors[k]) || imag(pz) >= imag(m_RealControls[j])))) $ifdef debug if (#x == testx && #y == testy) print(j, " considered for curve segment") endif $endif ; pixel is between top and bottom of curve ; see if it's within the triangle isleft1 = (real(m_RealAnchors[k]) - real(m_RealAnchors[j])) * (imag(pz) - imag(m_RealAnchors[j])) - \ (real(pz) - real(m_RealAnchors[j])) * (imag(m_RealAnchors[k]) - imag(m_RealAnchors[j])) isleft2 = (real(m_RealControls[j]) - real(m_RealAnchors[k])) * (imag(pz) - imag(m_RealAnchors[k])) - \ (real(pz) - real(m_RealAnchors[k])) * (imag(m_RealControls[j]) - imag(m_RealAnchors[k])) isleft3 = (real(m_RealAnchors[j]) - real(m_RealControls[j])) * (imag(pz) - imag(m_RealControls[j])) - \ (real(pz) - real(m_RealControls[j])) * (imag(m_RealAnchors[j]) - imag(m_RealControls[j])) if ((isleft1 <= 0 && isleft2 < 0 && isleft3 < 0) || (isleft1 >= 0 && isleft2 > 0 && isleft3 > 0)) ; point is inside the triangle; determine if/where the ray ; intersects the parabola. start by normalizing the parabola ; and the ray $ifdef debug if (#x == testx && #y == testy) print(j, " considered inside triangle") endif $endif isleft4 = -IsLeftQuadraticCore(pz, m_RealAnchors[j], m_RealControls[j], m_RealAnchors[k]) if (imag(m_RealAnchors[k]) > imag(m_RealAnchors[j])) ; upward crossing if (imag(pz) >= imag(m_RealAnchors[j]) && \ imag(pz) < imag(m_RealAnchors[k])) ; within line segment if (isleft4 > 0) segmentwinding = segmentwinding + 1 ; confirmed upward crossing endif else ; outside line segment if (isleft1 < 0) ; loop back occurs to the left if (isleft4 > 0) segmentwinding = segmentwinding + 1 endif else ; loop back occurs to the right if (isleft4 < 0) segmentwinding = segmentwinding - 1 ; loop back; downward crossing endif endif endif else ; downward crossing if (imag(pz) < imag(m_RealAnchors[j]) && \ imag(pz) >= imag(m_RealAnchors[k])) ; within line segment if (isleft4 < 0) segmentwinding = segmentwinding - 1 ; confirmed downward crossing endif else ; outside line segment if (isleft1 > 0) ; loop back occurs to the left if (isleft4 < 0) segmentwinding = segmentwinding - 1 endif else ; loop back occurs to the right if (isleft4 > 0) segmentwinding = segmentwinding + 1 ; loop back; upward crossing endif endif endif endif ; upward/downward else $ifdef debug if (#x == testx && #y == testy) print(j, " tested against line segment only") endif $endif ; point is outside the triangle; all that matters is its relation to the line segment if (imag(m_RealAnchors[j]) <= imag(pz)) ; line segment imag < point imag if (imag(m_RealAnchors[k]) > imag(pz)) ; upward crossing if ( isleft1 > 0 ) segmentwinding = segmentwinding + 1 endif endif else if (imag(m_RealAnchors[k]) <= imag(pz)) ; downward crossing if ( isleft1 < 0 ) segmentwinding = segmentwinding - 1 endif endif endif endif endif endif j = j + 1 k = k + 1 endwhile $ifdef debug if (#x == testx && #y == testy) print("final winding ", fullwinding) endif $endif ; set solid flag with results return (abs(fullwinding) % 2 != 0) endfunc ; given a complex number pz, locate the closest ; point on the curve, the segment number it is on, ; and the distance squared to that point; note ; that distance can never be negative (use IsLeft() ; to determine sign if you need it) complex func ClosestPoint(complex pz, float &psegment, float &psegmentoffset, float &pdistancesquared) int segmentcount = m_RealAnchorCount int j = 0 ; indices for current/next int k = 1 float t float t2 complex c ; third term of quadratic curve function (x and y parts) float m ; coefficients of distance-squared function (a cubic polynomial) float n float r float s int rootcount ; count of roots to cubic equation complex point complex pointmin = m_RealAnchors[0] ; closest point on curve; start with first point float ds = 0 float dsmin = |pz-m_RealAnchors[0]| ; closest distance squared; start with simple distance to first point float segment float segmentoffset float segmentmin = 0 float segmentminoffset = 0 complex vl complex vp float vls float vps $ifdef debug int testx = 700 int testy = 400 if (#x == testx && #y == testy) print("ClosestPoint() trace") print("total anchor count: ", m_RealAnchorCount) print("test point: ", testx, " ", testy, " = ", pz) print("starting dsmin: ", dsmin) endif $endif while (j < segmentcount) if (m_RealTypes[j] == 1) ; compute distance from line segment to point ; start by finding closest point along the line vl = m_RealAnchors[k] - m_RealAnchors[j] ; vector for line vp = pz - m_RealAnchors[j] ; vector from line start to point t = real(vl)*real(vp) + imag(vl)*imag(vp) ; dot product, |vl| |vp| cos theta t2 = t / m_RealLengths[j] ; normalize = cos theta * |vp|/|vl| $ifdef debug if (#x == testx && #y == testy) print("segment ", j, " type 1 (line) ", m_RealAnchors[j], " ", m_RealAnchors[k]) print("start: ", m_RealIsStartPoint[j], ", end: ", m_RealIsEndPoint[k]) print("length: ", m_RealLengths[j]) print("line vector: ", vl) print("point vector: ", vp) print("dot product: ", t) print("normalized: ", t2) endif $endif ; cap position along line if we're not extending it in either direction if (t2 <= 0 && (m_EndpointExtensionMode == 0 || !m_RealIsStartPoint[j])) point = m_RealAnchors[j] ds = |pz-point| segment = j elseif (t2 >= 1 && (m_EndpointExtensionMode == 0 || !m_RealIsEndPoint[k])) point = m_RealAnchors[k] ds = |pz-point| segment = k else point = m_RealAnchors[j] + vl*t2 ds = |pz-point| if (t2 < 0 || t2 > 1) ; this result is not really "on" the curve; log it as a segment + offset segment = j segmentoffset = t2 else segment = j+t2 segmentoffset = 0 endif endif $ifdef debug if (#x == testx && #y == testy) print("ds: ", ds) print("point: ", point) endif $endif ; update closest point, if this one is closer if (ds < dsmin) dsmin = ds pointmin = point segmentmin = segment segmentminoffset = segmentoffset $ifdef debug if (#x == testx && #y == testy) print("updated: dsmin = ", dsmin, ", pointmin = ", pointmin, ", segmentmin = ", segmentmin, ", segmentminoffset = ", segmentminoffset) endif $endif endif elseif (m_RealTypes[j] == 2) ; compute distance from parametric parabola to point ; we define D(t) to be the distance from P(t) to pz ; and look for minimum values of D(t) by taking its ; derivative D'(t) and solving that; this gives us ; local minima and maxima, so we simply compute the ; distances at these points and choose the lowest ; ; this was a pain for me to work out, mainly because ; of a stupid mistake in my equation scribbling that ; I kept making over and over again... c = m_RealAnchors[j] - pz ; remaining coefficients (depend on point we're finding distance to) m = |m_RealA[j]| ; get coefficients to cubic equation n = real(m_RealA[j])*real(m_RealB[j]) + imag(m_RealA[j])*imag(m_RealB[j]) r = 2 * ( real(m_RealA[j])*real(c) + imag(m_RealA[j])*imag(c) ) + |m_RealB[j]| s = real(m_RealB[j])*real(c) + imag(m_RealB[j])*imag(c) ; D'(t) = 4mt^3 + 6nt^2 + 2rt + 2s ; note that only r and s vary with c rootcount = Math.SolveRealCubicPolynomial(2*m, 3*n, r, s, 3, m_Roots) $ifdef debug if (#x == testx && #y == testy) print("segment ", j, " type 2 (quadratic) ", m_RealAnchors[j], " ", m_RealControls[j], " ", m_RealAnchors[k]) print("start: ", m_RealIsStartPoint[j], ", end: ", m_RealIsEndPoint[k]) print("a: ", m_RealA[j]) print("b: ", m_RealB[j]) print("c: ", c) print("m: ", m) print("n: ", n) print("r: ", r) print("s: ", s) print("rootcount: ", rootcount) print("root 0: ", m_Roots.m_Elements[0]) print("root 1: ", m_Roots.m_Elements[1]) print("root 2: ", m_Roots.m_Elements[2]) endif $endif while (rootcount > 0) rootcount = rootcount - 1 ; examine this root t = real(m_Roots.m_Elements[rootcount]) ; determine point at t ; we may need to compute the curve at the t value if it's within range ; also, if we are extending beyond endpoints, we may need to recompute ; t as well if (t <= 0) if (m_EndpointExtensionMode == 0 || !m_RealIsStartPoint[j]) point = m_RealAnchors[j] t = 0 else vl = m_RealControls[j] - m_RealAnchors[j] ; vector for line vp = pz - m_RealAnchors[j] ; vector from line start to point t2 = real(vl)*real(vp) + imag(vl)*imag(vp) ; dot product, |vl| |vp| cos theta vls = |vl| vps = cabs(vp) t = t2 / vls ; normalize = cos theta point = m_RealAnchors[j] + vl*t t = t * sqrt(vls) $ifdef debug if (#x == testx && #y == testy) print("extended start line") print("line vector: ", vl) print("point vector: ", vp) print("line vector length sqd: ", vls) print("point vector length sqd: ", vps) print("dot product: ", t2) print("normalized: ", t) print("point: ", point) endif $endif endif elseif (t >= 1) if (m_EndpointExtensionMode == 0 || !m_RealIsEndPoint[k]) point = m_RealAnchors[k] t = 1 else vl = m_RealAnchors[k] - m_RealControls[j] ; vector for line vp = pz - m_RealControls[j] ; vector from line start to point t2 = real(vl)*real(vp) + imag(vl)*imag(vp) ; dot product, |vl| |vp| cos theta vls = |vl| vps = cabs(vp) t = t2 / vls ; normalize = cos theta point = m_RealControls[j] + vl*t t2 = (t-1) * sqrt(vls) $ifdef debug if (#x == testx && #y == testy) print("extended end line") print("line vector: ", vl) print("point vector: ", vp) print("line vector length sqd: ", vls) print("point vector length sqd: ", vps) print("dot product: ", t2) print("normalized: ", t) print("point: ", point) endif $endif endif else point = InterpolateQuadratic(t, m_RealAnchors[j], m_RealControls[j], m_RealAnchors[k]) endif ; update closest point ds = |pz-point| $ifdef debug if (#x == testx && #y == testy) print("ds: ", ds) endif $endif if (ds < dsmin) dsmin = ds pointmin = point if (t < 0) segmentmin = j segmentminoffset = t elseif (t > 1) segmentmin = j+1 segmentminoffset = t2 else segmentmin = j+t segmentminoffset = 0 endif endif endwhile endif ; segment type j = j + 1 k = k + 1 endwhile $ifdef debug if (#x == testx && #y == testy) print("result t: ", segmentmin) print("result to: ", segmentminoffset) print("result B(t): ", pointmin) print("result ds: ", dsmin) endif $endif psegment = segmentmin psegmentoffset = segmentminoffset pdistancesquared = dsmin return pointmin endfunc ; Given a complex number pz, determine the closest ; similar curve to it. That is, assume the endpoints ; and control point can slide along their respective ; normals in unison; find the appropriate scaling ; factor that will allow them to create a curve that ; passes through our test point. This is useful for ; creating smooth mappings around curves without the ; discontinuities on the concave side that using ; ClosestPoint() would. complex func ClosestCurve(complex pz, float &psegment, float &pdistancesquared) return 0 endfunc ; given a fractional segment number, determine the ; distance from the curve start to that point ; if ptotal is set, the distance will be cumulative ; from all closed loops, not just from the start of ; the closest closed loop float func DistanceAlongCurve(float psegment, bool ptotal) int segment = floor(psegment) float s = 0 float t = psegment - segment int m int n ; to start with, use all the summed lengths of previous segments s = m_RealSummedLengths[segment] ; now determine a partial length within this segment if (t > 0) if (m_RealTypes[segment] == 1) ; linear segment s = s + t*m_RealLengths[segment] elseif (m_RealTypes[segment] == 2) ; quadratic segment ; determine number of sub-segments to use t = t * 256 m = m_RealIndex[segment] n = m + floor(t) t = t - floor(t) while (m < n) s = s + m_RealSubLengths[m] m = m + 1 endwhile if (t > 0) s = s + t*m_RealSubLengths[m] endif endif endif ; if we're not using the total distance, find the loop start ; and subtract all the distance accumulated before it return s endfunc ; given an interpolant and two endpoints, compute a linear curve static complex func InterpolateLinear(float pt, complex panchor1, complex panchor2) return panchor1 + pt * (panchor2-panchor1) endfunc ; given an interpolant, two endpoints, and a control point, compute a parabolic curve static complex func InterpolateQuadratic(float pt, complex panchor1, complex pcontrol, complex panchor2) return sqr(1-pt)*panchor1 + 2*pt*(1-pt)*pcontrol + sqr(pt)*panchor2 endfunc ; given an interpolant, two endpoints, and two control points, compute a cubic curve static complex func InterpolateCubic(float pt, complex panchor1, complex pcontrol1, complex pcontrol2, complex panchor2) return (1-pt)^3*panchor1 + 3*pt*(1-pt)^2*pcontrol1 + 3*pt^2*(1-pt)*pcontrol2 + pt^3*panchor2 endfunc ; public variables ; you can set these yourself, but if you create invalid ; curve data it may not render correctly complex m_Anchors[] complex m_Control1[] complex m_Control2[] float m_Lengths[] int m_Types[] int m_Auto[] protected: int m_WindingMode int m_EndpointExtensionMode int m_AnchorNext int m_AnchorHighest bool m_RealIsStartPoint[] bool m_RealIsEndPoint[] complex m_RealAnchors[] complex m_RealControls[] complex m_RealAnchorNormals[] complex m_RealControlNormals[] complex m_RealExitNormals[] complex m_RealNormals[] float m_RealLengths[] float m_RealSummedLengths[] float m_RealSubLengths[] int m_RealIndex[] int m_RealTypes[] complex m_RealA[] complex m_RealB[] int m_RealAnchorCount ComplexArray m_Roots } class DMJ_PolyCurveSelector(common.ulb:Generic) { ; PolyCurve Selector abstract base class. ; ; When writing a formula which uses PolyCurve objects, it is ; usually necessary to allow the user to select the actual ; curve shape (unless it is going to be synthetically created). ; In that case, you can include a DMJ_PolyCurveSelector object ; and allow the user to choose any of the available curve ; selection mechanisms. You may also want to allow them to use ; DMJ_PolyCurveTransform objects, but that is up to you. public: import "common.ulb" func DMJ_PolyCurveSelector(Generic pparent) Generic.Generic(pparent) endfunc ; this function is used to set up the curve object ; do as much setup in the curve object as you like, ; but don't Rasterize() the curve func SetCurve(DMJ_PolyCurve &pcurve) endfunc ; this function is used to set up the handles on ; the curve func SetHandles(Handles phandles) endfunc default: int param v_dmj_polycurveselector caption = "Version (DMJ_PolyCurveSelector)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_polycurveselector < 100 endparam } class DMJ_FastClosestPoint { ; Fast point finder tool class. ; ; Given a set of target points and a test point, find the target ; point that is closest to the test point. ; ; The simplest method is brute-force; test against each point ; and record the closest. However, for large data sets (e.g. ; one million points) this is very slow. Instead, we build a ; sorted list of the points and index it with a binary tree. ; This reduces the number of points that need to be tested by ; locating for us a point that is closest on one particular ; axis. From there we walk in each direction looking for close ; points. ; ; This was adapted from dmj3.uxf:FastMosaic. public: import "common.ulb" func DMJ_FastClosestPoint() m_Points = 0 m_Extra = 0 m_TreeLeft = 0 m_TreeRight = 0 m_OrderLeft = 0 m_OrderRight = 0 m_Order = 0 endfunc func SetPointData(ComplexArray ppoints, Array pextra) m_Points = ppoints m_Extra = pextra ; start with just the first element in the index int length = m_Points.GetArrayLength() m_TreeLeft = new IntegerArray(length) m_TreeRight = new IntegerArray(length) m_OrderLeft = new IntegerArray(length) m_OrderRight = new IntegerArray(length) m_TreeLeft.m_Elements[0] = -1 ; both directions lead nowhere; this node is a leaf m_TreeRight.m_Elements[0] = -1 m_OrderLeft.m_Elements[0] = -1 ; this node marks the end of the ordered list m_OrderRight.m_Elements[0] = -1 ; insert all other points into the index int j = 1 int k = 0 int l = 0 complex p = 0 bool leftbranch = false while (j < length) ; get current point p = m_Points.m_Elements[j] ; walk the existing tree to find the placement k = 0 while (k >= 0) ; go until we're at a leaf node l = k ; save last valid tree node if (real(p) < real(m_Points.m_Elements[k]) || \ (real(p) == real(m_Points.m_Elements[k]) && imag(p) < imag(m_Points.m_Elements[k]))) ; p is left of k k = m_TreeLeft.m_Elements[k] leftbranch = true else k = m_TreeRight.m_Elements[k] leftbranch = false endif endwhile ; insert the new node in the tree m_TreeLeft.m_Elements[j] = -1 m_TreeRight.m_Elements[j] = -1 if (leftbranch) m_TreeLeft.m_Elements[l] = j else m_TreeRight.m_Elements[l] = j endif ; insert the new node into the sorted list ; this requires updating two links: one on ; each side of the newly-inserted point if (leftbranch) if (m_OrderLeft.m_Elements[l] >= 0) ; not the leftmost node so far m_OrderRight.m_Elements[m_OrderLeft.m_Elements[l]] = j ; make node to left of new point link to it endif m_OrderLeft.m_Elements[j] = m_OrderLeft.m_Elements[l] ; link to point left of new point m_OrderRight.m_Elements[j] = l ; link to point right of new point m_OrderLeft.m_Elements[l] = j ; make node to right of new point link to it else if (m_OrderRight.m_Elements[l] >= 0) ; not the rightmost node so far m_OrderLeft.m_Elements[m_OrderRight.m_Elements[l]] = j ; make node to right of new point link to it endif m_OrderRight.m_Elements[j] = m_OrderRight.m_Elements[l] ; link to point right of new point m_OrderLeft.m_Elements[j] = l ; link to point left of new point m_OrderRight.m_Elements[l] = j ; make node to left of new point link to it endif j = j + 1 endwhile ; now we have the full order, but the tree is probably ; not balanced; create a single simple index in sorted ; order ; allocate space for the index m_Order = new IntegerArray(length) ; find the leftmost node k = 0 while (k >= 0) l = k k = m_TreeLeft.m_Elements[k] endwhile ; copy index numbers j = 0 while (l >= 0) m_Order.m_Elements[j] = l l = m_OrderRight.m_Elements[l] j = j + 1 endwhile ; release unneeded indices m_TreeLeft = 0 m_TreeRight = 0 m_OrderLeft = 0 m_OrderRight = 0 endfunc ; given precomputed index, find closest point in our set int func GetClosestPoint(complex pz) ; start by finding the closest index based solely on the real value int k = GetClosestRealIndex(pz) ; now scan left and right to find nearby values that are candidates ; for shorter distances int length = m_Order.GetArrayLength() int left = k-1 int right = k+1 ; complex c = m_Points.m_Elements[m_Order.m_Elements[k]] ; current closest point float d = GetDistanceIndex(pz, k) ; current closest distance complex q = 0 float d2 = 0 while (left >= 0 || right < length) if (left >= 0) q = m_Points.m_Elements[m_Order.m_Elements[left]] ; coordinate to test d2 = GetDistanceLimit(real(pz), real(q)) if (d2 > d) ; too far on just the real axis, so nothing in this direction is closer; stop left = -1 else d2 = GetDistanceIndex(pz, left) if (d2 < d) ; this point is closer than our best d = d2 ; c = q k = left endif endif left = left - 1 endif if (right < length) q = m_Points.m_Elements[m_Order.m_Elements[right]] ; coordinate to test d2 = GetDistanceLimit(real(pz), real(q)) if (d2 > d) ; too far on just the real axis, so nothing in this direction is closer; stop right = length else d2 = GetDistanceIndex(pz, right) if (d2 < d) ; this point is closer than our best d = d2 ; c = q k = right endif endif right = right + 1 endif endwhile return k endfunc ; like GetClosestPoint(), but returns a sorted list of ; the closest set of points (as many as requested) ; NOTE: less efficient than GetClosestPoint() int func GetClosestPointSet(complex pz, int pcount, IntegerArray pindices, FloatArray pdistances, ComplexArray ppoints) ; start by finding the closest point int k = GetClosestPoint(pz) ; now scan left and right to find nearby values that are candidates ; for shorter distances int length = m_Order.GetArrayLength() int left = k-1 int right = k+1 int found = 1 ppoints.m_Elements[0] = m_Points.m_Elements[m_Order.m_Elements[k]] ; current closest point pdistances.m_Elements[0] = GetDistanceIndex(pz, k) ; current closest distance complex q = 0 float d2 = 0 int l = 1 int m = 0 while (l < pcount) pdistances.m_Elements[l] = 1e20 l = l + 1 endwhile while (left >= 0 || right < length) if (left >= 0) q = m_Points.m_Elements[m_Order.m_Elements[left]] ; coordinate to test d2 = GetDistanceLimit(real(pz), real(q)) if (found >= pcount && d2 > pdistances.m_Elements[found-1]) ; too far on just the real axis, so nothing in this direction is closer; stop left = -1 else d2 = GetDistanceIndex(pz, left) l = 1 ; check every distance to see if this is closer while (l < pcount) if (d2 < pdistances.m_Elements[l]) m = found-1 ; distance is closer than previous ones; shift them down while (m > l) ppoints.m_Elements[m] = ppoints.m_Elements[m-1] pdistances.m_Elements[m] = pdistances.m_Elements[m-1] pindices.m_Elements[m] = pindices.m_Elements[m-1] m = m - 1 endwhile ppoints.m_Elements[l] = q pdistances.m_Elements[l] = d2 pindices.m_Elements[l] = left if (found < pcount) found = found + 1 endif l = pcount + 1 endif l = l + 1 endwhile endif left = left - 1 endif if (right < length) q = m_Points.m_Elements[m_Order.m_Elements[right]] ; coordinate to test d2 = GetDistanceLimit(real(pz), real(q)) if (found >= pcount && d2 > pdistances.m_Elements[found-1]) ; too far on just the real axis, so nothing in this direction is closer; stop right = length else d2 = GetDistanceIndex(pz, right) l = 1 ; check every distance to see if this is closer while (l < pcount) if (d2 < pdistances.m_Elements[l]) m = found-1 ; distance is closer than previous ones; shift them down while (m > l) ppoints.m_Elements[m] = ppoints.m_Elements[m-1] pdistances.m_Elements[m] = pdistances.m_Elements[m-1] pindices.m_Elements[m] = pindices.m_Elements[m-1] m = m - 1 endwhile ppoints.m_Elements[l] = q pdistances.m_Elements[l] = d2 pindices.m_Elements[l] = right if (found < pcount) found = found + 1 endif l = pcount + 1 endif l = l + 1 endwhile endif right = right + 1 endif endwhile return k endfunc int func GetClosestRealIndex(complex pz) ; we already have an index that gives us the points in ; sorted real order; just binary search that list int length = m_Order.GetArrayLength() int j = 0 int k = 0 int l = 0 ; search range lower bound int m = length-1 ; search range upper bound while (l < m) k = floor((l+m) / 2) ; midpoint between bounds j = m_Order.m_Elements[k] ; actual point index if (pz == m_Points.m_Elements[j]) ; exact match l = k m = k elseif (real(pz) < real(m_Points.m_Elements[j]) || \ (real(pz) == real(m_Points.m_Elements[j]) && imag(pz) < imag(m_Points.m_Elements[j]))) ; lower half m = k else ; upper half if (l == k) ; span of just one; point lies somewhere between if ((real(pz) < (real(m_Points.m_Elements[m_Order.m_Elements[l]]) + \ real(m_Points.m_Elements[m_Order.m_Elements[m]]))/2) || \ (real(pz) == real(m_Points.m_Elements[m_Order.m_Elements[l]]) && \ real(pz) == real(m_Points.m_Elements[m_Order.m_Elements[m]]) && \ imag(pz) < (imag(m_Points.m_Elements[m_Order.m_Elements[l]]) + \ imag(m_Points.m_Elements[m_Order.m_Elements[m]]))/2)) m = l else l = m k = m endif else l = k endif endif endwhile return k endfunc ; distance metric; override to use a different metric float func GetDistance(complex ppoint1, complex ppoint2) return |ppoint1-ppoint2| endfunc ; distance metric, indexed version float func GetDistanceIndex(complex ppoint, int pindex) return |ppoint-m_Points.m_Elements[m_Order.m_Elements[pindex]]| endfunc ; if this distance exceeds the closest distance so far, scanning will stop float func GetDistanceLimit(float pr1, float pr2) return sqr(pr1-pr2) endfunc ; these are public rather than protected ComplexArray m_Points Array m_Extra IntegerArray m_Order protected: IntegerArray m_TreeLeft IntegerArray m_TreeRight IntegerArray m_OrderLeft IntegerArray m_OrderRight default: } ; ----------------------------------------------------------------------------------------------------------------------------- ; Formula classes class DMJ_Simurgh(common.ulb:DivergentFormula) { ; ; Simurgh/Phoenix/Double Mandelbrot Family ; ; This fractal type encompasses many variants in the ; Mandelbrot family of fractals. The basic Mandelbrot ; equation is: ; ; z[n+1] = z[n]^a + c ; ; where z[0]=0, a=2 and c varies with pixel location. The ; Phoenix fractal discovered by Shigehiro Ushiki in 1988 ; is a variation of this, where the input of two iterations ; is used: ; ; z[n+1] = z[n]^a + c*z[n]^d + p*z[n-1] ; ; The classical Phoenix fractal is the Julia form, where ; z[0] varies with pixel, d=0, c=0.56667, and p=-0.5. Note ; that if d=0 and p=0, the classical Mandelbrot equation ; is still there. So Phoenix fractals are a superset of ; the Mandelbrot fractals. ; ; The Simurgh fractal is a fairly straightforward extension ; of the idea I wrote in November 1999; it uses three ; iterations as input: ; ; z[n+1] = z[n]^a + c*z[n]^d + p*z[n-1] + q*z[n-2] ; ; If q=0, the Phoenix equation emerges, so again Simurgh ; fractals are a superset of Phoenix fractals. ; ; A different direction of extension to the classical ; Mandelbrot extension is DoubleMandel, which uses two ; separately-scaled z[n] terms: ; ; z[n+1] = s*z[n]^a + t*z[n]^b + c ; ; We can include this extension into the Simurgh equation: ; ; z[n+1] = s*z[n]^a + t*z[n]^b + c*z[n]^d + p*z[n-1] + q*z[n-2] ; ; This becomes the Simurgh equation when s=1 and t=0, so ; again it is a superset. The above equation is the one used ; in this formula. ; public: import "common.ulb" func DMJ_Simurgh(Generic pparent) Formula.Formula(pparent) endfunc complex func Init(complex pz) Formula.Init(pz) m_Seed = pz m_W = (0,0) m_Y = (0,0) m_ZNew = (0,0) return @p_start endfunc complex func Iterate(complex pz) Formula.Iterate(pz) m_ZNew = @p_coeff1*pz^@p_power1 + @p_coeff2*pz^@p_power2 + m_Seed*pz^@p_power3 + @p_induct1 * m_Y + @p_induct2 * m_W m_W = m_Y m_Y = pz return m_ZNew endfunc ; Determine the primary exponent. ; ; Many fractals can be characterized by an exponent value that ; is useful to other formulas, so we provide that here. If ; your formula does not need or use this value, override the ; p_power parameter and make it hidden. ; ; @return the primary exponent parameter complex func GetPrimaryExponent() if (|@p_power1| > |@p_power2|) return @p_power1 else return @p_power2 endif endfunc protected: complex m_Seed complex m_W complex m_Y complex m_ZNew default: title = "Mandelbrot/Phoenix/Simurgh" int param v_dmj_simurgh caption = "Version (DMJ_Simurgh)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_simurgh < 100 endparam complex param p_power visible = false endparam int param p_mode caption = "Formula Type" default = 0 enum = "Mandelbrot" "Phoenix" "Simurgh" hint = "Sets the fractal type." endparam bool param p_double caption = "Double variant" default = false hint = "If checked, enables the Double variant." endparam complex param p_start caption = "Start Value" default = (0,0) hint = "Starting value for each point. You can use this to 'perturb' the fractal." endparam complex param p_power1 caption = "1st Exponent" default = (2,0) hint = "Defines the primary exponent for the fractal. Use 2.0 for classical Mandelbrot and Phoenix." endparam complex param p_coeff1 caption = "1st Scale" default = (1,0) hint = "Defines the coefficient (multiplier) for the primary exponent term. Use 1.0 for classical Mandelbrot and Phoenix." visible = (@p_double || @p_coeff1 != 1.0) endparam complex param p_power2 caption = "2nd Exponent" default = (0,0) hint = "Defines the secondary exponent for the fractal." visible = (@p_double || @p_coeff2 != 0.0) endparam complex param p_coeff2 caption = "2nd Scale" default = (0,0) hint = "Defines the coefficient (multiplier) for the secondary exponent term. Use 0 for classical Mandelbrot and Phoenix." visible = (@p_double || @p_coeff2 != 0.0) endparam complex param p_power3 caption = "Phoenix Exp." default = (0,0) hint = "Defines the exponent for constant term's multiplier. Use 0 for classical Mandelbrot and Phoenix." visible = (@p_mode == 1 || @p_mode == 2 || @p_power3 != 0.0) endparam complex param p_induct1 caption = "Phoenix Dist." default = (0,0) hint = "Sets how 'strong' the previous iteration's effect should be on the fractal. Use -0.5 for classical Phoenix; use 0 for classical Mandelbrot." visible = (@p_mode == 1 || @p_mode == 2 || @p_induct1 != 0.0) endparam complex param p_induct2 caption = "Simurgh Dist." default = (0,0) hint = "Sets how 'strong' the next previous iteration's effect should be on the fractal. Use 0 for classical Mandelbrot and Phoenix." visible = (@p_mode == 2 || @p_induct2 != 0.0) endparam float param p_bailout caption = "Bailout" default = 1.0e20 hint = "Defines how soon an orbit bails out, i.e. doesn't belong to the fractal set anymore. Note: larger values result in more iterations being calculated." endparam } class DMJ_FormulaGenerators(common.ulb:Formula) { ; Produce a sequence of complex numbers using two Generator classes. By default, Random Generators are used. public: import "common.ulb" func DMJ_FormulaGenerators(Generic pparent) Formula.Formula(pparent) m_Generator1 = new @f_generator1(this) m_Generator2 = new @f_generator2(this) m_Transfer1 = new @f_transfer1(this) m_Transfer2 = new @f_transfer2(this) endfunc complex func Init(complex pz) Formula.Init(pz) ; base class sets this flag to stop immediately; clear it m_BailedOut = false if (@p_ignorepixel) m_Sequence1 = m_Generator1.InitDefault() m_Sequence2 = m_Generator2.InitDefault() else m_Sequence1 = m_Generator1.Init(real(pz)) m_Sequence2 = m_Generator2.Init(imag(pz)) endif complex c = m_Sequence1 + flip(m_Sequence2) if (@p_defaulttransfer) c = c*2 - (1,1) else m_Transfer1.Init(pz) m_Transfer2.Init(pz) c = m_Transfer1.Iterate(real(c)) + flip(m_Transfer2.Iterate(imag(c))) endif c = c*@p_scale + @p_center return c endfunc complex func Iterate(complex pz) Formula.Iterate(pz) m_Sequence1 = m_Generator1.Iterate(m_Sequence1) m_Sequence2 = m_Generator2.Iterate(m_Sequence2) complex c = m_Sequence1 + flip(m_Sequence2) if (@p_defaulttransfer) c = c*2 - (1,1) else m_Transfer1.Init(pz) m_Transfer2.Init(pz) c = m_Transfer1.Iterate(real(c)) + flip(m_Transfer2.Iterate(imag(c))) endif c = c*@p_scale + @p_center return c endfunc protected: Generator m_Generator1 Generator m_Generator2 Transfer m_Transfer1 Transfer m_Transfer2 float m_Sequence1 float m_Sequence2 default: title = "Dual Generators (Random Default)" int param v_dmj_formulagenerators caption = "Version (DMJ_FormulaGenerators)" default = 101 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_formulagenerators < 100 endparam complex param p_power visible = false endparam Generator param f_generator1 caption = "Real Generator" default = DMJ_GeneratorRandom hint = "Sets the Generator to use to produce the real component of each value." endparam Generator param f_generator2 caption = "Imaginary Generator" default = DMJ_GeneratorRandom2 hint = "Sets the Generator to use to produce the imaginary component of each value." endparam bool param p_ignorepixel caption = "Ignore start value" default = true hint = "If checked, the Generators will be started with their default values regardless of the starting value that would normally apply (per pixel). This is useful in situations where the Formula is being used to generate the same sequence of values for every pixel." endparam bool param p_defaulttransfer caption = "Use default range" default = true hint = "If checked, the default range of [-1,1] will be used for each component Generator. Otherwise, you will be able to select your own Transfer objects." endparam Transfer param f_transfer1 caption = "Real Transfer" default = NullTransfer hint = "Allows you to use a Transfer object to manipulate the output of the real component Generator." visible = (@p_defaulttransfer == false) endparam Transfer param f_transfer2 caption = "Imaginary Transfer" default = NullTransfer hint = "Allows you to use a Transfer object to manipulate the output of the imaginary component Generator." visible = (@p_defaulttransfer == false) endparam float param p_scale caption = "Overall Scale" default = 2/#magn hint = "Allows you to scale the output of the Generators quickly. If the default range is used, this value will become the maximum and its negative will become the minimum." endparam complex param p_center caption = "Overall Center" default = #center hint = "Allows you to move the output of the Generators quickly." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Coloring classes class DMJ_OrbitTrapsColoring(common.ulb:GradientColoring) { ; ; ...need details... ; public: import "common.ulb" func DMJ_OrbitTrapsColoring(Generic pparent) GradientColoring.GradientColoring(pparent) m_TrapSelect = new @f_trapselect(this) m_TrapTransform = new @f_traptransform(this) m_TrapShape = new @f_trapshape(this) m_TrapTransfer = new @f_traptransfer(this) m_TrapMode = new @f_trapmode(this) m_TrapTexture = new @f_traptexture(this) m_TrapColoring = new @f_trapcoloring(this) m_TrapShape.SetThreshold(@p_threshold) m_TrapMode.SetThreshold(@p_threshold) endfunc func Init(complex pz, complex ppixel) GradientColoring.Init(pz, ppixel) m_TrapSelectSequence = m_TrapSelect.InitDefault() m_TrapTransform.Init(pz) m_TrapShape.Init(pz) m_TrapTransfer.Init(pz) m_TrapMode.Init(pz) m_TrapTexture.Init(pz) endfunc func Iterate(complex pz) GradientColoring.Iterate(pz) ; OVERVIEW ; decide whether to test this iteration ; for each trap ; update any incremental values for this trap ; get coordinate from #z or from previous trap transformations ; transform trap ; apply fBm distortion to trap ; radially repeat trap shape ; grid repeat trap shape ; quantize trap shape ; rotate trap shape ; apply trap aspect ratio ; skew trap ; determine distance to trap ; use different method for each shape ; apply diameter(s) to trap ; apply fBm texturizing to trap (before add) ; apply fade add/multiply ; apply fBm texturizing to trap (after add) ; apply trap transfer function ; apply fBm texturizing to trap (after transfer) ; eliminate negative distances ; compute and save fBm "color" for this coordinate ; combine trap distances ; for each trap ; apply trap weight function ; merge traps ; apply trap merge to update internal counters m_TrapSelectSequence = m_TrapSelect.Iterate(m_TrapSelectSequence) if ((@f_trapselect == DMJ_TrapSelect && m_TrapSelectSequence > 0.5) || m_TrapSelectSequence > @p_trapselectthreshold) complex zt = m_TrapTransform.Iterate(pz) float distance = m_TrapShape.Iterate(zt) distance = m_TrapTransfer.Iterate(distance) zt = m_TrapShape.GetTransformedPoint() float texture = m_TrapShape.GetTextureValue() if (@p_textureposttransform) texture = texture + m_TrapTexture.Iterate(zt) else texture = texture + m_TrapTexture.Iterate(pz) endif texture = (texture + m_TrapTexture.GetTextureValue()) * @p_texturestrength if (!m_TrapTransform.IsSolid()) m_TrapMode.Iterate(pz, zt, distance, texture) else m_TrapMode.IterateSilent() endif else m_TrapTransform.IterateSilent() m_TrapShape.IterateSilent() m_TrapTransfer.IterateSilent() m_TrapMode.IterateSilent() m_TrapTexture.IterateSilent() endif endfunc float func ResultIndex(complex pz) GradientColoring.ResultIndex(pz) ; OVERVIEW ; apply trap merge update function ; color based on coloring mode m_TrapMode.Result() m_Solid = @p_usesolid && m_TrapMode.IsSolid() ; honor solid flag return m_TrapColoring.Result(m_TrapMode) endfunc protected: Generator m_TrapSelect UserTransform m_TrapTransform TrapShape m_TrapShape Transfer m_TrapTransfer TrapMode m_TrapMode TrapShape m_TrapTexture TrapColoring m_TrapColoring float m_TrapSelectSequence default: title = "Orbit Traps Gradient (UF5)" int param v_dmj_orbittrapscoloring caption = "Version (DMJ_OrbitTrapsColoring)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_orbittrapscoloring < 100 endparam Generator param f_trapselect caption = "Trap Iteration" default = DMJ_TrapSelect expanded = false endparam float param p_trapselectthreshold caption = "Threshold" default = 0.5 hint = "When using a Generator other than a TrapSelect type, values could range anywhere from 0 to 1. This threshold sets which values will be interpreted as 'do not trap' and which will be interpreted as 'trap'." visible = (@f_trapselect != DMJ_TrapSelect) endparam UserTransform param f_traptransform caption = "Trap Position" default = TrapTransform hint = "This transform is commonly used to set the position of the trap. You can load any Transform object here, but if you replace the TrapTransform you will lose the ability to position the trap. To regain that ability, use the TransformMerge class here so you can load any Transform and still be able to use a TrapTransform object." endparam TrapShape param f_trapshape caption = "Trap Shape" default = DMJ_TrapShapeAstroid hint = "The trap shape sets the overall shape of the trap. Its only task is to measure the distance from any point to the trap shape." endparam Transfer param f_traptransfer caption = "Trap Transfer" default = TrapTransfer hint = "A transfer allows you to manipulate the distances provided by the trap shape." expanded = false endparam TrapMode param f_trapmode caption = "Trap Mode" default = DMJ_TrapModeClosest endparam float param p_threshold caption = "Trap Threshold" default = 0.25 hint = "This is the overall size or thickness of the trap area. (Some trap modes may not use the threshold value.)" visible = (@f_trapmode == TrapModeWithThreshold) endparam bool param p_usesolid caption = "Use Solid Color" default = false hint = "If checked, any areas not inside any trap shape will be colored the solid color." endparam TrapShape param f_traptexture caption = "Trap Texture" default = DMJ_TrapShapeFlat hint = "A trap shape that is used as a texture. Textures do not change the shape of the trap but may change its coloring." endparam bool param p_textureposttransform caption = "Use Transformed Coordinate" default = false hint = "If checked, texturing will be based on the final transformed coordinate used in the trap shape (i.e. it will follow the trap shape, rather than being separate from it)." visible = (@f_traptexture != DMJ_TrapShapeFlat) endparam float param p_texturestrength caption = "Texture Amount" default = 0.25 hint = "Sets the overall amount of texture to be used. Larger numbers will increase the effect of the texture. A value of 0 will remove the effects of the texture." visible = (@f_traptexture != DMJ_TrapShapeFlat) endparam TrapColoring param f_trapcoloring caption = "Trap Color Mode" default = TrapColoringDistance endparam } class DMJ_OrbitTrapsDirect(common.ulb:DirectColoring) { ; ; ...need details... ; public: import "common.ulb" func DMJ_OrbitTrapsDirect(Generic pparent) DirectColoring.DirectColoring(pparent) m_TrapSelect = new @f_trapselect(this) m_Blend = new @f_trapmergemode(this) m_TrapTransform = new @f_traptransform(this) m_TrapShape = new @f_trapshape(this) m_ColorTransfer = new @f_colortransfer(this) endfunc func Init(complex pz, complex ppixel) DirectColoring.Init(pz, ppixel) m_TrapSelectSequence = m_TrapSelect.InitDefault() m_TrapTransform.Init(pz) m_TrapShape.Init(pz) m_ColorTransfer.Init(pz) m_AccumulatedColor = @p_basecolor m_Opaque = false endfunc func Iterate(complex pz) DirectColoring.Iterate(pz) if (m_Opaque) return endif m_TrapSelectSequence = m_TrapSelect.Iterate(m_TrapSelectSequence) if ((@f_trapselect == DMJ_TrapSelect && m_TrapSelectSequence > 0.5) || m_TrapSelectSequence > @p_trapselectthreshold) complex zt = m_TrapTransform.Iterate(pz) color current = m_TrapShape.Iterate(zt) current = m_ColorTransfer.Iterate(current) if (m_TrapTransform.IsSolid()) current = @p_transformsolidcolor endif if (@p_trapmergeorder == 0) m_AccumulatedColor = m_Blend.FullMerge(m_AccumulatedColor, current, @p_trapmergeopacity) elseif (@p_trapmergeorder == 1) m_AccumulatedColor = m_Blend.FullMerge(current, m_AccumulatedColor, @p_trapmergeopacity) m_Opaque = m_Blend.IsOpaque(m_AccumulatedColor) endif else m_TrapTransform.IterateSilent() m_TrapShape.IterateSilent() m_ColorTransfer.IterateSilent() endif endfunc color func Result(complex pz) DirectColoring.Result(pz) if (@p_trapmergeorder == 0) m_AccumulatedColor = m_Blend.FullMerge(m_AccumulatedColor, @p_finalcolor, @p_trapmergeopacity) elseif (@p_trapmergeorder == 1) m_AccumulatedColor = m_Blend.FullMerge(@p_finalcolor, m_AccumulatedColor, @p_trapmergeopacity) endif return m_AccumulatedColor endfunc protected: Generator m_TrapSelect ColorMerge m_Blend UserTransform m_TrapTransform ColorTrap m_TrapShape ColorTransfer m_ColorTransfer color m_AccumulatedColor bool m_Opaque float m_TrapSelectSequence default: title = "Orbit Traps Direct (UF5)" int param v_dmj_orbittrapsdirect caption = "Version (DMJ_OrbitTrapsDirect)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_orbittrapsdirect < 100 endparam Generator param f_trapselect caption = "Trap Iteration" default = DMJ_TrapSelect expanded = false endparam float param p_trapselectthreshold caption = "Threshold" default = 0.5 hint = "When using a Generator other than a TrapSelect type, values could range anywhere from 0 to 1. This threshold sets which values will be interpreted as 'do not trap' and which will be interpreted as 'trap'." visible = (@f_trapselect != DMJ_TrapSelect) endparam color param p_transformsolidcolor caption = "Transform Solid Color" default = rgb(0,0,1) hint = "If you place a Transform in the Trap Position slot, and it produces areas with solid colors, they will get this color." visible = (@f_traptransform != TrapTransform) endparam color param p_basecolor caption = "Base Color" default = rgb(0,0,0) hint = "Specifies the 'base', or starting color with which all iterations' colors will be merged. If you are merging top-down rather than bottom-up, you will likely want this to be transparent." endparam color param p_finalcolor caption = "Final Color" default = rgba(0,0,0,0) hint = "Specifies the 'final' color that will be merged with the result after all iterations are complete. If you are merging top-down you can use this to provide a background color." endparam ColorMerge param f_trapmergemode caption = "Trap Color Merge" default = DefaultColorMerge hint = "This chooses the merge mode used to blend colors at each iteration." endparam ; additional alpha options go here float param p_trapmergeopacity caption = "Trap Merge Opacity" default = 0.2 hint = "Sets the opacity of each trap shape. Even if you set this value to 1 (forcing all traps to be fully opaque) you can still control opacity using the alpha channel in the gradient." endparam param p_trapmergeorder caption = "Trap Merge Order" default = 0 enum = "bottom-up" "top-down" hint = "Sets the order in which traps will be merged. Bottom-up merges new traps on top of previous ones; top-down merges new traps underneath previous ones." endparam UserTransform param f_traptransform caption = "Trap Position" default = TrapTransform endparam ColorTrap param f_trapshape caption = "Trap Shape" default = ColorTrapWrapper endparam ColorTransfer param f_colortransfer caption = "Color Transfer" default = NullColorTransfer hint = "This is the color effect to apply. By default, this is none." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Trap Shape classes class DMJ_TrapShapeAstroid(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeAstroid(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float d = abs(real(pz))^@p_power + abs(imag(pz))^@p_power if (@p_power < 0) d = 1/d endif return d endfunc default: title = "Astroid" int param v_dmj_trapshapeastroid caption = "Version (DMJ_TrapShapeAstroid)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapeastroid < 100 endparam float param p_power caption = "Squareness" default = 0.666666666666667 hint = "Indicates the squareness of the astroid shape. Values less than one produce diamonds; values larger than one push the shape closer and closer towards a square." endparam } class DMJ_TrapShapeCross(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeCross(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float d = abs(real(pz)) float d2 = abs(imag(pz)) if (d2 < d) return d2 else return d endif endfunc default: title = "Cross" int param v_dmj_trapshapecross caption = "Version (DMJ_TrapShapeCross)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapecross < 100 endparam } class DMJ_TrapShapeEgg(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeEgg(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) return cabs(pz) + cabs(pz-flip(@p_separation))*@p_egginess - @p_separation*@p_egginess endfunc default: title = "Egg" int param v_dmj_trapshapeegg caption = "Version (DMJ_TrapShapeEgg)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapeegg < 100 endparam float param p_separation caption = "Separation" default = 1.0 hint = "Indicates the distance between the focal points of the egg. Widening the distance will make the egg longer." endparam float param p_egginess caption = "Egginess" default = 0.6666667 hint = "Influences how pointy the egg should be. Use a value of 1.0 for a perfectly elliptical egg; use smaller values for an egg that is more pointed at one end." endparam } class DMJ_TrapShapeFBM(common.ulb:TrapShape) { ; Fractional Brownian Motion (fBm) trap shape class. public: import "common.ulb" func DMJ_TrapShapeFBM(Generic pparent) TrapShape.TrapShape(pparent) m_Noise = new @f_noise(this) m_Rotation = (0,1) ^ (@p_angle / 90.0) m_RotationStep = (0,1) ^ (@p_anglestep / 90.0) endfunc func Init(complex pz) TrapShape.Init(pz) m_Noise.Init(pz) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float sum = 0.0 float amplitude = 1.0 float basis = 0.0 pz = pz * @p_scale * m_Rotation + @p_offset int j = 0 while (j < @p_octaves) basis = m_Noise.Iterate(pz) * amplitude sum = sum + basis amplitude = amplitude * @p_step pz = pz * m_RotationStep / @p_step j = j + 1 endwhile if (@p_octaves - floor(@p_octaves) > 0) sum = sum - basis basis = basis * (@p_octaves - floor(@p_octaves))^(@p_step) sum = sum + basis endif return sum endfunc protected: TrapShape m_Noise complex m_Rotation complex m_RotationStep default: title = "fBm" int param v_dmj_trapshapefbm caption = "Version (DMJ_TrapShapeFBM)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapefbm < 100 endparam TrapShapeNoise param f_noise caption = "Noise Basis Function" default = TrapShapeNoise hint = "Sets the noise function that is used as the foundation for the fractional noise computation." expanded = false endparam float param p_scale caption = "Scale" default = 1.0 hint = "This is the overall scale of the noise. Larger numbers will produce finer-grained noise." endparam float param p_angle caption = "Rotation" default = 0.0 hint = "This is the angle, in degrees, of the noise." endparam float param p_step caption = "Scale Step" default = 0.5 min = 0.0 hint = "This is the step in scale between noise iterations. Reducing this value will result in noise with less 'bumpiness'." endparam float param p_anglestep caption = "Rotation Step" default = 37.0 hint = "This is the angle, in degrees, to rotate between noise iterations. You should use values that do not divide evenly into 360 for best results." endparam float param p_octaves caption = "Octaves" default = 7 min = 1 hint = "This is the number of iterations of the noise formula. More iterations will add progressively finer variations to the noise. If you use a non-integer value, the final iteration's contribution will only be partially used." endparam complex param p_offset caption = "Offset" default = (0,0) hint = "This is the offset of the entire pattern. This offset is applied only once, after scale and rotation." endparam } class DMJ_TrapShapeFlat(common.ulb:TrapShape) { ; This "flat" trap shape is used to get a flat texture and is not really a trap shape at all. public: import "common.ulb" func DMJ_TrapShapeFlat(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) return 0 endfunc default: title = "Flat Texture" int param v_dmj_trapshapeflat caption = "Version (DMJ_TrapShapeFlat)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapeflat < 100 endparam } class DMJ_TrapShapeGridRipples(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeGridRipples(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float d = 0 if (@p_diagonal == 0) if (@p_merging == 0) d = (cos((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 + \ cos((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) + \ (cos((imag(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 + \ cos((imag(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) elseif (@p_merging == 1) d = (cos((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 * \ cos((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) + \ (cos((imag(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 * \ cos((imag(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) endif elseif (@p_diagonal == 1) if (@p_merging == 0) d = (cos((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 + \ cos((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) * \ (cos((imag(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 + \ cos((imag(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) elseif (@p_merging == 1) d = (cos((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 * \ cos((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) * \ (cos((imag(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 * \ cos((imag(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) endif endif return d endfunc default: title = "Grid Ripples" int param v_dmj_trapshapegridripples caption = "Version (DMJ_TrapShapeGridRipples)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapegridripples < 100 endparam int param p_diagonal caption = "Ripple Style" default = 0 enum = "add" "multiply" hint = "Sets how the waves from horizontal and vertical components interact." endparam int param p_merging caption = "Wave Merging" default = 0 enum = "add" "multiply" hint = "Sets how the two frequency generators should be combined into a final wave." endparam float param p_amplitude1 caption = "Amplitude 1" default = 1.0 hint = "Sets one of the wave amplitudes. Higher amplitudes will result in higher peaks." endparam float param p_frequency1 caption = "Frequency 1" default = 1.0 visible = @p_amplitude1 != 0.0 hint = "Sets one of the wave frequencies. Higher frequencies will result in more tightly-spaced peaks in the wave." endparam float param p_phase1 caption = "Phase 1" default = 0.0 visible = @p_amplitude1 != 0.0 hint = "Sets the phase of one of the waves, in degrees." endparam float param p_amplitude2 caption = "Amplitude 2" default = 0.0 hint = "Sets one of the wave amplitudes. Higher amplitudes will result in higher peaks." endparam float param p_frequency2 caption = "Frequency 2" default = 3.0 visible = @p_amplitude2 != 0.0 hint = "Sets one of the wave frequencies. Higher frequencies will result in more tightly-spaced peaks in the wave." endparam float param p_phase2 caption = "Phase 2" default = 0.0 visible = @p_amplitude2 != 0.0 hint = "Sets the phase of one of the waves, in degrees." endparam } class DMJ_TrapShapeHeart1(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeHeart1(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) complex r = real(pz) + flip(abs(imag(pz))) r = r * ((0,1) ^ (@p_sharpness/90.0)) * 3 / @p_separation return abs(real(r) - sqr(imag(r)) + 3) endfunc default: title = "Heart 1" int param v_dmj_trapshapeheart1 caption = "Version (DMJ_TrapShapeHeart1)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapeheart1 < 100 endparam float param p_sharpness caption = "Sharpness" default = 45.0 hint = "Sets the angle of the two halves towards each other, in degrees. Decreasing this value will make the heart shape narrower and more 'pointy'." endparam float param p_separation caption = "Separation" default = 1.0 hint = "Sets how far apart the two halves are. Larger values will result in a larger heart shape." endparam } class DMJ_TrapShapeHeart2(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeHeart2(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) complex zt = real(abs(pz)) + flip(imag(pz)) zt = real(zt * ((0,1) ^ (@p_skew/90.0))) + flip(imag(zt)) return cabs(zt) endfunc default: title = "Heart 2" int param v_dmj_trapshapeheart2 caption = "Version (DMJ_TrapShapeHeart2)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapeheart2 < 100 endparam float param p_skew caption = "Skew" default = 45.0 hint = "Sets the skew angle of the two halves of the heart shape, in degrees. Increasing this value will make the heart shape narrower and more 'pointy'." endparam } class DMJ_TrapShapeHyperbola(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeHyperbola(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) return imag(pz) * real(pz) endfunc default: title = "Hyperbola" int param v_dmj_trapshapehyperbola caption = "Version (DMJ_TrapShapeHyperbola)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapehyperbola < 100 endparam } class DMJ_TrapShapeHypercross(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeHypercross(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) return abs(imag(pz) * real(pz)) endfunc default: title = "Hypercross" int param v_dmj_trapshapehypercross caption = "Version (DMJ_TrapShapeHypercross)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapehypercross < 100 endparam } class DMJ_TrapShapeLinearWave(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeLinearWave(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float d = 0 if (@p_mirroring == 0) if (@p_merging == 0) d = abs(imag(pz) + \ sin((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 + \ sin((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) elseif (@p_merging == 1) d = abs(imag(pz) + \ sin((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 * \ sin((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2) endif elseif (@p_mirroring == 1) if (@p_merging == 0) d = abs(imag(pz)) + \ sin((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 + \ sin((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2 elseif (@p_merging == 1) d = abs(imag(pz)) + \ sin((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 * \ sin((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2 endif d = abs(d) elseif (@p_mirroring == 2) float d2 = 0 if (@p_merging == 0) d2 = sin((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 + \ sin((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2 elseif (@p_merging == 1) d2 = sin((real(pz)+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1 * \ sin((real(pz)+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2 endif d = abs(abs(imag(pz)) - d2) ; distance to each wave d2 = abs(abs(imag(pz)) + d2) if (d2 < d) d = d2 endif endif return d endfunc default: title = "Linear Wave" int param v_dmj_trapshapelinearwave caption = "Version (DMJ_TrapShapeLinearWave)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapelinearwave < 100 endparam int param p_mirroring caption = "Mirroring" default = 0 enum = "none" "half" "full" hint = "Sets how the trap shape will be reflected along a line." endparam int param p_merging caption = "Wave Merging" default = 0 enum = "add" "multiply" hint = "Sets how the two frequency generators should be combined into a final wave." endparam float param p_amplitude1 caption = "Amplitude 1" default = 1.0 hint = "Sets one of the wave amplitudes. Higher amplitudes will result in higher peaks." endparam float param p_frequency1 caption = "Frequency 1" default = 1.0 visible = @p_amplitude1 != 0.0 hint = "Sets one of the wave frequencies. Higher frequencies will result in more tightly-spaced peaks in the wave." endparam float param p_phase1 caption = "Phase 1" default = 0.0 visible = @p_amplitude1 != 0.0 hint = "Sets the phase of one of the waves, in degrees." endparam float param p_amplitude2 caption = "Amplitude 2" default = 0.0 hint = "Sets one of the wave amplitudes. Higher amplitudes will result in higher peaks." endparam float param p_frequency2 caption = "Frequency 2" default = 3.0 visible = @p_amplitude2 != 0.0 hint = "Sets one of the wave frequencies. Higher frequencies will result in more tightly-spaced peaks in the wave." endparam float param p_phase2 caption = "Phase 2" default = 0.0 visible = @p_amplitude2 != 0.0 hint = "Sets the phase of one of the waves, in degrees." endparam } class DMJ_TrapShapeLines(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeLines(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) return abs(imag(pz)) endfunc default: title = "Lines" int param v_dmj_trapshapelines caption = "Version (DMJ_TrapShapeLines)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapelines < 100 endparam } class DMJ_TrapShapePinch(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapePinch(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float d = atan2(pz) if (d < 0) d = d + 2*#pi endif return sqrt(cabs(pz)) / abs(sin(d*@p_petals*0.5)) * 0.25 endfunc default: title = "Pinch" int param v_dmj_trapshapepinch caption = "Version (DMJ_TrapShapePinch)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapepinch < 100 endparam float param p_petals caption = "Petals" default = 4.0 hint = "Indicates how many petals the pinch trap shape should have." endparam } class DMJ_TrapShapePoint(common.ulb:TrapShape) { ; Simple point trap shape. (This is a clone of the common.ulb shape, included here for convenience.) public: import "common.ulb" func DMJ_TrapShapePoint(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) return cabs(pz) endfunc default: title = "Point" int param v_dmj_trapshapepoint caption = "Version (DMJ_TrapShapePoint)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapepoint < 100 endparam } class DMJ_TrapShapePolyCurve(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapePolyCurve(Generic pparent) TrapShape.TrapShape(pparent) m_Handles = new @f_handles(this) m_Handles.SetScreenRelative(0) m_PolyCurve = new DMJ_PolyCurve() m_Selector = new @f_selector(this) m_Selector.SetCurve(m_PolyCurve) m_PolyCurve.Rasterize(0) m_Selector.SetHandles(m_Handles) endfunc func Init(complex pz) TrapShape.Init(pz) m_Handles.Init(pz) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) ; check if point is inside the polycurve or not bool isinside = m_PolyCurve.IsInside(pz) ; check handles bool inhandle = false if (m_Iterations == 1) complex cz = m_Handles.Iterate(pz) inhandle = m_Handles.IsSolid() endif if (@p_edgemode > 0) float d float t float to bool issolid = false ; get distance from curve edge and apply correct sign m_PolyCurve.ClosestPoint(pz, t, to, d) d = sqrt(d) if (isinside) d = -d endif ; apply sharp edge modes now if (@p_edgemode < 4) isinside = false if (@p_edgemode == 1) if (d < -@p_edgegrow) isinside = true endif elseif (@p_edgemode == 2) if (d < @p_edgegrow) isinside = true endif elseif (@p_edgemode == 3) if (d < @p_edgegrow && d > -@p_edgegrow) isinside = true endif endif ; apply handle state if (inhandle) isinside = !isinside endif ; return result if (isinside) return 0 else return @p_edgevalue*2 endif endif ; limit distance to edge if necessary if (@p_edgelimit) if (d < -@p_edgewidth) d = -@p_edgewidth elseif (d > @p_edgewidth) d = @p_edgewidth issolid = true endif endif ; apply edge mode if (@p_edgemode == 4) if (d > 0) d = 0 issolid = true endif elseif (@p_edgemode == 5) if (d < 0) d = 0 endif elseif (@p_edgemode == 7) d = abs(d) if (@p_edgelimit && d >= @p_edgewidth) ; inside needs clipping too issolid = true endif endif ; return result if (issolid) if (inhandle) return 0 else return 1e20 endif else if (inhandle) return 1e20 else return @p_edgevalue + d/@p_edgesoftness endif endif else ; sharp edges ; apply handle state if (inhandle) isinside = !isinside endif ; return result if (isinside) return 0 else return @p_edgevalue*2 endif endif endfunc protected: Handles m_Handles DMJ_PolyCurve m_PolyCurve DMJ_PolyCurveSelector m_Selector default: title = "PolyCurve Family" int param v_dmj_trapshapepolycurve caption = "Version (DMJ_TrapShapePolyCurve)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapepolycurve < 100 endparam DMJ_PolyCurveSelector param f_selector caption = "Curve Definition" default = DMJ_SelectorArbitraryPolygon hint = "Sets the method by which you will define your curve or polygon." endparam float param p_edgevalue caption = "Assumed Edge Distance" default = 0.5 hint = "PolyCurve objects don't have a central point from which distance is measured; instead, they have a precise edge that marks the boundary between inside and outside. To make a PolyCurve work like a trap shape, we treat the edge as being a particular 'distance' and then measure the distance to the edge to get the 'real' distance. If your edge is set to sharp, points 'inside' will have distance 0 and points 'outside' will have double this edge distance. Otherwise, the distance used will be the distance from the edge added to this value." endparam int param p_edgemode caption = "Edge Style" default = 0 enum = "sharp" "sharp (shrink)" "sharp (grow)" "sharp (edge only)" "soft (inside only)" "soft (outside only)" "soft (both sides)" "soft (edge only)" hint = "Sets whether sharp edges or soft edges will be used. Sharp edges are faster to calculate and will remain sharp no matter how much you zoom in. Soft edges are slower but allow different effects." endparam float param p_edgegrow caption = "Grow/Shrink Edge" default = 0.1 hint = "Sets the amount to grow or shrink the edge." visible = (@p_edgemode > 0 && @p_edgemode < 4) endparam float param p_edgesoftness caption = "Edge Softness" default = 1.0 hint = "Sets the softness of the edge. Larger values will result in a softer edge." visible = (@p_edgemode >= 4) endparam bool param p_edgelimit caption = "Limit Edge" default = true hint = "If set, the soft edge will be clipped to all points within a set distance of the edge. This produces more consistent trap shapes." visible = (@p_edgemode >= 4) endparam float param p_edgewidth caption = "Edge Width" default = 0.1 hint = "Sets the maximum distance from the curve edge for the soft edge." visible = (@p_edgemode >= 4) endparam heading caption = "Handle Options" expanded = false endheading Handles param f_handles caption = "Handle Options" selectable = false endparam } class DMJ_TrapShapeRadialWave(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeRadialWave(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float a = atan2(pz) float d = 0 if (@p_merging == 0) d = cabs(pz) * (1 - \ sin((a+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1*0.5 - \ sin((a+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2*0.5) elseif (@p_merging == 1) d = cabs(pz) * (1 - \ sin((a+@p_phase1*#pi/180)*@p_frequency1)*@p_amplitude1*0.5 * \ sin((a+@p_phase2*#pi/180)*@p_frequency2)*@p_amplitude2*0.5) endif return d endfunc default: title = "Radial Wave" int param v_dmj_trapshaperadialwave caption = "Version (DMJ_TrapShapeRadialWave)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshaperadialwave < 100 endparam int param p_merging caption = "Wave Merging" default = 0 enum = "add" "multiply" hint = "Sets how the two frequency generators should be combined into a final wave." endparam float param p_amplitude1 caption = "Amplitude 1" default = 1.0 hint = "Sets one of the wave amplitudes. Higher amplitudes will result in higher peaks." endparam float param p_frequency1 caption = "Frequency 1" default = 4.0 visible = @p_amplitude1 != 0.0 hint = "Sets one of the wave frequencies. Higher frequencies will result in more tightly-spaced peaks in the wave." endparam float param p_phase1 caption = "Phase 1" default = 0.0 visible = @p_amplitude1 != 0.0 hint = "Sets the phase of one of the waves, in degrees." endparam float param p_amplitude2 caption = "Amplitude 2" default = 0.0 hint = "Sets one of the wave amplitudes. Higher amplitudes will result in higher peaks." endparam float param p_frequency2 caption = "Frequency 2" default = 3.0 visible = @p_amplitude2 != 0.0 hint = "Sets one of the wave frequencies. Higher frequencies will result in more tightly-spaced peaks in the wave." endparam float param p_phase2 caption = "Phase 2" default = 0.0 visible = @p_amplitude2 != 0.0 hint = "Sets the phase of one of the waves, in degrees." endparam } class DMJ_TrapShapeRectangle(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeRectangle(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float d = abs(real(pz)) float d2 = abs(imag(pz)) if (d2 > d) return d2 else return d endif endfunc default: title = "Rectangle" int param v_dmj_trapshaperectangle caption = "Version (DMJ_TrapShapeRectangle)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshaperectangle < 100 endparam } class DMJ_TrapShapeSpiral(common.ulb:TrapShape) { public: import "common.ulb" func DMJ_TrapShapeSpiral(Generic pparent) TrapShape.TrapShape(pparent) endfunc float func Iterate(complex pz) TrapShape.Iterate(pz) float d = 1/cabs(pz) * @p_tightness complex r = (0,1) ^ d complex zt = pz * r return atan(abs(imag(zt)/real(zt))) endfunc default: title = "Spiral" int param v_dmj_trapshapespiral caption = "Version (DMJ_TrapShapeSpiral)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapshapespiral < 100 endparam float param p_tightness caption = "Tightness" default = 4.0 hint = "Sets how 'tight' the spiral is. Higher values will place loops of the spiral closer together." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Color Trap Shape classes class DMJ_BlurTrapWrapper(common.ulb:ColorTrap) { ; This wrapper allows you to use any color trap and any convolution ; filter to produce blurred or sharpened (or other) trap effects. public: import "common.ulb" func DMJ_BlurTrapWrapper(Generic pparent) ColorTrap.ColorTrap(pparent) m_Filter = new @f_filter(this) m_Samples = length(m_Filter.m_Offsets) setLength(m_TrapShapes, m_Samples) int j = 0 while (j < m_Samples) m_TrapShapes[j] = new @f_trapshape(this) j = j + 1 endwhile endfunc func Init(complex pz) ColorTrap.Init(pz) int j = 0 while (j < m_Samples) m_TrapShapes[j].Init(pz) j = j + 1 endwhile if (@p_filterreset == 0 && DMJ_VariableConvolutionFilter(m_Filter) != 0) m_Filter.Init(pz) endif endfunc color func Iterate(complex pz) ColorTrap.Iterate(pz) if (@p_filterreset == 1 && DMJ_VariableConvolutionFilter(m_Filter) != 0) m_Filter.Init(pz) endif float r = 0.0 float g = 0.0 float b = 0.0 float a = 0.0 float w float o color c = rgb(0,0,0) int j = 0 while (j < m_Samples) c = m_TrapShapes[j].Iterate(pz+m_Filter.m_Offsets[j]) w = m_Filter.m_Weights[j] r = r + red(c) * w g = g + green(c) * w b = b + blue(c) * w a = a + alpha(c) * w j = j + 1 endwhile w = m_Filter.m_Multiplier o = m_Filter.m_Bias r = r * w + o g = g * w + o b = b * w + o a = a * w + o return rgba(r,g,b,a) endfunc protected: DMJ_ConvolutionFilter m_Filter int m_Samples ColorTrap m_TrapShapes[] default: title = "Blurred Trap Wrapper" int param v_dmj_blurtrapwrapper caption = "Version (DMJ_BlurTrapWrapper)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_blurtrapwrapper < 100 endparam ColorTrap param f_trapshape caption = "Trap Shape" default = ColorTrapWrapper hint = "Sets the trap shape that the convolution filter will be applied to." endparam DMJ_ConvolutionFilter param f_filter caption = "Convolution Filter" default = DMJ_GaussianBlur hint = "Sets the filter that will be applied." endparam int param p_filterreset caption = "Reset Filter" default = 1 enum = "first iteration" "every iteration" hint = "Some convolution filters are position-dependent: the filter's effect varies from one part of the complex plane to another. For this kind of filter, you may choose whether to recompute the filter's effect only at the first iteration or on every iteration." visible = (@f_filter == DMJ_VariableConvolutionFilter) endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Trap Mode classes class DMJ_TrapModeClosest(common.ulb:TrapModeWithThreshold) { ; This is identical to the version in common.ulb and is here for convenience. public: import "common.ulb" func DMJ_TrapModeClosest(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true int j = 0 while (j < 4) m_Distances[j] = 1e20 j = j + 1 endwhile endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) m_Solid = false endif if (pdistance < m_Distances[0]) m_Distances[0] = pdistance m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations endif endfunc default: title = "Closest" int param v_dmj_trapmodeclosest caption = "Version (DMJ_TrapModeClosest)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodeclosest < 100 endparam } class DMJ_TrapModeFarthest(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeFarthest(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold && pdistance >= m_Distances[0]) m_Distances[0] = pdistance m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations m_Solid = false endif endfunc default: title = "Farthest" int param v_dmj_trapmodefarthest caption = "Version (DMJ_TrapModeFarthest)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodefarthest < 100 endparam } class DMJ_TrapModeFirst(common.ulb:TrapModeWithThreshold) { ; This trap mode uses the first trapped point that is within the threshold. ; (This is a clone of TrapModeFirst, here for convenience.) public: import "common.ulb" func DMJ_TrapModeFirst(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold && m_Solid) m_Distances[0] = pdistance m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations m_Solid = false endif endfunc default: title = "First" int param v_dmj_trapmodefirst caption = "Version (DMJ_TrapModeFirst)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodefirst < 100 endparam } class DMJ_TrapModeLast(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeLast(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) m_Distances[0] = pdistance m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations m_Solid = false endif endfunc default: title = "Last" int param v_dmj_trapmodelast caption = "Version (DMJ_TrapModeLast)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodelast < 100 endparam } class DMJ_TrapModeSum(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeSum(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) m_Distances[0] = m_Distances[0] + pdistance m_Textures[0] = m_Textures[0] + ptexture m_UntransformedPoints[0] = m_UntransformedPoints[0] + pz m_TransformedPoints[0] = m_TransformedPoints[0] + pzt m_IterationPoints[0] = m_IterationPoints[0] + 1 m_Solid = false endif endfunc default: title = "Sum" int param v_dmj_trapmodesum caption = "Version (DMJ_TrapModeSum)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodesum < 100 endparam } class DMJ_TrapModeAverage(DMJ_TrapModeSum) { public: import "common.ulb" func DMJ_TrapModeAverage(Generic pparent) DMJ_TrapModeSum.DMJ_TrapModeSum(pparent) endfunc func Result() float ii = 1.0 / m_IterationPoints[0] m_Distances[0] = m_Distances[0] * ii m_Textures[0] = m_Textures[0] * ii m_UntransformedPoints[0] = m_UntransformedPoints[0] * ii m_TransformedPoints[0] = m_TransformedPoints[0] * ii endfunc default: title = "Average" int param v_dmj_trapmodeaverage caption = "Version (DMJ_TrapModeAverage)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodeaverage < 100 endparam } class DMJ_TrapModeProduct(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeProduct(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true int j = 0 while (j < 4) m_Distances[j] = 1.0 m_Textures[j] = 1.0 j = j + 1 endwhile endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) float id = 1.0 / m_Threshold m_Distances[0] = m_Distances[0] * pdistance * id m_Textures[0] = m_Textures[0] * ptexture * id m_UntransformedPoints[0] = m_UntransformedPoints[0] * pz * id m_TransformedPoints[0] = m_TransformedPoints[0] * pzt * id m_IterationPoints[0] = m_IterationPoints[0] * m_Iterations m_Solid = false endif endfunc func Result() m_Distances[0] = abs(m_Distances[0]) ; Note that texture is not forced to positive here. endfunc default: title = "Product" int param v_dmj_trapmodeproduct caption = "Version (DMJ_TrapModeProduct)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodeproduct < 100 endparam } class DMJ_TrapModeSignAverage(common.ulb:TrapMode) { public: import "common.ulb" func DMJ_TrapModeSignAverage(Generic pparent) TrapMode.TrapMode(pparent) endfunc func Init(complex pz) TrapMode.Init(pz) m_Solid = true m_PreviousDistance = 0 endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapMode.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_PreviousDistance) m_Distances[0] = m_Distances[0] + 1 m_Textures[0] = m_Textures[0] + ptexture m_UntransformedPoints[0] = m_UntransformedPoints[0] + pz m_TransformedPoints[0] = m_TransformedPoints[0] + pzt m_IterationPoints[0] = m_IterationPoints[0] + 1 m_Solid = false else m_IterationPoints[0] = m_IterationPoints[0] - 1 endif m_PreviousDistance = pdistance endfunc func Result() float ii = 1.0 / m_Iterations m_Distances[0] = m_Distances[0] * ii m_Textures[0] = m_Textures[0] * ii m_UntransformedPoints[0] = m_UntransformedPoints[0] * ii m_TransformedPoints[0] = m_TransformedPoints[0] * ii endfunc protected: float m_PreviousDistance default: title = "Sign Average" int param v_dmj_trapmodesignaverage caption = "Version (DMJ_TrapModeSignAverage)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodesignaverage < 100 endparam } class DMJ_TrapModeSecondClosest(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeSecondClosest(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true int j = 0 while (j < 4) m_Distances[j] = 1e20 ; Note that textures are left initialized to zero. j = j + 1 endwhile endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) m_Solid = false endif if (pdistance < m_Distances[0]) m_Distances[1] = m_Distances[0] m_Textures[1] = m_Textures[0] m_UntransformedPoints[1] = m_UntransformedPoints[0] m_TransformedPoints[1] = m_TransformedPoints[0] m_IterationPoints[1] = m_IterationPoints[0] m_Distances[0] = pdistance m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations elseif (pdistance < m_Distances[1]) m_Distances[1] = pdistance m_Textures[1] = ptexture m_UntransformedPoints[1] = pz m_TransformedPoints[1] = pzt m_IterationPoints[1] = m_Iterations endif endfunc func Result() m_Distances[0] = abs(m_Distances[0] - m_Distances[1]) m_Textures[0] = m_Textures[0] - m_Textures[1] m_UntransformedPoints[0] = m_UntransformedPoints[0] - m_UntransformedPoints[1] m_TransformedPoints[0] = m_TransformedPoints[0] - m_TransformedPoints[1] m_IterationPoints[0] = abs(m_IterationPoints[0] - m_IterationPoints[1]) endfunc default: title = "Second Closest" int param v_dmj_trapmodesecondclosest caption = "Version (DMJ_TrapModeSecondClosest)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodesecondclosest < 100 endparam } class DMJ_TrapModeTwoClosest(DMJ_TrapModeSecondClosest) { public: import "common.ulb" func DMJ_TrapModeTwoClosest(Generic pparent) DMJ_TrapModeSecondClosest.DMJ_TrapModeSecondClosest(pparent) endfunc func Result() m_Distances[0] = (m_Distances[0] + m_Distances[1]) / 2 m_Textures[0] = (m_Textures[0] + m_Textures[1]) / 2 m_UntransformedPoints[0] = (m_UntransformedPoints[0] + m_UntransformedPoints[1]) / 2 m_TransformedPoints[0] = (m_TransformedPoints[0] + m_TransformedPoints[1]) / 2 m_IterationPoints[0] = round((m_IterationPoints[0] + m_IterationPoints[1]) / 2) endfunc default: title = "Two Closest" int param v_dmj_trapmodetwoclosest caption = "Version (DMJ_TrapModeTwoClosest)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodetwoclosest < 100 endparam } class DMJ_TrapModeSecondFarthest(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeSecondFarthest(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance >= m_Distances[0] && pdistance < m_Threshold) m_Distances[1] = m_Distances[0] m_Textures[1] = m_Textures[0] m_UntransformedPoints[1] = m_UntransformedPoints[0] m_TransformedPoints[1] = m_TransformedPoints[0] m_IterationPoints[1] = m_IterationPoints[0] m_Distances[0] = pdistance m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations m_Solid = false elseif (pdistance >= m_Distances[1] && pdistance < m_Threshold) m_Distances[1] = pdistance m_Textures[1] = ptexture m_UntransformedPoints[1] = pz m_TransformedPoints[1] = pzt m_IterationPoints[1] = m_Iterations m_Solid = false endif endfunc func Result() m_Distances[0] = abs(m_Distances[0] - m_Distances[1]) m_Textures[0] = m_Textures[0] - m_Textures[1] m_UntransformedPoints[0] = m_UntransformedPoints[0] - m_UntransformedPoints[1] m_TransformedPoints[0] = m_TransformedPoints[0] - m_TransformedPoints[1] m_IterationPoints[0] = abs(m_IterationPoints[0] - m_IterationPoints[1]) endfunc default: title = "Second Farthest" int param v_dmj_trapmodesecondfarthest caption = "Version (DMJ_TrapModeSecondFarthest)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodesecondfarthest < 100 endparam } class DMJ_TrapModeTwoFarthest(DMJ_TrapModeSecondFarthest) { public: import "common.ulb" func DMJ_TrapModeTwoFarthest(Generic pparent) DMJ_TrapModeSecondFarthest.DMJ_TrapModeSecondFarthest(pparent) endfunc func Result() m_Distances[0] = (m_Distances[0] + m_Distances[1]) / 2 m_Textures[0] = (m_Textures[0] + m_Textures[1]) / 2 m_UntransformedPoints[0] = (m_UntransformedPoints[0] + m_UntransformedPoints[1]) / 2 m_TransformedPoints[0] = (m_TransformedPoints[0] + m_TransformedPoints[1]) / 2 m_IterationPoints[0] = round((m_IterationPoints[0] + m_IterationPoints[1]) / 2) endfunc default: title = "Two Farthest" int param v_dmj_trapmodetwofarthest caption = "Version (DMJ_TrapModeTwoFarthest)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodetwofarthest < 100 endparam } class DMJ_TrapModeAlternatingAverage(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeAlternatingAverage(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) m_Distances[0] = m_Threshold - abs(m_Distances[0] - pdistance) m_Textures[0] = m_Threshold - abs(m_Textures[0] - ptexture) m_UntransformedPoints[0] = pz - m_UntransformedPoints[0] m_TransformedPoints[0] = pzt - m_TransformedPoints[0] m_IterationPoints[0] = abs(m_Iterations - m_IterationPoints[0]) m_Solid = false endif endfunc default: title = "Alternating Average" int param v_dmj_trapmodealternatingaverage caption = "Version (DMJ_TrapModeAlternatingAverage)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodealternatingaverage < 100 endparam } class DMJ_TrapModeAlternatingAverage2(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeAlternatingAverage2(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) m_Distances[0] = abs(pdistance - m_Threshold + m_Distances[0]) m_Textures[0] = abs(ptexture - m_Threshold + m_Textures[0]) m_UntransformedPoints[0] = pz - m_UntransformedPoints[0] m_TransformedPoints[0] = pzt - m_TransformedPoints[0] m_IterationPoints[0] = abs(m_Iterations - m_IterationPoints[0]) m_Solid = false endif endfunc default: title = "Alternating Average 2" int param v_dmj_trapmodealternatingaverage2 caption = "Version (DMJ_TrapModeAlternatingAverage2)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodealternatingaverage2 < 100 endparam } class DMJ_TrapModeInvertedSum(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeInvertedSum(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true m_TrappedIterations = 0 endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) float d2 = pdistance / m_Threshold m_Distances[0] = m_Distances[0] + pdistance m_Textures[0] = ptexture + (m_Textures[0]-ptexture) * d2 m_UntransformedPoints[0] = pz + (m_UntransformedPoints[0]-pz) * d2 m_TransformedPoints[0] = pzt + (m_TransformedPoints[0]-pzt) * d2 m_IterationPoints[0] = m_Iterations + (m_IterationPoints[0]-m_Iterations) * d2 m_TrappedIterations = m_TrappedIterations + 1 m_Solid = false endif endfunc func Result() m_Distances[0] = m_Threshold * m_TrappedIterations - m_Distances[0] ; Note: texture is computed differently. endfunc protected: int m_TrappedIterations default: title = "Inverted Sum" int param v_dmj_trapmodeinvertedsum caption = "Version (DMJ_TrapModeInvertedSum)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodeinvertedsum < 100 endparam } class DMJ_TrapModeExponentialAverage(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeExponentialAverage(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_Threshold) float expd = exp(-pdistance) m_Distances[0] = m_Distances[0] + expd m_Textures[0] = m_Textures[0] + exp(abs(ptexture)) m_UntransformedPoints[0] = pz + (m_UntransformedPoints[0]-pz) * expd m_TransformedPoints[0] = pzt + (m_TransformedPoints[0]-pzt) * expd m_IterationPoints[0] = m_Iterations + (m_IterationPoints[0]-m_Iterations) * expd m_Solid = false endif endfunc default: title = "Exponential Average" int param v_dmj_trapmodeexponentialaverage caption = "Version (DMJ_TrapModeExponentialAverage)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodeexponentialaverage < 100 endparam } class DMJ_TrapModeChangeAverage(common.ulb:TrapMode) { public: import "common.ulb" func DMJ_TrapModeChangeAverage(Generic pparent) TrapMode.TrapMode(pparent) endfunc func Init(complex pz) TrapMode.Init(pz) m_Solid = true m_PreviousDistance = 0 m_PreviousTexture = 0 endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapMode.Iterate(pz, pzt, pdistance, ptexture) if (pdistance < m_PreviousDistance) m_Distances[0] = m_Distances[0] + m_PreviousDistance-pdistance m_Textures[0] = m_Textures[0] + m_PreviousTexture-ptexture m_UntransformedPoints[0] = m_UntransformedPoints[0] + pz m_TransformedPoints[0] = m_TransformedPoints[0] + pzt m_IterationPoints[0] = m_IterationPoints[0] + 1 m_Solid = false endif m_PreviousDistance = pdistance endfunc protected: float m_PreviousDistance float m_PreviousTexture default: title = "Change Average" int param v_dmj_trapmodechangeaverage caption = "Version (DMJ_TrapModeChangeAverage)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodechangeaverage < 100 endparam } class DMJ_TrapModeStacked(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeStacked(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true int j = 0 while (j < 4) m_Distances[j] = 1e20 ; Note: leave textures initialized at zero. j = j + 1 endwhile endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) float d2 = 0 if (@p_trapstackorder == 0) d2 = m_Distances[0] - (m_Iterations-m_IterationPoints[0]) * @p_trapstackdistance * m_Threshold else d2 = m_Distances[0] + (m_Iterations-m_IterationPoints[0]) * @p_trapstackdistance * m_Threshold endif if (pdistance < d2 && pdistance < m_Threshold) m_Distances[0] = pdistance m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations m_Solid = false endif endfunc default: title = "Stacked" int param v_dmj_trapmodestacked caption = "Version (DMJ_TrapModeStacked)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodestacked < 100 endparam int param p_trapstackorder caption = "Stack Order" default = 0 enum = "top-down" "bottom-up" hint = "Sets the order in which traps are stacked. Top-down places earlier iterations above later iterations; bottom-up places later iterations above earlier iterations." endparam float param p_trapstackdistance caption = "Stack Distance" default = 0.25 min = 0.0 hint = "Amount trap distance varies with each iteration. Use this to adjust how close together traps appear and intertwine with each other." endparam } class DMJ_TrapModeTrapOnly(common.ulb:TrapModeWithThreshold) { public: import "common.ulb" func DMJ_TrapModeTrapOnly(Generic pparent) TrapModeWithThreshold.TrapModeWithThreshold(pparent) endfunc func Init(complex pz) TrapModeWithThreshold.Init(pz) m_Solid = true endfunc func Iterate(complex pz, complex pzt, float pdistance, float ptexture) TrapModeWithThreshold.Iterate(pz, pzt, pdistance, ptexture) if (m_Iterations == 1) m_Distances[0] = pdistance/m_Threshold m_Textures[0] = ptexture m_UntransformedPoints[0] = pz m_TransformedPoints[0] = pzt m_IterationPoints[0] = m_Iterations if (pdistance < m_Threshold) m_Solid = false endif endif endfunc default: title = "Trap Only" int param v_dmj_trapmodetraponly caption = "Version (DMJ_TrapModeTrapOnly)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapmodetraponly < 100 endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Trap Coloring classes class DMJ_TrapColoringDistance(common.ulb:TrapColoring) { ; This is identical to the version in common.ulb and is here for convenience. public: import "common.ulb" func DMJ_TrapColoringDistance(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) if (@p_autoscale) return (ptrapmode.GetDistance(0) + ptrapmode.GetTexture(0)) / ptrapmode.GetThreshold() else return (ptrapmode.GetDistance(0) + ptrapmode.GetTexture(0)) endif endfunc default: title = "Distance" int param v_dmj_trapcoloringdistance caption = "Version (DMJ_TrapColoringDistance)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringdistance < 100 endparam bool param p_autoscale caption = "Auto-scale Coloring" default = true hint = "If checked, coloring will be automatically scaled to match the trap threshold." endparam } class DMJ_TrapColoringMagnitude(common.ulb:TrapColoring) { public: import "common.ulb" func DMJ_TrapColoringMagnitude(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) if (@p_untransformed) return cabs(ptrapmode.GetUntransformedPoint(0)) + ptrapmode.GetTexture(0) else return cabs(ptrapmode.GetTransformedPoint(0)) + ptrapmode.GetTexture(0) endif endfunc default: title = "Magnitude" int param v_dmj_trapcoloringmagnitude caption = "Version (DMJ_TrapColoringMagnitude)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringmagnitude < 100 endparam bool param p_untransformed caption = "Use value before transformation" default = false hint = "If checked, coloring will be based on the value prior to any trap position transformations." endparam } class DMJ_TrapColoringReal(common.ulb:TrapColoring) { public: import "common.ulb" func DMJ_TrapColoringReal(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) if (@p_untransformed) return abs(real(ptrapmode.GetUntransformedPoint(0))) + ptrapmode.GetTexture(0) else return abs(real(ptrapmode.GetTransformedPoint(0))) + ptrapmode.GetTexture(0) endif endfunc default: title = "Real" int param v_dmj_trapcoloringreal caption = "Version (DMJ_TrapColoringReal)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringreal < 100 endparam bool param p_untransformed caption = "Use value before transformation" default = false hint = "If checked, coloring will be based on the value prior to any trap position transformations." endparam } class DMJ_TrapColoringImaginary(common.ulb:TrapColoring) { public: import "common.ulb" func DMJ_TrapColoringImaginary(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) if (@p_untransformed) return abs(imag(ptrapmode.GetUntransformedPoint(0))) + ptrapmode.GetTexture(0) else return abs(imag(ptrapmode.GetTransformedPoint(0))) + ptrapmode.GetTexture(0) endif endfunc default: title = "Imaginary" int param v_dmj_trapcoloringimaginary caption = "Version (DMJ_TrapColoringImaginary)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringimaginary < 100 endparam bool param p_untransformed caption = "Use value before transformation" default = false hint = "If checked, coloring will be based on the value prior to any trap position transformations." endparam } class DMJ_TrapColoringAngleToTrap(common.ulb:TrapColoring) { public: import "common.ulb" func DMJ_TrapColoringAngleToTrap(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) float d = atan2(ptrapmode.GetTransformedPoint(0)) if (d < 0) d = d + #pi*2 endif return d / (#pi * 2) + ptrapmode.GetTexture(0) endfunc default: title = "Angle to Trap" int param v_dmj_trapcoloringangletotrap caption = "Version (DMJ_TrapColoringAngleToTrap)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringangletotrap < 100 endparam } class DMJ_TrapColoringAngleToOrigin(common.ulb:TrapColoring) { public: import "common.ulb" func DMJ_TrapColoringAngleToOrigin(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) float d = atan2(ptrapmode.GetUntransformedPoint(0)) if (d < 0) d = d + #pi*2 endif return d / (#pi * 2) + ptrapmode.GetTexture(0) endfunc default: title = "Angle to Origin" int param v_dmj_trapcoloringangletoorigin caption = "Version (DMJ_TrapColoringAngleToOrigin)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringangletoorigin < 100 endparam } class DMJ_TrapColoringIteration(common.ulb:TrapColoring) { public: import "common.ulb" func DMJ_TrapColoringIteration(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) return ptrapmode.GetIteration(0) * 0.01 + ptrapmode.GetTexture(0) endfunc default: title = "Iteration" int param v_dmj_trapcoloringiteration caption = "Version (DMJ_TrapColoringIteration)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringiteration < 100 endparam } class DMJ_TrapColoringTexture(common.ulb:TrapColoring) { public: import "common.ulb" func DMJ_TrapColoringTexture(Generic pparent) TrapColoring.TrapColoring(pparent) endfunc float func Result(TrapMode ptrapmode) return ptrapmode.GetTexture(0) endfunc default: title = "Texture Only" int param v_dmj_trapcoloringtexture caption = "Version (DMJ_TrapColoringTexture)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapcoloringtexture < 100 endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Transfer classes class DMJ_TrapDiameter(common.ulb:Transfer) { ; This transfer will make a trap shape have a diameter. public: import "common.ulb" func DMJ_TrapDiameter(Generic pparent) Transfer.Transfer(pparent) endfunc float func Iterate(float pr) return abs(@p_diameter*0.5 - pr) endfunc default: title = "Trap Diameter" int param v_dmj_trapdiameter caption = "Version (DMJ_TrapDiameter)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapdiameter < 100 endparam float param p_diameter caption = "Diameter" default = 0.5 hint = "This is the diameter value used to create the hollow shape." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Color Transfer classes class DMJ_Colorize(common.ulb:ColorTransfer) { ; This applies a variety of color altering effects. public: import "common.ulb" func DMJ_Colorize(Generic pparent) ColorTransfer.ColorTransfer(pparent) if (@p_effecttype == 2) m_Blend = new @f_mergemode(this) endif if (@p_effecttype == 5) m_Gradient = new @f_gradient(this) endif endfunc color func Iterate(color pcolor) color c = pcolor ; our original color color vc = rgba(red(c),green(c),blue(c),1.0) ; our original color, stripped of alpha float vg = 0.0 ; grey value if (@p_effecttype == 0 || @p_effecttype == 5) ; channel extract or gradient if (@p_sourcechannel == 0) ; red vg = red(c) vc = rgba(vg,0,0,1) elseif (@p_sourcechannel == 1) ; green vg = green(c) vc = rgba(0,vg,0,1) elseif (@p_sourcechannel == 2) ; blue vg = blue(c) vc = rgba(0,0,vg,1) elseif (@p_sourcechannel == 3) ; cyan vg = red(c) vc = rgba(vg,1,1,1) elseif (@p_sourcechannel == 4) ; magenta vg = green(c) vc = rgba(1,vg,1,1) elseif (@p_sourcechannel == 5) ; yellow vg = blue(c) vc = rgba(1,1,vg,1) elseif (@p_sourcechannel == 6) ; grey vg = red(c)*0.3 + green(c)*0.59 + blue(c)*0.11 vc = rgba(vg,vg,vg,1) elseif (@p_sourcechannel == 7) ; hue vg = hue(c) vc = hsla(vg,1,0.5,1) elseif (@p_sourcechannel == 8) ; saturation (appears as red) vg = sat(c) vc = hsla(0,vg,0.5,1) elseif (@p_sourcechannel == 9) ; lightness (not the same as grey!) vg = lum(c) vc = hsla(0,0,vg,1) elseif (@p_sourcechannel == 10) ; value (not the same as lightness or grey!) vg = 0 ; **** unimplemented vc = hsla(0,0,vg,1) elseif (@p_sourcechannel == 11) ; Y vg = 0 ; **** unimplemented vc = hsla(0,0,vg,1) elseif (@p_sourcechannel == 12) ; Cb vg = 0 ; **** unimplemented vc = hsla(0,0,vg,1) elseif (@p_sourcechannel == 13) ; Cr vg = 0 ; **** unimplemented vc = hsla(0,0,vg,1) elseif (@p_sourcechannel == 14) ; U vg = 0 ; **** unimplemented vc = hsla(0,0,vg,1) elseif (@p_sourcechannel == 15) ; V vg = 0 ; **** unimplemented vc = hsla(0,0,vg,1) elseif (@p_sourcechannel == 16) ; alpha (appears as grey) vg = alpha(c) vc = rgba(vg,vg,vg,1) endif if (@p_effecttype == 0) ; channel extract if (@p_targetchannel == 1) ; extract as grey vc = rgba(vg,vg,vg,1) elseif (@p_targetchannel == 2) ; extract as alpha (color) vc = rgba(red(vc),green(vc),blue(vc),vg) elseif (@p_targetchannel == 3) ; extract as alpha (black) vc = rgba(0,0,0,vg) elseif (@p_targetchannel == 4) ; extract as alpha (white) vc = rgba(1,1,1,vg) elseif (@p_targetchannel == 5) ; extract as alpha (grey) vc = rgba(vg,vg,vg,vg) endif elseif (@p_effecttype == 5) ; gradient vc = m_Gradient.getColor(vg) endif elseif (@p_effecttype == 1) ; color filter vc = rgba(0,0,0,1) ; **** unimplemented elseif (@p_effecttype == 2) ; color merge if (@p_mergeorder == 0) vc = m_Blend.FullMerge(@p_mergecolor, c, @p_mergeopacity) else vc = m_Blend.FullMerge(c, @p_mergecolor, @p_mergeopacity) endif elseif (@p_effecttype == 3) ; gamma correction if (@p_gammaoverall) vg = red(c)*0.3 + green(c)*0.59 + blue(c)*0.11 ; compute grey value float vgc = vg if (@p_gammareverse) vgc = vg^(1/@p_gamma) else vgc = vg^@p_gamma endif if (vg != 0) vg = vgc/vg ; brightness ratio endif vc = rgba(red(c)*vg,green(c)*vg,blue(c)*vg,alpha(c)*vg) else if (@p_gammareverse) vc = rgba(red(c)^(1/@p_gamma),green(c)^(1/@p_gamma),blue(c)^(1/@p_gamma),alpha(c)^(1/@p_gamma)) else vc = rgba(red(c)^@p_gamma,green(c)^@p_gamma,blue(c)^@p_gamma,alpha(c)^@p_gamma) endif endif elseif (@p_effecttype == 4) ; solarization float r = Solarize(@p_solarizemode, @p_solarizethreshold, red(c)) float g = Solarize(@p_solarizemode, @p_solarizethreshold, green(c)) float b = Solarize(@p_solarizemode, @p_solarizethreshold, blue(c)) float a = Solarize(@p_solarizemode, @p_solarizethreshold, alpha(c)) vc = rgba(r,g,b,a) ; type 5 is handled with type 0 above elseif (@p_effecttype == 6) ; threshold if (@p_thresholdgrey) vg = red(c)*0.3 + green(c)*0.59 + blue(c)*0.11 ; compute grey value vg = Threshold(@p_threshold, vg) vc = rgba(vg,vg,vg,vg) else float r = Threshold(@p_threshold, red(c)) float g = Threshold(@p_threshold, green(c)) float b = Threshold(@p_threshold, blue(c)) float a = Threshold(@p_threshold, alpha(c)) vc = rgba(r,g,b,a) endif elseif (@p_effecttype == 7) ; posterize float r = floor(red(c) * @p_posterizelevels) / @p_posterizelevels float g = floor(green(c) * @p_posterizelevels) / @p_posterizelevels float b = floor(blue(c) * @p_posterizelevels) / @p_posterizelevels float a = floor(alpha(c) * @p_posterizelevels) / @p_posterizelevels vc = rgba(r,g,b,a) endif if (@p_preservealpha) vc = rgba(red(vc),green(vc),blue(vc),alpha(c)) endif return vc endfunc protected: GradientWrapper m_Gradient ColorMerge m_Blend float func Solarize(const int pmethod, const float pthreshold, float pvalue) if (pmethod == 0) ; stretch to fit if (pvalue < pthreshold) if (pthreshold == 0) pvalue = -1e10 else pvalue = pvalue/pthreshold endif else if (pthreshold == 1) pvalue = -1e10 else pvalue = (1-pvalue)/(1-pthreshold) endif endif elseif (pmethod == 1) ; equal slope if (pthreshold == 0) pvalue = -1e10 elseif (pvalue < pthreshold) pvalue = pvalue/pthreshold else pvalue = 2 - pvalue/pthreshold endif elseif (pmethod == 2) ; repeating sawtooth if (pthreshold == 0) pvalue = -1e10 else pvalue = pvalue % (pthreshold*2) if (pvalue < pthreshold) pvalue = pvalue/pthreshold else pvalue = 2 - pvalue/pthreshold endif endif endif return pvalue endfunc float func Threshold(float pthreshold, float pvalue) if (pvalue < pthreshold) return 0 else return 1 endif return pvalue endfunc default: title = "Colorize" int param v_dmj_colorize caption = "Version (DMJ_Colorize)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_colorize < 100 endparam param p_effecttype caption = "Coloring Type" default = 0 enum = "channel extract" "color filter" "color merge" "gamma adjust" "solarize" "apply gradient" "threshold" "posterize" hint = "Selects the type of colorizing effect you want to use." endparam param p_sourcechannel caption = "Channel" default = 6 enum = "red" "green" "blue" "cyan" "magenta" "yellow" "grey" "hue" "saturation" "lightness" "value" "Y" "Cb" "Cr" "U" "V" "alpha" hint = "Selects the channel data to extract." visible = (@p_effecttype == 0 || @p_effecttype == 5) endparam param p_targetchannel caption = "Extract As" default = 0 enum = "color" "grey" "alpha (color)" "alpha (black)" "alpha (white)" "alpha (grey)" hint = "Selects how the extracted data should be expressed. 'Color' will try to show a native color representation. 'Grey' will show the data as a grey value, and 'alpha' will show the data as transparency values." visible = (@p_effecttype == 0) endparam color param p_mergecolor caption = "Merge Color" default = rgba(1,0,0.5,1) hint = "Sets the color to merge with." visible = (@p_effecttype == 2) endparam ColorMerge param f_mergemode caption = "Merge Mode" default = DefaultColorMerge hint = "Sets the method used to merge input colors with." visible = (@p_effecttype == 2) endparam float param p_mergeopacity caption = "Merge Opacity" default = 0.5 hint = "Sets the opacity of the merge. 0 will give no change to colors; 1 will give full effect of the merge mode." visible = (@p_effecttype == 2) endparam int param p_mergeorder caption = "Merge Order" default = 1 enum = "input onto fixed" "fixed onto input" hint = "Sets the order in which colors are merged. The fixed color is the one you choose here; the input color is provided by the formula you plug this class into." visible = (@p_effecttype == 2) endparam float param p_gamma caption = "Gamma Value" default = 2.2 hint = "Sets the gamma correction value. Gamma is used to correct midtones in images that occur due to non-linear brightness." visible = (@p_effecttype == 3) endparam bool param p_gammareverse caption = "Reverse Correction" default = false hint = "If checked, the reverse correction will be applied. This will allow you to easily 'undo' a previously-applied correction." visible = (@p_effecttype == 3) endparam bool param p_gammaoverall caption = "Preserve Colors" default = false hint = "EXPERIMENTAL: Gamma correction is properly applied to each color channel individually. However, when this is done, colors may shift towards primary colors. If you are using gamma correction as a way to adjust midtones in your image (rather than to actually correct for non-linear color) you may want to enable this option, which will try to preserve the colors while applying gamma correction based on the overall brightness of each color." visible = (@p_effecttype == 3) endparam float param p_solarizethreshold caption = "Solarize Threshold" default = 0.5 hint = "Sets the cutoff level for the solarize effect. Values below the threshold will be increased; values at the threshold will be set to the maximum; values above the threshold will decrease towards zero." visible = (@p_effecttype == 4) endparam int param p_solarizemode caption = "Solarize Mode" default = 0 enum = "stretch to fit" "equal slope" "repeating sawtooth" hint = "Sets the style of solarization." visible = (@p_effecttype == 4) endparam GradientWrapper param f_gradient caption = "Gradient" default = DefaultGradient hint = "Selects the gradient class to use for providing the gradient colors." visible = (@p_effecttype == 5) endparam float param p_threshold caption = "Threshold" default = 0.5 hint = "Sets the cutoff level. Everything above this value will be set to 1; everything below it will be set to zero." visible = (@p_effecttype == 6) endparam bool param p_thresholdgrey caption = "Black and White" default = false hint = "If checked, thresholding will be done based on grey value and black or white will be the result." visible = (@p_effecttype == 6) endparam int param p_posterizelevels caption = "Posterize Levels" default = 6 min = 2 hint = "Sets the number of resulting color levels." visible = (@p_effecttype == 7) endparam bool param p_preservealpha caption = "Preserve Alpha" default = true hint = "If checked, the original transparency data from the image will be preserved regardless of the other changes." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Image classes class DMJ_ImageColorize(common.ulb:ImageWrapper) { ; This convenience class bundles an ImageWrapper with a Colorize. public: import "common.ulb" func DMJ_ImageColorize(Generic pparent) ImageWrapper.ImageWrapper(pparent) m_ImportedImage = new @f_importedimage(this) m_ColorTransfer = new @f_colortransfer(this) endfunc color func getColor(complex pz) color c = m_ImportedImage.getColor(pz) m_ColorTransfer.Init(pz) return m_ColorTransfer.Iterate(c) endfunc color func getPixel(int px, int py) color c = m_ImportedImage.getPixel(px,py) m_ColorTransfer.Init(px+flip(py)) return m_ColorTransfer.Iterate(c) endfunc protected: ImageWrapper m_ImportedImage ColorTransfer m_ColorTransfer default: title = "Image Colorize" int param v_dmj_imagecolorize caption = "Version (DMJ_ImageColorize)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_imagecolorize < 100 endparam ImageWrapper param f_importedimage caption = "Image Object" default = ImageImport hint = "This is the image object to apply color effects to." endparam ColorTransfer param f_colortransfer caption = "Color Transfer" default = DMJ_Colorize hint = "This is the color effect to apply." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Transformation classes class DMJ_Clipping(common.ulb:UserTransform) { ; Basic clipping framework transform. public: import "common.ulb" func DMJ_Clipping(Generic pparent) UserTransform.UserTransform(pparent) m_ScreenRelative = new @f_screenrelative(this) m_Handles = new @f_handles(this) m_Handles.SetScreenRelative(m_ScreenRelative.GetScreenRelative()) m_ClipShape = new @f_clipshape(this) m_ClipShape.SetHandles(m_Handles) endfunc func Init(complex pz) UserTransform.Init(pz) m_ScreenRelative.Init(pz) m_Handles.Init(pz) m_ClipShape.Init(pz) endfunc complex func Iterate(complex pz) UserTransform.Iterate(pz) bool inshape ; flag for recording whether we're inside our shape bool inhandle ; flag for recording whether we're inside a handle complex cz = pz ; begin with pixel value ; apply screen-relative cz = m_ScreenRelative.Iterate(cz) ; apply offset cz = cz - ((0,1) ^ (@p_offsetangle / 90)) * @p_offsetamount ; see if point is in clipping shape cz = m_ClipShape.Iterate(cz) inshape = m_ClipShape.IsSolid() ; check handles cz = m_Handles.Iterate(cz) inhandle = m_Handles.IsSolid() ; merge results m_Solid = (inshape && !inhandle) || (!inshape && inhandle) if (@p_insideoutside == 1) m_Solid = !m_Solid endif return pz ; return original value unchanged endfunc protected: DMJ_ScreenRelative m_ScreenRelative Handles m_Handles ClipShape m_ClipShape default: title = "Über Clipping" int param v_dmj_clipping caption = "Version (DMJ_Clipping)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_clipping < 100 endparam param p_insideoutside caption = "Clipping Region" default = 1 enum = "inside" "outside" hint = "Determines whether points inside the clipping shape or outside the clipping shape will be solid-colored." endparam DMJ_ScreenRelative param f_screenrelative caption = "Screen Relative" selectable = false endparam float param p_offsetamount caption = "Offset Amount" default = 0.0 hint = "If you need to shift your entire clipping region without altering its shape, you can specify an amount and a direction to offset it. Keep in mind that if you have an offset enabled, it will still be in effect while you use the eyedropper or explorer tools to select points." endparam float param p_offsetangle caption = "Offset Angle" default = 0.0 visible = (@p_offsetamount != 0.0) hint = "Indicates the angle of the offset. Note that, if your image is rotated, and you're not using screen-relative clipping, offset angles will be rotated too. Normally an angle of 0 is to the right and 90 is up, if your image is not rotated." endparam float param p_rotation caption = "Rotation" default = 0.0 hint = "Rotates the clipping shape. Note that, if you're using screen-relative coordinates and your image is not square, or if you've squashed or stretched the zoombox, the clipping shape will deform as you rotate it. Angles are in degrees, counter-clockwise." endparam param p_rotationtype caption = "Rotation Center" default = 0 enum = "object center" "screen center" "specific point" visible = (@p_rotation != 0.0) hint = "Specifies around which point the object should be rotated. 'Object center' will try to determine a center point for the object, but for some complex object types this may not be the point you expect. 'Screen center' will always use the screen center, but this will cause your clipping shape to change if you zoom or pan your image. 'Specific point' will let you set an exact rotation center for your shape." endparam complex param p_rotationcenter caption = "Rotation Center" default = #center visible = (@p_rotation != 0.0 && @p_rotationtype == 2) hint = "Sets the center of rotation for your clip shape." endparam heading caption = "Handle Options" expanded = false endheading Handles param f_handles caption = "Handle Options" selectable = false endparam heading caption = "Clip Shape Options" expanded = true endheading ClipShape param f_clipshape caption = "Clipping Shape" default = DMJ_ClipShapeRectangle hint = "Selects the shape of the clipping region." endparam } class DMJ_Distort(common.ulb:UserTransform) { ; Distortion (especially fBm) transformation. ; (Roughly ported from dmj.uxf:dmj-fBm-Glass1 and dmj3.uxf:dmj3-fBm-Distort) public: import "common.ulb" func DMJ_Distort(Generic pparent) UserTransform.UserTransform(pparent) m_DistortionAngle = (0,1) ^ (@p_distortionangle / 90.0) if (@p_fixed > 0) m_Transform = new @f_transform(this) m_TrapShape = new @f_trapshape(this) m_Transfer = new @f_transfer(this) endif if (@p_fixed2 > 0) m_Transform2 = new @f_transform2(this) m_TrapShape2 = new @f_trapshape2(this) m_Transfer2 = new @f_transfer2(this) endif endfunc func Init(complex pz) UserTransform.Init(pz) if (@p_fixed > 0) m_Transform.Init(pz) m_TrapShape.Init(pz) m_Transfer.Init(pz) endif if (@p_fixed2 > 0) m_Transform2.Init(pz) m_TrapShape2.Init(pz) m_Transfer2.Init(pz) endif endfunc complex func Iterate(complex pz) UserTransform.Iterate(pz) complex zt float d float d2 ; get distortion basis if (@p_fixed > 0) zt = m_Transform.Iterate(pz) d = m_TrapShape.Iterate(zt) d = m_Transfer.Iterate(d) else d = 1.0 endif ; determine distortion angle complex v = m_DistortionAngle ; by default, assume linear distortion; use regular vector if (@p_fixed2 > 0) zt = m_Transform2.Iterate(pz) d2 = m_TrapShape2.Iterate(zt) d2 = m_Transfer2.Iterate(d2) d2 = d2 * @p_distortionstrength2 v = v * ((0,1) ^ (d2 / 90.0)) endif if (@p_distortionstyle == 0) ; radial distortion v = (pz - @p_distortioncenter) / cabs(pz - @p_distortioncenter) * v ; rotate to correct angle endif ; distort pz = pz + v * d * @p_distortionstrength return pz endfunc protected: complex m_DistortionAngle UserTransform m_Transform TrapShape m_TrapShape Transfer m_Transfer complex m_DistortionAngle2 UserTransform m_Transform2 TrapShape m_TrapShape2 Transfer m_Transfer2 default: title = "Distort (fBm and more)" int param v_dmj_distort caption = "Version (DMJ_Distort)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_distort < 100 endparam int param p_distortionstyle caption = "Distortion Style" default = 0 enum = "radial" "linear" hint = "This selects whether the distortion will be focused around a single point, or directed along a line." endparam float param p_distortionangle caption = "Distortion Angle" default = 0.0 hint = "This is the angle to rotate the distortion." endparam complex param p_distortioncenter caption = "Distortion Center" default = #center hint = "Sets the center of distortion." endparam heading caption = "Distortion Strength" expanded = true endheading int param p_fixed caption = "Method" default = 1 enum = "fixed value" "object" hint = "Chooses whether distortion strength should be a fixed value or be calculated by an object." endparam float param p_distortionstrength caption = "Distortion Strength" default = 0.5/#magn hint = "This is the amount of pushing or pulling on the image to do. When the strength is calculated by an object, this acts as a multiplier, controlling the overall amount of distortion." endparam UserTransform param f_transform caption = "Distortion Transform" default = NullTransform hint = "This will apply a transformation to coordinates used to generate the distortion texture, but this transformation will not affect the underlying image. You can use this to warp only the distortion texture." visible = (@p_fixed == 1) endparam TrapShape param f_trapshape caption = "Distortion Texture" default = DMJ_TrapShapeFBM hint = "This is the distortion texture. You can use any trap shape here, but the most effective distortions for texturing will likely come from noise and fBm trap shapes." visible = (@p_fixed == 1) endparam Transfer param f_transfer caption = "Distortion Transfer" default = TrapTransfer hint = "This allows you to modify the output of the distortion texture before it is used in the distortion algorithm." expanded = false visible = (@p_fixed == 1) endparam heading caption = "Distortion Angle" expanded = true endheading int param p_fixed2 caption = "Method" default = 0 enum = "fixed value" "object" hint = "Chooses whether distortion angle should be a fixed value or be calculated by an object." endparam float param p_distortionstrength2 caption = "Distortion Angle Multiplier" default = 90.0 hint = "This is multiplied with the object calculation to give a distortion angle in degrees." visible = (@p_fixed2 == 1) endparam UserTransform param f_transform2 caption = "Distortion Transform" default = NullTransform hint = "This will apply a transformation to coordinates used to generate the distortion texture, but this transformation will not affect the underlying image. You can use this to warp only the distortion texture." visible = (@p_fixed2 == 1) endparam TrapShape param f_trapshape2 caption = "Distortion Texture" default = DMJ_TrapShapeFBM hint = "This is the distortion texture. You can use any trap shape here, but the most effective distortions for texturing will likely come from noise and fBm trap shapes." visible = (@p_fixed2 == 1) endparam Transfer param f_transfer2 caption = "Distortion Transfer" default = TrapTransfer hint = "This allows you to modify the output of the distortion texture before it is used in the distortion algorithm." expanded = false visible = (@p_fixed2 == 1) endparam } class DMJ_FastMosaic(common.ulb:UserTransform) { ; Mosaic transformation. ; (Ported from dmj3.uxf:dmj3-FastMosaic) public: import "common.ulb" ; $define debug func DMJ_FastMosaic(Generic pparent) UserTransform.UserTransform(pparent) ; create objects m_Formula = new @f_formula(this) m_Transform = new @f_transform(this) m_Points = new ComplexArray(@p_pointcount) m_Closest = new DMJ_FastClosestPoint() m_FoundIndices = new IntegerArray(3) m_FoundDistances = new FloatArray(3) m_FoundPoints = new ComplexArray(3) ; compute first point complex zg = m_Formula.Init(0) m_Transform.Init(0) zg = m_Transform.Iterate(zg) ; compute remaining points int j = 0 while (j < @p_pointcount) m_Points.m_Elements[j] = zg zg = m_Formula.Iterate(zg) zg = m_Transform.Iterate(zg) j = j + 1 endwhile ; prep point index Array m_Dummy = 0 m_Closest.SetPointData(m_Points, m_Dummy) endfunc complex func Iterate(complex pz) if (@p_edgewidth > 0) $ifdef debug int testx = 400 int testy = 400 $endif ; get an ordered list of close points int k = m_Closest.GetClosestPointSet(pz, 3, m_FoundIndices, m_FoundDistances, m_FoundPoints) $ifdef debug if (#x == testx && #y == testy) print("GetClosestPointSet results:") print("1: ", m_FoundIndices.m_Elements[0], " ", m_FoundDistances.m_Elements[0], " ", m_FoundPoints.m_Elements[0]) print("2: ", m_FoundIndices.m_Elements[1], " ", m_FoundDistances.m_Elements[1], " ", m_FoundPoints.m_Elements[1]) print("3: ", m_FoundIndices.m_Elements[2], " ", m_FoundDistances.m_Elements[2], " ", m_FoundPoints.m_Elements[2]) endif $endif ; get distances to closest and second-closest edge complex q1 = (m_FoundPoints.m_Elements[1]-m_FoundPoints.m_Elements[0]) / \ cabs(m_FoundPoints.m_Elements[1]-m_FoundPoints.m_Elements[0]) complex q2 = (m_FoundPoints.m_Elements[2]-m_FoundPoints.m_Elements[0]) / \ cabs(m_FoundPoints.m_Elements[2]-m_FoundPoints.m_Elements[0]) float d1 = real((pz-(m_FoundPoints.m_Elements[1]+m_FoundPoints.m_Elements[0])*0.5) * conj(q1)) float d2 = real((pz-(m_FoundPoints.m_Elements[2]+m_FoundPoints.m_Elements[0])*0.5) * conj(q2)) ; determine if this is an edge point or a tile point bool isedge = abs(d1) < @p_edgewidth || abs(d2) < @p_edgewidth ; check for solid color use if (@p_soliduse == 1) if (!isedge) m_Solid = true endif elseif (@p_soliduse == 2) if (isedge) m_Solid = true endif endif if (!m_Solid) if (@p_edgestretchmode > 0 && @p_edgestretch != 0.0 && isedge) ; apply edge stretching float d = d2 complex q = q2 if (abs(d1) < @p_edgewidth && abs(d1) < abs(d2)) d = d1 q = q1 endif if (@p_edgestretchmode == 2) pz = pz - q * @p_edgestretch * d elseif (@p_edgestretchmode == 1) pz = pz + q * @p_edgestretch * (abs(d) - @p_edgewidth) endif else ; apply ordinary mosaic complex c = m_FoundPoints.m_Elements[0] pz = c + \ (@p_tileforcecenter - c) * @p_tileforce + \ (pz - c) * @p_tilestretch endif endif return pz else int k = m_Closest.GetClosestPoint(pz) complex c = m_Points.m_Elements[m_Closest.m_Order.m_Elements[k]] pz = c + \ (@p_tileforcecenter - c) * @p_tileforce + \ (pz - c) * @p_tilestretch return pz endif endfunc protected: Formula m_Formula Transform m_Transform ComplexArray m_Points DMJ_FastClosestPoint m_Closest IntegerArray m_FoundIndices FloatArray m_FoundDistances ComplexArray m_FoundPoints default: title = "Mosaic" int param v_dmj_fastmosaic caption = "Version (DMJ_FastMosaic)" default = 101 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_fastmosaic < 100 endparam heading caption = "Point Creation" endheading int param p_pointcount caption = "Tile Count" default = 1000 hint = "Sets the number of mosaic tiles. More tiles will take longer to compute but will allow smaller tiles." endparam Formula param f_formula caption = "Point Creator" default = DMJ_FormulaGenerators hint = "This selects the formula you will use to create the points that are used as the centers of the mosaic tiles. If you use a random formula (the default) the tiles will be distributed evenly. You can use other formulas to produce different groupings of tiles." endparam UserTransform param f_transform caption = "Point Transform" default = NullTransform hint = "After points have been generated, you may choose to have them transformed in some way before being used." endparam heading caption = "Edge & Tile Effects" endheading float param p_edgewidth caption = "Edge Width" default = 0.0 min = 0.0 hint = "Sets the width of any edges. Note that if you set this value to 0.0, all edge effects are disabled." endparam int param p_soliduse caption = "Solid Color Use" default = 0 enum = "none" "tiles" "edges" visible = @p_edgewidth > 0.0 hint = "Sets which areas will be flagged for solid colors. Note that using solid colors will disable some edge and tile effects." endparam int param p_edgestretchmode caption = "Edge Stretching" default = 0 enum = "none" "from inside" "from outside" visible = @p_edgewidth > 0.0 && @p_soliduse != 2 hint = "Chooses how edge stretching should be used. This will allow the edge to be 'stretched' from either the inside edge (nearest the tile center) or the outside edge (between the tiles)." endparam float param p_edgestretch caption = " Stretch Amount" default = 0.0 visible = @p_edgewidth > 0.0 && @p_soliduse != 2 && @p_edgestretchmode > 0 hint = "Sets the amount of stretching the edge will have. Use 0.0 for no effect and 1.0 for full effect (other values may provide interesting results)." endparam float param p_tilestretch caption = "Tile Scale" default = 0.0 visible = @p_soliduse != 1 hint = "Sets tile scale, the zoom applied relative to the tile focus point. Use a value of 0.0 to get solid tiles (all tile points map to the focus point). Use a value of 1.0 to leave tiles unchanged (useful if you have enabled edge effects). Use other values for different effects." endparam float param p_tileforce caption = "Tile Shift" default = 0.0 visible = @p_soliduse != 1 hint = "Forces tiles to appear centered at a different location, rather than each tile's focus point. Use a value of 0.0 to disable this effect; use a value of 1.0 for full effect (all tiles appear centered at the designated point). Use values in between to partially apply the effect." endparam complex param p_tileforcecenter caption = " Shift Point" default = (0,0) visible = @p_soliduse != 1 && @p_tileforce != 0.0 hint = "This is the point all tiles will be shifted towards." endparam } class DMJ_FollowCurve(common.ulb:UserTransform) { ; Distort to follow an arbitrary curve. (BETA, SUBJECT TO CHANGE) ; You can use this transform to make a fractal shape follow a ; curve that you define. public: import "common.ulb" ; $define debug func DMJ_FollowCurve(Generic pparent) UserTransform.UserTransform(pparent) m_PolyCurve = new DMJ_PolyCurve() m_Selector = new @f_selector(pparent) m_Selector.SetCurve(m_PolyCurve) m_PolyCurve.SetExtensionMode(@p_endpoints) m_PolyCurve.Rasterize(1) ; note that we enable arc lengths (makes Rasterize() slower) m_Handles = new @f_handles(pparent) ; create handle object m_Selector.SetHandles(m_Handles) ; note we're not allowing screen-relative here endfunc func Init(complex pz) UserTransform.Init(pz) m_Handles.Init(pz) endfunc complex func Iterate(complex pz) complex point float segment = 0 float segmentoffset = 0 float distancesquared = 0 float isleft = 0.0 complex oz = pz ; the assignment here seems to be required point = \ m_PolyCurve.ClosestPoint(pz, segment, segmentoffset, distancesquared) isleft = m_PolyCurve.IsLeft(pz, floor(segment)) pz = m_PolyCurve.DistanceAlongCurve(segment, true) ; should be a parameter here instead of true pz = pz + segmentoffset ; if we have a linear offset (extended segment), include it if (isleft > 0) isleft = 1 elseif (isleft < 0) isleft = -1 endif $ifdef debug int testx = 700 int testy = 400 if (#x == testx && #y == testy) print("FollowCurve.Iterate() trace") print("ClosestPoint: ", point) print("segment: ", segment) print("segmentoffset: ", segmentoffset) print("IsLeft: ", isleft) print("IsLeft1: ", m_PolyCurve.IsLeftLine(oz, (-1.5,0), (1.5,0))) print("IsLeft2: ", m_PolyCurve.IsLeftLine(oz, (1.5,0), (0,-1))) print("IsLeft3: ", m_PolyCurve.IsLeftLine(oz, (0,-1), (-1.5,0))) print("IsLeft4: ", m_PolyCurve.IsLeftQuadraticCore(oz, (-1.5,0), (0,-1), (1.5,0))) print("IsLeft5: ", m_PolyCurve.IsLeftLine((0,-1), (-1.5,0), (1.5,0))) print("DistanceAlongCurve: ", pz-segmentoffset) endif $endif pz = (real(pz)+real(@p_offset))*real(@p_scale) + flip(-sqrt(distancesquared)*isleft+imag(@p_offset))*imag(@p_scale) $ifdef debug if (#x == testx && #y == testy) print("final pz: ", pz) endif $endif m_Handles.Iterate(pz) m_Solid = m_Handles.IsSolid() point = point + 1 ; **** silences compiler warning return pz endfunc protected: DMJ_PolyCurve m_PolyCurve Handles m_Handles DMJ_PolyCurveSelector m_Selector default: title = "Follow Curve (BETA)" rating = notRecommended int param v_dmj_followcurve caption = "Version (DMJ_FollowCurve)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_followcurve < 100 endparam complex param p_offset caption = "Origin" default = (0,0) hint = "Sets the origin for transformed pixels. This is the point that will be used at the starting point of the curve. The easiest way to set this is to disable the transform, then use the eyedropper." endparam complex param p_scale caption = "Scale" default = (1,1) hint = "Sets the scale of the transform around the shape (real) and out from the shape (imaginary)." endparam int param p_endpoints caption = "Endpoints" default = 1 enum = "stop at ends" "extend lines" hint = "Sets how the transform will behave near unclosed curve endpoints. 'Stop at ends' will cause the transform to curve inward at the ends. 'Extend lines' will act as though the curve has been extended in infinite lines from the endpoints. This parameter has no effect if your curve is completely closed." endparam int param p_method caption = "Mapping Method" default = 0 enum = "closest point" "closest curve" visible = false hint = "Sets the method the transform will use to warp the complex plane to follow your curve. 'Closest point' is fast and works reasonably well, but in tight corners or at large distances from the curve it may show unwanted bunching. 'Closest curve' is slower but handles tight corners better." endparam heading caption = "Handle Options" expanded = false endheading Handles param f_handles caption = "Handle Options" selectable = false endparam heading caption = "Curve Options" expanded = true endheading DMJ_PolyCurveSelector param f_selector caption = "Curve Definition" default = DMJ_SelectorArbitraryPolygon hint = "Sets the method by which you will define your curve or polygon." endparam } class DMJ_Inversion(common.ulb:UserTransform) { ; General Inversion transformation. ; (Ported from dmj.uxf:dmj-Inversion) public: import "common.ulb" func DMJ_Inversion(Generic pparent) UserTransform.UserTransform(pparent) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 complex c = @p_invcenter ; assume inversion center is fixed complex r = (0,1) ^ (@p_angle / 90.0) ; rotation vector complex z = (pz-c) * r ; apply translation and rotation z = real(z) + flip(imag(z) * @p_aspect) ; apply aspect float d = 1.0 if (@p_invtype == 4) ; one axis only inversion if (@p_invpower == (-1,0)) ; standard power z = real(z) + @p_invscale*flip(1/imag(z)) ; invert just the one axis else ; general power z = real(z) + @p_invscale*flip(imag(z)^@p_invpower) ; do inversion endif else ; any other inversion type ; if (@p_invtype == 0) ; ellipse ; ; same distance everywhere if (@p_invtype == 1) ; hypercross d = sqrt(abs(real(z)*imag(z))/|z|) ; Kerry's simplification elseif (@p_invtype == 2) ; flower d = atan2(z) ; angle to z d = abs(cos(d*@p_invorder)+@p_diameter) ; distance from origin to flower at angle z elseif (@p_invtype == 3) ; lines d = sqrt(sqr(real(z)/imag(z))+1) ; distance from origin to horizontal line through z endif if (@p_invpower == (-1,0)) ; standard power z = @p_invscale*z*d / |z| ; do inversion (Kerry-optimized) else ; general power z = @p_invscale*z*d * cabs(z)^(@p_invpower-1) ; do inversion endif endif z = real(z) + flip(imag(z) / @p_aspect) ; undo aspect pz = z * conj(r) + c ; undo rotation and translation return pz endfunc protected: default: title = "General Inversion" int param v_dmj_inversion caption = "Version (DMJ_Inversion)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_inversion < 100 endparam int param p_invtype caption = "Inversion Curve" default = 0 enum = "ellipse" "hypercross" "flower" "lines" "one axis" hint = "Sets the shape of the curve through which inversion is done." endparam float param p_invscale caption = "Inversion Scale" default = 1.0 hint = "Scale of the inversion. This performs a similar function to changing the magnitude of the fractal zoom." endparam float param p_invorder caption = "Inversion Order" default = 3.0 hint = "Number of leaves for the flower curve." endparam complex param p_invcenter caption = "Inversion Center" default = #center hint = "Sets the center of the inversion." endparam float param p_aspect caption = "Aspect Ratio" default = 1.0 hint = "This is how square the inversion curve is. You can distort the curve by using a value other than 1.0." endparam float param p_diameter caption = "Diameter" default = 1.5 hint = "This is the diameter of the inversion curve. Note this only matters for the 'flower' curve type." endparam float param p_angle caption = "Rotation" default = 0.0 hint = "This is the angle, in degrees, that the inversion curve should be rotated." endparam complex param p_invpower caption = "Exponent" default = (-1,0) hint = "Gives the inversion exponent. (-1,0) gives the classic 1/z inversion type." endparam } class DMJ_Kaleidoscope(common.ulb:UserTransform) { ; Kaleidoscope transformation. ; (Ported from dmj.uxf:dmj-Kaleidoscope) public: import "common.ulb" func DMJ_Kaleidoscope(Generic pparent) UserTransform.UserTransform(pparent) endfunc func Init(complex pz) UserTransform.Init(pz) m_Rotation = (0,1) ^ (@p_angle / 90.0) m_Rotation2 = (0,1) ^ (@p_angle2 / 90.0) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 ; apply per-iteration kaleidoscope if (!@p_symreset) pz = (pz - @p_symcenter) * m_Rotation else pz = (pz) * m_Rotation endif float d = cabs(pz) float r = atan2(pz) if (r < 0) ; negative angle; we want 0 <= atan < 2pi r = r + #pi*2 endif if (@p_symorder != 0.0) if (@p_symreflect < 3) ; any actual kaleidoscopic mode r = r - floor(r * @p_symorder/#pi/2) * #pi*2/@p_symorder ; base kaleidoscope ; for some modes, reflect this if (@p_symreflect == 0) ; reflective if (r > #pi/@p_symorder) ; more than halfway through r = #pi*2/@p_symorder - r ; reflect endif elseif (@p_symreflect == 3) ; right symmetry r = #pi*2/@p_symorder - r ; reflect (always) endif elseif (@p_symreflect == 3) ; slice only if (r > #pi*2/@p_symorder) r = 0 m_Solid = true endif endif return (cos(r)*d + flip(sin(r)*d)) * m_Rotation2 + @p_symcenter else return pz endif endfunc protected: complex m_Rotation complex m_Rotation2 default: title = "Kaleidoscope" int param v_dmj_kaleidoscope caption = "Version (DMJ_Kaleidoscope)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_kaleidoscope < 100 endparam float param p_symorder caption = "Symmetry Order" default = 8.0 hint = "Indicates the number of reflected components. Use 0 to temporarily disable symmetry." endparam int param p_symreflect caption = "Symmetry Mode" default = 0 enum = "reflective" "left" "right" "slice only" hint = "Style of symmetry. Reflective will always be seamless; left and right may not be. If Slice is selected, only the section that will be mirrored will be shown." endparam complex param p_symcenter caption = "Symmetry Center" default = #center hint = "Sets the center of symmetry." endparam bool param p_symreset caption = "Relocate to origin" default = false hint = "If checked, the resulting symmetric pattern will be relocated to the origin." endparam float param p_angle caption = "Pre-Symmetry Rotation" default = 0.0 hint = "Sets how much to rotate the fractal (in degrees) before applying symmetry." endparam float param p_angle2 caption = "Post-Symmetry Rotation" default = 0.0 hint = "Sets how much to rotate the fractal (in degrees) after applying symmetry." endparam } class DMJ_LinearWave(common.ulb:UserTransform) { ; Linear wave transformation. ; (Ported from dmj3.uxf:dmj3-LinearWave) public: import "common.ulb" func DMJ_LinearWave(Generic pparent) UserTransform.UserTransform(pparent) endfunc func Init(complex pz) UserTransform.Init(pz) m_WaveRotation = (0,1) ^ (@p_waveangle / 90.0) m_DistortionRotation = (0,1) ^ (@p_distortionangle / 90.0) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 ; apply per-iteration wave distortion complex p = pz * m_WaveRotation float d = sin(real(p)*@p_distortionfrequency/#pi+@p_distortionphase/#pi)*@p_distortionstrength p = p + d * m_DistortionRotation pz = p * conj(m_WaveRotation) return pz endfunc protected: complex m_WaveRotation complex m_DistortionRotation default: title = "Linear Wave" int param v_dmj_linearwave caption = "Version (DMJ_LinearWave)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_linearwave < 100 endparam float param p_waveangle caption = "Wave Angle" default = 0.0 hint = "Sets the angle the wave is generated at, in degrees." endparam float param p_distortionangle caption = "Distortion Angle" default = 0.0 hint = "Sets the angle of the distortion, in degrees, relative to the wave angle." endparam float param p_distortionstrength caption = "Distortion Strength" default = 1.0 hint = "Sets the amount of distortion the wave will cause." endparam float param p_distortionfrequency caption = "Distortion Frequency" default = 10.0 hint = "Sets the frequency of the wave." endparam float param p_distortionphase caption = "Distortion Phase" default = 0.0 hint = "Sets the phase of the wave, in degrees. Use this to shift the wave without changing its strength or direction." endparam } class DMJ_LogSpiral(common.ulb:UserTransform) { ; Distort to follow a logarithmic spiral. ; (Ported from dmj3.uxf:dmj3-LogSpiral) public: import "common.ulb" func DMJ_LogSpiral(Generic pparent) UserTransform.UserTransform(pparent) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 float a = atan2(pz - @p_spiralcenter) * 0.5 / #pi float d = cabs(pz - @p_spiralcenter) float d2 = log(d)*@p_spiraltightness + a float d2f = d2 - floor(d2) float d2i = d2 - d2f pz = (d2i-a)*@p_spiralascale + flip(d2f)*@p_spiraldscale + @p_spiraloffset return pz endfunc protected: default: title = "Log Spiral" int param v_dmj_logspiral caption = "Version (DMJ_LogSpiral)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_logspiral < 100 endparam complex param p_spiralcenter caption = "Spiral Center" default = (0,0) endparam float param p_spiralascale caption = "Rotational scale" default = 1.0 endparam float param p_spiraldscale caption = "Distance scale" default = 1.0 endparam float param p_spiraltightness caption = "Tightness" default = 1.0 endparam complex param p_spiraloffset caption = "Strip Offset" default = (0,0) endparam } class DMJ_LowRes(common.ulb:UserTransform) { ; Reduce resolution. ; (Ported from dmj.uxf:dmj-LowRes) public: import "common.ulb" func DMJ_LowRes(Generic pparent) UserTransform.UserTransform(pparent) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 complex r = (0,1) ^ (@p_angle/90) ; complex rotation vector pz = (round(real(pz*r)*@p_xres)/@p_xres + flip(round(imag(pz*r)*@p_yres)/@p_yres)) * conj(r) return pz endfunc protected: default: title = "Low Resolution" int param v_dmj_lowres caption = "Version (DMJ_LowRes)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_lowres < 100 endparam float param p_xres caption = "Real Resolution" default = 25.0 hint = "Number of separate positions per horizontal unit distance." endparam float param p_yres caption = "Imaginary Resolution" default = 25.0 hint = "Number of separate positions per vertical unit distance." endparam float param p_angle caption = "Rotation angle" default = 0.0 hint = "Sets the angle at which the low-resolution grid is applied." endparam } class DMJ_PolarToRectangular(common.ulb:UserTransform) { ; Convert polar coordinates to rectangular. ; (Ported from dmj.uxf:dmj-Polar) public: import "common.ulb" func DMJ_PolarToRectangular(Generic pparent) UserTransform.UserTransform(pparent) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 pz = real(pz)*cos(imag(pz)) + flip(real(pz)*sin(imag(pz))) + @p_center return pz endfunc protected: default: title = "Polar to Rectangular" int param v_dmj_polartorectangular caption = "Version (DMJ_PolarToRectangular)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_polartorectangular < 100 endparam complex param p_center caption = "Polar Center" default = (0,0) hint = "This is the center of the polar coordinate system." endparam } class DMJ_RectangularToPolar(common.ulb:UserTransform) { ; Convert rectangular coordinates to polar. ; (Ported from dmj.uxf:dmj-Unpolar) public: import "common.ulb" func DMJ_RectangularToPolar(Generic pparent) UserTransform.UserTransform(pparent) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 pz = cabs(pz-@p_center) + flip(atan2(pz-@p_center)) return pz endfunc protected: default: title = "Rectangular to Polar" int param v_dmj_rectangulartopolar caption = "Version (DMJ_RectangularToPolar)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_rectangulartopolar < 100 endparam complex param p_center caption = "Polar Center" default = (0,0) hint = "This is the center of the polar coordinate system." endparam } class DMJ_TransformRepeat(common.ulb:UserTransform) { ; Repeat a transform a certain number of times. public: import "common.ulb" func DMJ_TransformRepeat(Generic pparent) UserTransform.UserTransform(pparent) m_Rotation = (0,1) ^ (@p_angle / 90.0) m_RotationReset = (0,1) ^ (-@p_resetcount * @p_angle / 90.0) m_Transform = new @f_transform(this) endfunc func Init(complex pz) UserTransform.Init(pz) m_Transform.Init(pz) endfunc complex func Iterate(complex pz) m_Iterations = m_Iterations + 1 complex zt int resetcount = 0 int j = 0 while (j < @p_repeat) zt = m_Transform.Iterate(pz) if (@p_autoscale) zt = pz + (zt-pz)/@p_repeat endif zt = zt * m_Rotation if (@p_resetcount > 0) resetcount = resetcount + 1 if (resetcount == @p_resetcount) zt = zt * m_RotationReset resetcount = 0 endif endif pz = zt + @p_offset j = j + 1 endwhile return pz endfunc protected: complex m_Rotation complex m_RotationReset Transform m_Transform default: title = "Repeat Transform" int param v_dmj_transformrepeat caption = "Version (DMJ_TransformRepeat)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_transformrepeat < 100 endparam int param p_repeat caption = "Iterations" default = 100 hint = "Sets the number of times to repeat the transform." endparam float param p_angle caption = "Rotation" default = 90.0 hint = "The transformed point will be rotated by this angle (in degrees) at every iteration." endparam int param p_resetcount caption = "Reset Rotation" default = 2 hint = "After this many iterations, the rotation will be 'reset' by rotating in the reverse direction in an amount equal to the previous rotations combined. You can use this parameter to do popcorn-like effects by setting it to 180 divided by the rotation angle. Use 90 and 2 for classic popcorn, 60 and 3 for triangular popcorn, etc. (assuming you are using Linear Wave as your repeated transform). Use a value of zero to disable this." endparam complex param p_offset caption = "Offset" default = (0,0) hint = "This offset will be added to the transformed point at every iteration." endparam bool param p_autoscale caption = "Auto-Scale Transform" default = true hint = "If this is checked, the amount of change from the transform will automatically be reduced to be proportional to the number of iterations. This allows the overall amount of transformation to remain roughly the same while taking advantage of finer-grained feedback. If you uncheck this, the transform will be applied at full strength with every iteration, and as you change the number of times the transform is applied you may need to adjust parameters within the transform to compensate." endparam UserTransform param f_transform caption = "Transform" default = DMJ_LinearWave hint = "This is the transform that will be repeated." endparam } class DMJ_TrapTiling(common.ulb:UserTransform) { ; Provides useful tiling effects, primarily intended for use with trap shapes, but will work with anything. public: import "common.ulb" func DMJ_TrapTiling(Generic pparent) UserTransform.UserTransform(pparent) endfunc complex func Iterate(complex pz) UserTransform.Iterate(pz) pz = (pz - @p_tilingcenter) * ((0,1) ^ (@p_tileangle / 90.0)) ; offset and rotate if (@p_tiletype == 0) ; rectangular if (real(@p_tilespacing) != 0) pz = (real(pz) / real(@p_tilespacing) - round(real(pz) / real(@p_tilespacing))) * real(@p_tilespacing) + flip(imag(pz)) endif if (imag(@p_tilespacing) != 0) pz = flip(imag(pz) / imag(@p_tilespacing) - round(imag(pz) / imag(@p_tilespacing))) * imag(@p_tilespacing) + real(pz) endif else pz = atan2(pz)*4/#pi + flip(cabs(pz)) ; convert if (real(@p_tilespacing) != 0) pz = (real(pz) / real(@p_tilespacing) - round(real(pz) / real(@p_tilespacing))) * real(@p_tilespacing) + flip(imag(pz)) endif if (imag(@p_tilespacing) != 0) pz = flip(imag(pz) / imag(@p_tilespacing) - floor(imag(pz) / imag(@p_tilespacing))) * imag(@p_tilespacing) + real(pz) endif if (@p_tiletype == 2) ; radial; convert back to rectangular pz = cos(real(pz)*#pi/4)*imag(pz) + flip(sin(real(pz)*#pi/4)*imag(pz)) endif endif pz = (pz * ((0,1) ^ (-@p_tileangle / 90.0))) + @p_tilingcenter ; un-rotate and un-offset return pz endfunc default: title = "Trap Tiling" int param v_dmj_traptiling caption = "Version (DMJ_TrapTiling)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_traptiling < 100 endparam int param p_tiletype caption = "Tile Type" default = 0 enum = "rectangular" "polar" "radial" hint = "Selects the kind of tiling to use. 'Rectangular' provides simple grid tiling. 'Polar' will tile rectangularly, then treat the coordinates as polar coordinates. 'Radial' will tile radially around a central point." endparam complex param p_tilingcenter caption = "Tiling Center" default = (0,0) hint = "Sets the tiling center." endparam complex param p_tilespacing caption = "Tile Spacing" default = (1,1) hint = "Sets the tile spacing. For rectangular tiles, provides the width and height of the tiles. For polar and radial tiles, sets the angular distance and radial distance. For any of these, use a value of 0 to indicate no tiling in that direction." endparam float param p_tileangle caption = "Tile Rotation" default = 0 hint = "Sets the angle of the tiling grid, in degrees." endparam } class DMJ_ScreenRelative(common.ulb:UtilityTransform) { public: import "common.ulb" func DMJ_ScreenRelative(Generic pparent) UtilityTransform.UtilityTransform(pparent) if (@p_screenrelative) m_Aspect = #width/#height else m_Aspect = 1.0 endif endfunc func Init(complex pz) endfunc complex func Iterate(complex pz) if (@p_screenrelative) return real(#screenpixel)/#width + flip(1.0-imag(#screenpixel)/#height) else return pz endif endfunc ; provide information about transformation style ; this is required by the Handles helper class which ; has to be able to reverse the transformation int func GetScreenRelative() if (@p_screenrelative) return 1 else return 0 endif endfunc float m_Aspect default: title = "Screen Relative" int param v_dmj_screenrelative caption = "Version (DMJ_ScreenRelative)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_screenrelative < 100 endparam bool param @p_screenrelative caption = "Screen Relative Coordinates" default = false hint = "If set, coordinates will be fixed with (0,0) in the lower left corner and (1,1) in the upper right corner, regardless of how you zoom your image." endparam ; bool param @p_imagestyle ; stretches screen-relative range to (-1,-1) } ; ----------------------------------------------------------------------------------------------------------------------------- ; Clip Shape classes class DMJ_ClipShapeCircle(common.ulb:ClipShape) { ; Circular clipping shape. public: import "common.ulb" func DMJ_ClipShapeCircle(Generic pparent) ClipShape.ClipShape(pparent) complex c ; computed center of circle complex s ; working point float d = 0.0 float a = m_Aspect if (@p_circletype == 0) ; bounding box method c = (@p_circleupperleft + @p_circlelowerright) * 0.5 if (abs(real(@p_circleupperleft)-real(@p_circlelowerright)) > abs(imag(@p_circleupperleft)-imag(@p_circlelowerright))) d = sqr(abs(imag(@p_circleupperleft)-imag(@p_circlelowerright)) * 0.5) else d = sqr(abs(real(@p_circleupperleft)-real(@p_circlelowerright)) * 0.5) endif s = c + sqrt(d) elseif (@p_circletype == 1) ; center and edge method c = @p_circlecenter s = @p_circleedge s = real(s-c)*a + flip(imag(s-c)) + c ; correct for aspect distortion, centered on c d = |s - c| elseif (@p_circletype == 2) ; opposite edges method c = (@p_circleedge1 + @p_circleedge2) * 0.5 s = @p_circleedge1 s = real(s-c)*a + flip(imag(s-c)) + c ; correct for aspect distortion, centered on c d = |s - c| elseif (@p_circletype == 3) ; three edge points method ; from mathworld.wolfram.com float ca = real(@p_circleedge1)*(imag(@p_circleedge2)*1-imag(@p_circleedge3)*1) - \ imag(@p_circleedge1)*(real(@p_circleedge2)*1-real(@p_circleedge3)*1) + \ 1*(real(@p_circleedge2)*imag(@p_circleedge3)-real(@p_circleedge3)*imag(@p_circleedge2)) float cd = -( (|@p_circleedge1|)*(imag(@p_circleedge2)*1-imag(@p_circleedge3)*1) - \ imag(@p_circleedge1)*((|@p_circleedge2|)*1-(|@p_circleedge3|)*1) + \ 1*((|@p_circleedge2|)*imag(@p_circleedge3)-(|@p_circleedge3|)*imag(@p_circleedge2)) ) float ce = (|@p_circleedge1|)*(real(@p_circleedge2)*1-real(@p_circleedge3)*1) - \ real(@p_circleedge1)*((|@p_circleedge2|)*1-(|@p_circleedge3|)*1) + \ 1*((|@p_circleedge2|)*real(@p_circleedge3)-(|@p_circleedge3|)*real(@p_circleedge2)) float cf = -( (|@p_circleedge1|)*(real(@p_circleedge2)*imag(@p_circleedge3)-real(@p_circleedge3)*imag(@p_circleedge2)) - \ real(@p_circleedge1)*((|@p_circleedge2|)*imag(@p_circleedge3)-(|@p_circleedge3|)*imag(@p_circleedge2)) + \ imag(@p_circleedge1)*((|@p_circleedge2|)*real(@p_circleedge3)-(|@p_circleedge3|)*real(@p_circleedge2)) ) c = -cd / (2*ca) + flip( -ce / (2*ca) ) d = (sqr(cd)+sqr(ce)) / (4*sqr(ca)) - cf/ca s = c + sqrt(d) endif ; save results of circle definition m_Center = c m_Edge = s m_Radius = sqrt(d) m_RadiusSquared = sqr(m_Radius + @p_expansion) endfunc complex func Iterate(complex pz) complex c = m_Center ; computed center of circle complex p = pz ; working point (generally, pixel/z) complex s = m_Edge ; computed edge point of the circle float d = m_RadiusSquared ; computed radius squared, including expansion float a = m_Aspect p = real(p-c)*a + flip(imag(p-c)) + c ; correct for aspect distortion, centered on c if (|c-p| <= d) m_Solid = true endif return pz endfunc func SetHandles(Handles phandles) ClipShape.SetHandles(phandles) int handletype = 1 ; assume we're using square handles float allowrotation = 0.0 ; assume we're zeroing out rotations if (@p_expansion != 0) ; we've expanded the edge handletype = 6 ; use double-arrow handles for edge points allowrotation = 1.0 ; allow the handles to rotate endif if (@p_circletype == 0) m_Handles.SetHandleCount(3,4,0,0) m_Handles.SetHandlePoint(-1,3,-22028,0,m_Center,0) m_Handles.SetHandlePoint(-1,1,-1262,0,@p_circleupperleft,0) m_Handles.SetHandlePoint(-1,1,-908,0,@p_circlelowerright,0) complex ur = real(@p_circlelowerright) + flip(imag(@p_circleupperleft)) complex ll = real(@p_circleupperleft) + flip(imag(@p_circlelowerright)) m_Handles.SetHandleLine(-1,2,false,@p_circleupperleft,ur) m_Handles.SetHandleLine(-1,2,true,ur,@p_circlelowerright) m_Handles.SetHandleLine(-1,2,true,@p_circlelowerright,ll) m_Handles.SetHandleLine(-1,2,true,ll,@p_circleupperleft) elseif (@p_circletype == 1) m_Handles.SetHandleCount(2,0,0,0) m_Handles.SetHandlePoint(-1,3,-22028,0,m_Center,0) m_Handles.SetHandlePoint(-1,handletype,-983095,0,@p_circleedge,allowrotation*atan2(@p_circleedge-@p_circlecenter)*180/#pi) elseif (@p_circletype == 2) m_Handles.SetHandleCount(3,0,0,0) m_Handles.SetHandlePoint(-1,3,-22028,0,m_Center,0) m_Handles.SetHandlePoint(-1,handletype,1,0,@p_circleedge1,allowrotation*atan2(@p_circleedge1-m_Center)*180/#pi) m_Handles.SetHandlePoint(-1,handletype,2,0,@p_circleedge2,allowrotation*atan2(@p_circleedge2-m_Center)*180/#pi) elseif (@p_circletype == 3) m_Handles.SetHandleCount(4,0,0,0) m_Handles.SetHandlePoint(-1,3,-22028,0,m_Center,0) m_Handles.SetHandlePoint(-1,handletype,1,0,@p_circleedge1,allowrotation*atan2(@p_circleedge1-m_Center)*180/#pi) m_Handles.SetHandlePoint(-1,handletype,2,0,@p_circleedge2,allowrotation*atan2(@p_circleedge2-m_Center)*180/#pi) m_Handles.SetHandlePoint(-1,handletype,3,0,@p_circleedge3,allowrotation*atan2(@p_circleedge3-m_Center)*180/#pi) endif if (@p_expansion != 0) m_Handles.SetHandleCount(-1,-1,1,-1) m_Handles.SetHandleCircle(-1,3,m_Center,m_Radius) endif endfunc protected: complex m_Center complex m_Edge float m_Radius float m_RadiusSquared default: title = "Circle" int param v_dmj_clipshapecircle caption = "Version (DMJ_ClipShapeCircle)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_clipshapecircle < 100 endparam float param p_expansion caption = "Expansion" default = 0.0 hint = "Sets the amount the shape will be expanded. Positive values will increase the size of the shape, negative values will decrease the size of the shape." endparam int param p_circletype caption = "Define Circle By" default = 2 enum = "bounding box" "center and edge" "opposite edges" "three edge points" hint = "Choose how you want to define where the circle is. Different methods are useful in different circumstances." endparam complex param p_circleupperleft caption = "Box Upper Left" default = #center - 1/#magn + flip(1/#magn) visible = (@p_circletype == 0) hint = "Sets the upper left corner of the bounding box that the circle fits in. Note that if your image is rotated, and you're not using screen-relative clipping, the bounding box for your circle will be rotated along with the image. In that case, you may find 'center and edge' easier to use." endparam complex param p_circlelowerright caption = "Box Lower Right" default = #center + 1/#magn - flip(1/#magn) visible = (@p_circletype == 0) hint = "Sets the upper left corner of the bounding box that the circle fits in. Note that if your image is rotated, and you're not using screen-relative clipping, the bounding box for your circle will be rotated along with the image. In that case, you may find 'center and edge' easier to use." endparam complex param p_circlecenter caption = "Circle Center" default = #center visible = (@p_circletype == 1) hint = "Sets the center of the circle. If you're not sure exactly where the center should go, you may find 'opposite edges' easier to use." endparam complex param p_circleedge caption = "Circle Edge" default = #center + 1/#magn visible = (@p_circletype == 1) hint = "Sets the edge of the circle. If you need the edge to line up with something else in the image, you may find 'bounding box' easier to use." endparam complex param p_circleedge1 caption = "Circle Edge 1" default = #center - 1/#magn visible = (@p_circletype == 2 || @p_circletype == 3) hint = "Sets one of the edge points of the circle." endparam complex param p_circleedge2 caption = "Circle Edge 2" default = #center + 1/#magn visible = (@p_circletype == 2 || @p_circletype == 3) hint = "Sets one of the edge points of the circle." endparam complex param p_circleedge3 caption = "Circle Edge 3" default = #center + flip(1/#magn) visible = (@p_circletype == 3) hint = "Sets one of the edge points of the circle." endparam } class DMJ_ClipShapePolyCurve(common.ulb:ClipShape) { ; PolyCurve clipping shape abstract base class. public: import "common.ulb" func DMJ_ClipShapePolyCurve(Generic pparent) ClipShape.ClipShape(pparent) m_PolyCurve = new DMJ_PolyCurve() m_Selector = new @f_selector(pparent) m_Selector.SetCurve(m_PolyCurve) m_PolyCurve.Rasterize(0) endfunc complex func Iterate(complex pz) m_Solid = m_PolyCurve.IsInside(pz) if (@p_outlinemode > 0) float d float t float to m_PolyCurve.ClosestPoint(pz, t, to, d) if (@p_outlinemode == 1) ; shrink curve if (d < @p_outlinewidth*@p_outlinewidth) m_Solid = false endif elseif (@p_outlinemode == 2) ; grow curve if (d < @p_outlinewidth*@p_outlinewidth) m_Solid = true endif elseif (@p_outlinemode == 3) ; outline only if (d < @p_outlinewidth*@p_outlinewidth) m_Solid = true else m_Solid = false endif endif endif return pz endfunc func SetHandles(Handles phandles) m_Selector.SetHandles(phandles) endfunc protected: DMJ_PolyCurve m_PolyCurve DMJ_PolyCurveSelector m_Selector default: title = "PolyCurve Family" int param v_dmj_clipshapepolycurve caption = "Version (DMJ_ClipShapePolyCurve)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_clipshapepolycurve < 100 endparam DMJ_PolyCurveSelector param f_selector caption = "Curve Definition" default = DMJ_SelectorArbitraryPolygon hint = "Sets the method by which you will define your curve or polygon." endparam int param p_outlinemode caption = "Outline Use" default = 0 enum = "off" "shrink shape" "grow shape" "outline only" hint = "Sets the way the outline will be used." endparam float param p_outlinewidth caption = "Outline Width" default = 0.1/#magn visible = (@p_outlinemode != 0) hint = "Sets the width of the outline." endparam } class DMJ_ClipShapeRectangle(common.ulb:ClipShape) { ; Rectangle clipping shape. public: import "common.ulb" func DMJ_ClipShapeRectangle(Generic pparent) ClipShape.ClipShape(pparent) endfunc complex func Iterate(complex pz) complex c ; computed center of rectangle complex p = pz ; working point (generally, pixel/z) complex q ; working point complex r = (1,0) ; rotation vector complex s ; working point complex t ; working point complex n ; normal vector float a = m_Aspect if (@p_recttype == 0) ; three corners c = (@p_rectupperleft + @p_rectlowerright) * 0.5 p = real(p-c)*a + flip(imag(p-c)) + c ; correct for aspect distortion, centered on c q = real(@p_rectupperleft-c)*a + flip(imag(@p_rectupperleft-c)) + c ; correct for aspect distortion, centered on c t = real(@p_rectupperright-c)*a + flip(imag(@p_rectupperright-c)) + c ; correct for aspect distortion, centered on c p = (p - c) * conj(r) + c ; apply rotation d = cabs(q - c) s = c + (t-c)*d/cabs(t-c) ; corrected upper-right n = (s-q) / cabs(s-q) ; unit vector along the top edge s = (s-c)*conj(n) p = (p-c)*conj(n) s = abs(s) if (real(p) >= -real(s) && real(p) <= real(s) && imag(p) >= -imag(s) && imag(p) <= imag(s)) m_Solid = true endif elseif (@p_recttype == 1) ; center and two edges c = @p_rectcenter p = real(p-c)*a + flip(imag(p-c)) + c ; correct for aspect distortion, centered on c q = real(@p_recttop-c)*a + flip(imag(@p_recttop-c)) + c ; correct for aspect distortion, centered on c t = real(@p_rectright-c)*a + flip(imag(@p_rectright-c)) + c ; correct for aspect distortion, centered on c p = (p - c) * conj(r) + c ; apply rotation n = (q-c) / cabs(q-c) ; unit vector to top edge n = conj(flip(n)) s = real((t-c)*conj(n)) + flip(imag((q-c)*conj(n))) p = (p-c)*conj(n) s = abs(s) if (real(p) >= -real(s) && real(p) <= real(s) && imag(p) >= -imag(s) && imag(p) <= imag(s)) m_Solid = true endif elseif (@p_recttype == 2) ; fixed angle (edges) c = @p_rectcenter p = real(p-c)*a + flip(imag(p-c)) + c ; correct for aspect distortion, centered on c q = real(@p_recttop-c)*a + flip(imag(@p_recttop-c)) + c ; correct for aspect distortion, centered on c t = real(@p_rectright-c)*a + flip(imag(@p_rectright-c)) + c ; correct for aspect distortion, centered on c p = (p - c) * conj(r) + c ; apply rotation n = (0,1)^(@p_rectangle/90) ; unit vector along top edge s = real((t-c)*conj(n)) + flip(imag((q-c)*conj(n))) p = (p-c)*conj(n) s = abs(s) if (real(p) >= -real(s) && real(p) <= real(s) && imag(p) >= -imag(s) && imag(p) <= imag(s)) m_Solid = true endif endif return pz endfunc func SetHandles(Handles phandles) ClipShape.SetHandles(phandles) if (@p_recttype == 0) m_Handles.SetHandleCount(3,0,0,0) m_Handles.SetHandlePoint(-1,1,1,0,@p_rectupperleft,0) m_Handles.SetHandlePoint(-1,1,2,0,@p_rectupperright,0) m_Handles.SetHandlePoint(-1,1,3,0,@p_rectlowerright,0) elseif (@p_recttype == 1) m_Handles.SetHandleCount(3,0,0,0) m_Handles.SetHandlePoint(-1,1,-30,0,@p_recttop,0) m_Handles.SetHandlePoint(-1,3,-13,0,@p_rectcenter,0) m_Handles.SetHandlePoint(-1,1,-28,0,@p_rectright,0) elseif (@p_recttype == 2) m_Handles.SetHandleCount(3,0,0,0) m_Handles.SetHandlePoint(-1,1,-30,0,@p_recttop,0) m_Handles.SetHandlePoint(-1,3,-13,0,@p_rectcenter,0) m_Handles.SetHandlePoint(-1,1,-28,0,@p_rectright,0) endif endfunc default: title = "Rectangle" int param v_dmj_clipshaperectangle caption = "Version (DMJ_ClipShapeRectangle)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_clipshaperectangle < 100 endparam int param p_recttype caption = "Define Rect By" default = 2 enum = "three corners" "center and two edges" "fixed angle (edges)" ;"fixed angle (corners)" hint = "Choose how you want to define where the rectangle is. Different methods are useful in different circumstances." endparam complex param p_rectupperleft caption = "Rect Upper Left" default = #center + (-1/#magn + flip(1/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_recttype == 0 || @p_recttype == 3) hint = "Selects the upper left corner of the rectangle." endparam complex param p_rectlowerright caption = "Rect Lower Right" default = #center + (1/#magn + flip(-1/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_recttype == 0 || @p_recttype == 3) hint = "Selects the lower right corner of the rectangle." endparam complex param p_rectupperright caption = "Rect Upper Right" default = #center + (1/#magn + flip(1/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_recttype == 0) hint = "Selects one of the other corners of the rectangle (upper right is suggested, but not required). Note that only points that are on a circle, centered on the rectangle's center, and passing through the upper left and lower right corners, will produce a valid rectangle; whatever point you actually choose for this third corner will be moved to that circle in order to produce a proper rectangle. Practically speaking, you should use this corner to set the overall angle of the rectangle, after selecting opposite corners." endparam float param p_rectangle caption = "Rectangle Angle" default = #angle*180/#pi visible = (@p_recttype == 2 || @p_recttype == 3) hint = "Selects the rotation angle of the rectangle. This is a bit different from the general 'Rotation' parameter. If 'Rotation' is zero, then this parameter is used to set the angle of the rectangle, and the point you select for 'Rect Top Edge' will lie exactly on the rectangle's top edge, even if it's not the center of that edge. Changing this parameter after you've selected rectangle edge points will change the size of the rectangle, as different parts of the edges have to pass through the points to make your rectangle. If you want to rotate the clipping shape without changing the size, use the general 'Rotation' parameter. Doing so, however, will affect your selection of 'Rect Edge' with the eyedropper or explorer tools. Angles are in degrees, counter-clockwise." endparam complex param p_rectcenter caption = "Rect Center" default = #center visible = (@p_recttype == 1 || @p_recttype == 2) hint = "Selects the center point of the rectangle." endparam complex param p_recttop caption = "Rect Top Edge" default = #center + (flip(1/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_recttype == 1 || @p_recttype == 2) hint = "Selects the center of the top edge of the rectangle, for 'center and two edges', or any point on the top edge, for 'fixed angle'. Note that, for 'center and two edges', this point also sets an implicit rotation for the rectangle." endparam complex param p_rectright caption = "Rect Right Edge" default = #center + (-1/#magn) * ((0,1)^(#angle*2/#pi)) visible = (@p_recttype == 1 || @p_recttype == 2) hint = "Selects the right edge of the rectangle. It can be anywhere on the right edge of the rectangle." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; PolyCurve Selector classes class DMJ_SelectorArbitraryPolygon(DMJ_PolyCurveSelector) { ; Arbitrary polygon curve selector. public: import "common.ulb" func DMJ_SelectorArbitraryPolygon(Generic pparent) DMJ_PolyCurveSelector.DMJ_PolyCurveSelector(pparent) m_Anchors[0] = @p_anchor1 m_Anchors[1] = @p_anchor2 m_Anchors[2] = @p_anchor3 m_Anchors[3] = @p_anchor4 m_Anchors[4] = @p_anchor5 m_Anchors[5] = @p_anchor6 m_Anchors[6] = @p_anchor7 m_Anchors[7] = @p_anchor8 m_Anchors[8] = @p_anchor9 m_Anchors[9] = @p_anchor10 m_Anchors[10] = @p_anchor11 m_Anchors[11] = @p_anchor12 m_Anchors[12] = @p_anchor13 m_Anchors[13] = @p_anchor14 m_Anchors[14] = @p_anchor15 m_Anchors[15] = @p_anchor16 m_Anchors[16] = @p_anchor17 m_Anchors[17] = @p_anchor18 m_Anchors[18] = @p_anchor19 m_Anchors[19] = @p_anchor20 endfunc func SetCurve(DMJ_PolyCurve &pcurve) pcurve.SetSegmentCount(@p_segmentcount) int j = 0 int closed = 0 while (j < @p_segmentcount) if (j == @p_segmentcount-1) closed = 1 endif pcurve.SetSegment(-1, 1, closed, m_Anchors[j], 0, 0) j = j + 1 endwhile endfunc func SetHandles(Handles phandles) DMJ_PolyCurveSelector.SetHandles(phandles) phandles.SetHandleCount(@p_segmentcount,0,0,0) int j = 0 while (j < @p_segmentcount) phandles.SetHandlePoint(-1, 1, j+1, 0, m_Anchors[j], 0) j = j + 1 endwhile endfunc protected: complex m_Anchors[20] default: title = "Arbitrary Polygon" int param v_dmj_selectorarbitrarypolygon caption = "Version (DMJ_SelectorArbitraryPolygon)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_selectorarbitrarypolygon < 100 endparam int param p_segmentcount caption = "Number of sides" default = 3 min = 3 max = 20 hint = "Sets the number of sides in the polygon." endparam complex param p_anchor1 caption = "Point 1" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 1) hint = "Sets the location of point 1 in the polygon." endparam complex param p_anchor2 caption = "Point 2" default = #center + (1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 2) hint = "Sets the location of point 2 in the polygon." endparam complex param p_anchor3 caption = "Point 3" default = #center + (1/#magn + flip(-0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 3) hint = "Sets the location of point 3 in the polygon." endparam complex param p_anchor4 caption = "Point 4" default = #center + (-1/#magn + flip(-0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 4) hint = "Sets the location of point 4 in the polygon." endparam complex param p_anchor5 caption = "Point 5" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 5) hint = "Sets the location of point 5 in the polygon." endparam complex param p_anchor6 caption = "Point 6" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 6) hint = "Sets the location of point 6 in the polygon." endparam complex param p_anchor7 caption = "Point 7" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 7) hint = "Sets the location of point 7 in the polygon." endparam complex param p_anchor8 caption = "Point 8" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 8) hint = "Sets the location of point 8 in the polygon." endparam complex param p_anchor9 caption = "Point 9" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 9) hint = "Sets the location of point 9 in the polygon." endparam complex param p_anchor10 caption = "Point 10" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 10) hint = "Sets the location of point 10 in the polygon." endparam complex param p_anchor11 caption = "Point 11" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 11) hint = "Sets the location of point 11 in the polygon." endparam complex param p_anchor12 caption = "Point 12" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 12) hint = "Sets the location of point 12 in the polygon." endparam complex param p_anchor13 caption = "Point 13" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 13) hint = "Sets the location of point 13 in the polygon." endparam complex param p_anchor14 caption = "Point 14" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 14) hint = "Sets the location of point 14 in the polygon." endparam complex param p_anchor15 caption = "Point 15" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 15) hint = "Sets the location of point 15 in the polygon." endparam complex param p_anchor16 caption = "Point 16" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 16) hint = "Sets the location of point 16 in the polygon." endparam complex param p_anchor17 caption = "Point 17" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 17) hint = "Sets the location of point 17 in the polygon." endparam complex param p_anchor18 caption = "Point 18" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 18) hint = "Sets the location of point 18 in the polygon." endparam complex param p_anchor19 caption = "Point 19" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 19) hint = "Sets the location of point 19 in the polygon." endparam complex param p_anchor20 caption = "Point 20" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 20) hint = "Sets the location of point 20 in the polygon." endparam } class DMJ_SelectorArbitraryCurve(DMJ_PolyCurveSelector) { ; Arbitrary quadratic Bézier curve selector. public: import "common.ulb" func DMJ_SelectorArbitraryCurve(Generic pparent) DMJ_PolyCurveSelector.DMJ_PolyCurveSelector(pparent) m_Anchors[0] = @p_anchor1 m_Anchors[1] = @p_anchor2 m_Anchors[2] = @p_anchor3 m_Anchors[3] = @p_anchor4 m_Anchors[4] = @p_anchor5 m_Anchors[5] = @p_anchor6 m_Anchors[6] = @p_anchor7 m_Anchors[7] = @p_anchor8 m_Anchors[8] = @p_anchor9 m_Anchors[9] = @p_anchor10 m_Anchors[10] = @p_anchor11 m_Anchors[11] = @p_anchor12 m_Anchors[12] = @p_anchor13 m_Anchors[13] = @p_anchor14 m_Anchors[14] = @p_anchor15 m_Anchors[15] = @p_anchor16 m_Anchors[16] = @p_anchor17 m_Anchors[17] = @p_anchor18 m_Anchors[18] = @p_anchor19 m_Anchors[19] = @p_anchor20 m_OnCurve[0] = !@p_oncurve1 || !@p_autoclosecurve m_OnCurve[1] = !@p_oncurve2 m_OnCurve[2] = !@p_oncurve3 m_OnCurve[3] = !@p_oncurve4 m_OnCurve[4] = !@p_oncurve5 m_OnCurve[5] = !@p_oncurve6 m_OnCurve[6] = !@p_oncurve7 m_OnCurve[7] = !@p_oncurve8 m_OnCurve[8] = !@p_oncurve9 m_OnCurve[9] = !@p_oncurve10 m_OnCurve[10] = !@p_oncurve11 m_OnCurve[11] = !@p_oncurve12 m_OnCurve[12] = !@p_oncurve13 m_OnCurve[13] = !@p_oncurve14 m_OnCurve[14] = !@p_oncurve15 m_OnCurve[15] = !@p_oncurve16 m_OnCurve[16] = !@p_oncurve17 m_OnCurve[17] = !@p_oncurve18 m_OnCurve[18] = !@p_oncurve19 m_OnCurve[19] = !@p_oncurve20 m_OnCurve[@p_segmentcount-1] = m_OnCurve[@p_segmentcount-1] || !@p_autoclosecurve m_CloseCurve[0] = @p_closecurve1 m_CloseCurve[1] = @p_closecurve2 m_CloseCurve[2] = @p_closecurve3 m_CloseCurve[3] = @p_closecurve4 m_CloseCurve[4] = @p_closecurve5 m_CloseCurve[5] = @p_closecurve6 m_CloseCurve[6] = @p_closecurve7 m_CloseCurve[7] = @p_closecurve8 m_CloseCurve[8] = @p_closecurve9 m_CloseCurve[9] = @p_closecurve10 m_CloseCurve[10] = @p_closecurve11 m_CloseCurve[11] = @p_closecurve12 m_CloseCurve[12] = @p_closecurve13 m_CloseCurve[13] = @p_closecurve14 m_CloseCurve[14] = @p_closecurve15 m_CloseCurve[15] = @p_closecurve16 m_CloseCurve[16] = @p_closecurve17 m_CloseCurve[17] = @p_closecurve18 m_CloseCurve[18] = @p_closecurve19 m_CloseCurve[19] = @p_closecurve20 m_CloseCurve[@p_segmentcount-1] = @p_autoclosecurve endfunc func SetCurve(DMJ_PolyCurve &pcurve) pcurve.SetSegmentCount(@p_segmentcount) int j = 0 int closed = 0 ; curve is not closed at this point complex anchor = 0 bool haveanchor = false ; no anchor (on-curve) point saved while (j < @p_segmentcount) if (m_CloseCurve[j]) ; this point closes the curve closed = 1 ; set this auto flag (close curve) endif if (m_OnCurve[j]) ; this is an on-curve point; we may not write it this iteration if (haveanchor) ; previous point was on-curve (two consecutive on-curve points) pcurve.SetSegment(-1, 1, 0, anchor, 0, 0) ; create a straight line from that point endif if (closed == 1) ; curve should be closed after this point haveanchor = false ; so there cannot be a saved anchor point pcurve.SetSegment(-1, 1, closed, m_Anchors[j], 0, 0) ; create line segment to this point else ; curve should not be closed after this point haveanchor = true ; we now have a saved anchor point anchor = m_Anchors[j] ; save its location endif else ; this is an off-curve point; always write it this cycle if (haveanchor) ; we have an on-curve point to link it to pcurve.SetSegment(-1, 2, closed, anchor, m_Anchors[j], 0) ; create curve segment haveanchor = false ; last point was off-curve else ; no on-curve point to link to pcurve.SetSegment(-1, 2, closed + 2, 0, m_Anchors[j], 0) ; create auto-curved segment endif endif j = j + 1 closed = 0 endwhile if (haveanchor) ; trailing unclosed on-curve point (always happens with unclosed curves) pcurve.SetSegment(-1, 0, 0, anchor, 0, 0) ; add this point so the last segment will connect to it endif endfunc func SetHandles(Handles phandles) DMJ_PolyCurveSelector.SetHandles(phandles) phandles.SetHandleCount(@p_segmentcount*2,@p_segmentcount*2,0,0) int j = 0 int loopstart = 0 bool offcurve = false while (j < @p_segmentcount) if (m_OnCurve[j]) if (offcurve) phandles.SetHandleLine(-1, 2, false, m_Anchors[j-1], m_Anchors[j]) endif phandles.SetHandlePoint(-1, 1, j+1, 0, m_Anchors[j], 0) offcurve = false else if (offcurve) phandles.SetHandlePoint(-1, 4, 0, 0, (m_Anchors[j]+m_Anchors[j-1]) / 2, 0) endif phandles.SetHandlePoint(-1, 2, j+1, 0, m_Anchors[j], 0) phandles.SetHandleLine(-1, 2, false, m_Anchors[j-1], m_Anchors[j]) offcurve = true endif if (m_CloseCurve[j] || j == @p_segmentcount-1) if (!m_OnCurve[j] && !m_OnCurve[loopstart]) phandles.SetHandlePoint(-1, 4, 0, 0, (m_Anchors[j]+m_Anchors[loopstart]) / 2, 0) endif phandles.SetHandleLine(-1, 2, false, m_Anchors[loopstart], m_Anchors[j]) loopstart = j + 1 offcurve = false endif j = j + 1 endwhile endfunc protected: complex m_Anchors[20] bool m_OnCurve[20] bool m_CloseCurve[20] default: title = "Arbitrary Bézier Curve" int param v_dmj_selectorarbitrarycurve caption = "Version (DMJ_SelectorArbitraryCurve)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_selectorarbitrarycurve < 100 endparam int param p_segmentcount caption = "Number of segments" default = 3 min = 3 max = 20 hint = "Sets the number of curve segments in the shape." endparam complex param p_anchor1 caption = "Point 1" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 1) hint = "Sets the location of point 1 in the curve shape." endparam bool param p_oncurve1 caption = "Smooth" default = true visible = (@p_segmentcount >= 1 && @p_autoclosecurve) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve1 caption = "Close curve" default = false visible = (@p_segmentcount >= 2) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor2 caption = "Point 2" default = #center + (1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 2) hint = "Sets the location of point 2 in the curve shape." endparam bool param p_oncurve2 caption = "Smooth" default = true visible = (@p_segmentcount >= 2 && !(@p_segmentcount == 2 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve2 caption = "Close curve" default = false visible = (@p_segmentcount >= 3) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor3 caption = "Point 3" default = #center + (1/#magn + flip(-0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 3) hint = "Sets the location of point 3 in the curve shape." endparam bool param p_oncurve3 caption = "Smooth" default = true visible = (@p_segmentcount >= 3 && !(@p_segmentcount == 3 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve3 caption = "Close curve" default = false visible = (@p_segmentcount >= 4) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor4 caption = "Point 4" default = #center + (-1/#magn + flip(-0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 4) hint = "Sets the location of point 4 in the curve shape." endparam bool param p_oncurve4 caption = "Smooth" default = true visible = (@p_segmentcount >= 4 && !(@p_segmentcount == 4 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve4 caption = "Close curve" default = false visible = (@p_segmentcount >= 5) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor5 caption = "Point 5" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 5) hint = "Sets the location of point 5 in the curve shape." endparam bool param p_oncurve5 caption = "Smooth" default = true visible = (@p_segmentcount >= 5 && !(@p_segmentcount == 5 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve5 caption = "Close curve" default = false visible = (@p_segmentcount >= 6) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor6 caption = "Point 6" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 6) hint = "Sets the location of point 6 in the curve shape." endparam bool param p_oncurve6 caption = "Smooth" default = true visible = (@p_segmentcount >= 6 && !(@p_segmentcount == 6 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve6 caption = "Close curve" default = false visible = (@p_segmentcount >= 7) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor7 caption = "Point 7" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 7) hint = "Sets the location of point 7 in the curve shape." endparam bool param p_oncurve7 caption = "Smooth" default = true visible = (@p_segmentcount >= 7 && !(@p_segmentcount == 7 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve7 caption = "Close curve" default = false visible = (@p_segmentcount >= 8) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor8 caption = "Point 8" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 8) hint = "Sets the location of point 8 in the curve shape." endparam bool param p_oncurve8 caption = "Smooth" default = true visible = (@p_segmentcount >= 8 && !(@p_segmentcount == 8 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve8 caption = "Close curve" default = false visible = (@p_segmentcount >= 9) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor9 caption = "Point 9" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 9) hint = "Sets the location of point 9 in the curve shape." endparam bool param p_oncurve9 caption = "Smooth" default = true visible = (@p_segmentcount >= 9 && !(@p_segmentcount == 9 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve9 caption = "Close curve" default = false visible = (@p_segmentcount >= 10) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor10 caption = "Point 10" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 10) hint = "Sets the location of point 10 in the curve shape." endparam bool param p_oncurve10 caption = "Smooth" default = true visible = (@p_segmentcount >= 10 && !(@p_segmentcount == 10 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve10 caption = "Close curve" default = false visible = (@p_segmentcount >= 11) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor11 caption = "Point 11" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 11) hint = "Sets the location of point 11 in the curve shape." endparam bool param p_oncurve11 caption = "Smooth" default = true visible = (@p_segmentcount >= 11 && !(@p_segmentcount == 11 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve11 caption = "Close curve" default = false visible = (@p_segmentcount >= 12) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor12 caption = "Point 12" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 12) hint = "Sets the location of point 12 in the curve shape." endparam bool param p_oncurve12 caption = "Smooth" default = true visible = (@p_segmentcount >= 12 && !(@p_segmentcount == 12 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve12 caption = "Close curve" default = false visible = (@p_segmentcount >= 13) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor13 caption = "Point 13" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 13) hint = "Sets the location of point 13 in the curve shape." endparam bool param p_oncurve13 caption = "Smooth" default = true visible = (@p_segmentcount >= 13 && !(@p_segmentcount == 13 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve13 caption = "Close curve" default = false visible = (@p_segmentcount >= 14) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor14 caption = "Point 14" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 14) hint = "Sets the location of point 14 in the curve shape." endparam bool param p_oncurve14 caption = "Smooth" default = true visible = (@p_segmentcount >= 14 && !(@p_segmentcount == 14 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve14 caption = "Close curve" default = false visible = (@p_segmentcount >= 15) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor15 caption = "Point 15" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 15) hint = "Sets the location of point 15 in the curve shape." endparam bool param p_oncurve15 caption = "Smooth" default = true visible = (@p_segmentcount >= 15 && !(@p_segmentcount == 15 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve15 caption = "Close curve" default = false visible = (@p_segmentcount >= 16) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor16 caption = "Point 16" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 16) hint = "Sets the location of point 16 in the curve shape." endparam bool param p_oncurve16 caption = "Smooth" default = true visible = (@p_segmentcount >= 16 && !(@p_segmentcount == 16 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve16 caption = "Close curve" default = false visible = (@p_segmentcount >= 17) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor17 caption = "Point 17" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 17) hint = "Sets the location of point 17 in the curve shape." endparam bool param p_oncurve17 caption = "Smooth" default = true visible = (@p_segmentcount >= 17 && !(@p_segmentcount == 17 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve17 caption = "Close curve" default = false visible = (@p_segmentcount >= 18) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor18 caption = "Point 18" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 18) hint = "Sets the location of point 18 in the curve shape." endparam bool param p_oncurve18 caption = "Smooth" default = true visible = (@p_segmentcount >= 18 && !(@p_segmentcount == 18 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve18 caption = "Close curve" default = false visible = (@p_segmentcount >= 19) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor19 caption = "Point 19" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 19) hint = "Sets the location of point 19 in the curve shape." endparam bool param p_oncurve19 caption = "Smooth" default = true visible = (@p_segmentcount >= 19 && !(@p_segmentcount == 19 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve19 caption = "Close curve" default = false visible = (@p_segmentcount >= 20) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam complex param p_anchor20 caption = "Point 20" default = #center + (-1/#magn + flip(0.5/#magn)) * ((0,1)^(#angle*2/#pi)) visible = (@p_segmentcount >= 20) hint = "Sets the location of point 20 in the curve shape." endparam bool param p_oncurve20 caption = "Smooth" default = true visible = (@p_segmentcount >= 20 && !(@p_segmentcount == 20 && !@p_autoclosecurve)) hint = "Sets whether this point will be smooth or not. Use sequences of smooth points \ to produce a smooth curve. Uncheck this box to create a sharp point (a corner) \ and uncheck it for several points in a row to create line segments. \ Whenever you have sequences of smooth points, the curve generator will show you \ the 'touching points' with X handles." endparam bool param p_closecurve20 caption = "Close curve" default = false visible = (@p_segmentcount >= 21) hint = "Sets whether the curve will be closed at this point. Curves must contain \ at least three points." endparam bool param p_autoclosecurve caption = "Close curve" default = true hint = "Sets whether the curve will be closed at the end. Closed curves are required for some uses." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Generator classes class DMJ_TrapSelect(common.ulb:Generator) { ; TrapSelect base class for choosing which iterations to trap. ; ; The TrapSelect class is a Generator that produces a ; sequence of values that are all 0 or 1. A 0 indicates ; the trap calculation should be skipped for this ; iteration; a 1 indicates the trap calculation should ; be performed at this iteration. ; ; You can derive other TrapSelect classes from this ; base class to indicate they adhere to the 0 or 1 ; convention. public: import "common.ulb" ; Constructor ; @param pparent = a reference to the object creating the new object; typically, 'this' func DMJ_TrapSelect(Generic pparent) Generator.Generator(pparent) endfunc ; Set up for a sequence of values ; ; This function will be called at the beginning of each ; sequence of values (e.g. at the beginning of each fractal ; orbit). ; @param pz = seed value for the sequence ; @return first value in the sequence float func Init(float pz) Generator.Init(pz) ; Base class implementation flags the sequence to end ; immediately. We don't want this (this sequence proceeds ; indefinitely) so clear the flag. m_BailedOut = false m_FractionalIteration = @p_trapstart m_Trapping = false m_LoopCount = @p_traploop return pz endfunc ; Produce the next value in the sequence ; float func Iterate(float pz) Generator.Iterate(pz) if (@p_trapalliterations == 1) ; we are skipping some iterations m_FractionalIteration = m_FractionalIteration - 1 ; one less to go before we trap while (m_FractionalIteration < 0.0) ; iterations all used up if (m_Trapping) ; we are currently trapping m_Trapping = false ; so stop m_FractionalIteration = m_FractionalIteration + @p_trapskip ; skip this many iterations else ; we aren't currently trapping m_Trapping = true ; so start m_FractionalIteration = m_FractionalIteration + @p_trapiter ; do this many iterations m_LoopCount = m_LoopCount - 1 endif endwhile if (m_LoopCount < 0) ; loops exhausted; always return 0 m_BailedOut = true ; and flag the sequence to end return 0 elseif (m_Trapping) return 1 else return 0 endif else return 1 ; always trapping endif endfunc protected: float m_FractionalIteration bool m_Trapping float m_LoopCount default: title = "Trap Select" int param v_dmj_trapselect caption = "Version (DMJ_TrapSelect)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_trapselect < 100 endparam int param p_trapalliterations caption = "Traps Apply To" default = 0 enum = "all iterations" "some iterations" hint = "This option, if set to 'some iterations', allows you to apply the orbit traps to only some iterations (with extra options shown below)." endparam float param p_trapstart caption = "Start Iteration" default = 0.0 hint = "This is the iteration at which to start watching for orbit traps." visible = @p_trapalliterations == 1 endparam float param p_trapiter caption = "Trap Iterations" default = 10.0 hint = "This is the number of iterations to watch for traps in a single block." visible = @p_trapalliterations == 1 endparam float param p_trapskip caption = "Skip Iterations" default = 10.0 hint = "This is the number of iterations to skip watching for traps, after a block of watched iterations." visible = @p_trapalliterations == 1 endparam float param p_traploop caption = "Loop Count" default = 1e20 hint = "This is the number of times to perform the trap/don't trap cycle; after this many trap/don't trap cycles, trapping will stop." visible = @p_trapalliterations == 1 endparam } class DMJ_GeneratorRandom(common.ulb:Generator) { ; This Generator produces a random sequence. The random number generator is very simple and quick. public: import "common.ulb" ; Constructor ; @param pparent = a reference to the object creating the new object; typically, 'this' func DMJ_GeneratorRandom(Generic pparent) Generator.Generator(pparent) endfunc ; Set up for a sequence of values ; ; This function will be called at the beginning of each ; sequence of values (e.g. at the beginning of each fractal ; orbit). ; @param pz = seed value for the sequence ; @return first value in the sequence float func Init(float pz) Generator.Init(pz) ; Base class implementation flags the sequence to end ; immediately. We don't want this (this sequence proceeds ; indefinitely) so clear the flag. m_BailedOut = false return pz endfunc ; Set up for a sequence of values ; ; This alternate form of Init will cause the Generator to ; start with its "default" sequence. This can be used if a ; generator allows a user to select a "seed" value itself. ; Such Generator classes should override this function. ; @return first value in the sequence float func InitDefault() return Init(@p_seed / 2147483648.0) endfunc ; Produce the next value in the sequence ; float func Iterate(float pz) Generator.Iterate(pz) pz = pz * 2147483648.0 pz = (pz * 1103515245 + 12345) % 2147483648.0 pz = pz / 2147483648.0 return pz endfunc protected: default: title = "Random" int param v_dmj_generatorrandom caption = "Version (DMJ_GeneratorRandom)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_generatorrandom < 100 endparam int param p_seed caption = "Seed" default = 51853571 hint = "Sets the seed for the random number generator. Each different seed produces a different sequence of random values." endparam } class DMJ_GeneratorRandom2(DMJ_GeneratorRandom) { ; This is identical to Random except it has a different default sequence. public: import "common.ulb" ; Constructor ; @param pparent = a reference to the object creating the new object; typically, 'this' func DMJ_GeneratorRandom2(Generic pparent) DMJ_GeneratorRandom.DMJ_GeneratorRandom(pparent) endfunc default: title = "Random (Alternate Seed 1)" int param v_dmj_generatorrandom2 caption = "Version (DMJ_GeneratorRandom2)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_generatorrandom2 < 100 endparam int param p_seed caption = "Seed" default = 8072177 hint = "Sets the seed for the random number generator. Each different seed produces a different sequence of random values." endparam } class DMJ_GeneratorRandom3(DMJ_GeneratorRandom) { ; This is identical to Random except it has a different default sequence. public: import "common.ulb" ; Constructor ; @param pparent = a reference to the object creating the new object; typically, 'this' func DMJ_GeneratorRandom3(Generic pparent) DMJ_GeneratorRandom.DMJ_GeneratorRandom(pparent) endfunc default: title = "Random (Alternate Seed 2)" int param v_dmj_generatorrandom3 caption = "Version (DMJ_GeneratorRandom3)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_generatorrandom3 < 100 endparam int param p_seed caption = "Seed" default = 654187327 hint = "Sets the seed for the random number generator. Each different seed produces a different sequence of random values." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; Convolution Filter classes class DMJ_ConvolutionFilter(common.ulb:Generic) { ; Base convolution filter class. ; ; The purpose of this class is to create the arrays required to ; support convolution filtering. This consists of an offset array, ; a weight array, an overall bias, and an overall multiplier. ; These should be set in the constructor. public: import "common.ulb" func DMJ_ConvolutionFilter(Generic pparent) Generic.Generic(pparent) endfunc func Init(complex pz) endfunc ; These values are available for the calling class to use directly. complex m_Offsets[] float m_Weights[] float m_Bias float m_Multiplier default: int param v_dmj_convolutionfilter caption = "Version (DMJ_ConvolutionFilter)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_convolutionfilter < 100 endparam } class DMJ_VariableConvolutionFilter(DMJ_ConvolutionFilter) { ; Base convolution filter class (position-dependent version). ; ; The purpose of this class is to create the arrays required to ; support convolution filtering. This consists of an offset array, ; a weight array, an overall bias, and an overall multiplier. ; These should be set in the Init() function. public: import "common.ulb" func DMJ_VariableConvolutionFilter(Generic pparent) DMJ_ConvolutionFilter.DMJ_ConvolutionFilter(pparent) endfunc default: int param v_dmj_variableconvolutionfilter caption = "Version (DMJ_VariableConvolutionFilter)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_variableconvolutionfilter < 100 endparam } class DMJ_GaussianBlur(DMJ_ConvolutionFilter) { ; Basic Gaussian blur filter. Provides even, soft blurring. public: import "common.ulb" func DMJ_GaussianBlur(Generic pparent) DMJ_ConvolutionFilter.DMJ_ConvolutionFilter(pparent) int count = (@p_samples*2+1) int elements = sqr(count) setLength(m_Offsets, elements) setLength(m_Weights, elements) m_Bias = 0.0 m_Multiplier = 0.0 int j = -@p_samples int k = 0 int l = 0 float o = 1.0 / (2.0*sqr(@p_samples / 3.0)) while (j <= @p_samples) k = -@p_samples while (k <= @p_samples) m_Offsets[l] = @p_radius*j/@p_samples + flip(@p_radius*k/@p_samples) m_Weights[l] = o/#pi * exp(-(sqr(j)+sqr(k))*o) m_Multiplier = m_Multiplier + m_Weights[l] l = l + 1 k = k + 1 endwhile j = j + 1 endwhile m_Multiplier = 1.0 / m_Multiplier endfunc default: title = "Gaussian Blur" int param v_dmj_gaussianblur caption = "Version (DMJ_GaussianBlur)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_gaussianblur < 100 endparam float param p_radius caption = "Filter Radius" default = 0.1 hint = "Sets the radius of the filter. A smaller radius will result in less blurring." endparam int param p_samples caption = "Sample Density" default = 9 min = 1 hint = "Sets the density of samples used to compute the blur. Increasing the density will dramatically increase the rendering time (increasing with 4x the square of the density) but for very wide blurs you will need to do this to get even blurring." endparam } class DMJ_UnsharpMask(DMJ_ConvolutionFilter) { ; Basic Unsharp Mask filter. Provides even, dispersed sharpening. (This is the opposite of Gaussian Blur.) public: import "common.ulb" func DMJ_UnsharpMask(Generic pparent) DMJ_ConvolutionFilter.DMJ_ConvolutionFilter(pparent) int count = (@p_samples*2+1) int elements = sqr(count) setLength(m_Offsets, elements) setLength(m_Weights, elements) m_Bias = 0.0 m_Multiplier = 0.0 int j = -@p_samples int k = 0 int l = 0 float o = 1.0 / (2.0*sqr(@p_samples / 3.0)) while (j <= @p_samples) k = -@p_samples while (k <= @p_samples) m_Offsets[l] = @p_radius*j/@p_samples + flip(@p_radius*k/@p_samples) m_Weights[l] = -( o/#pi * exp(-(sqr(j)+sqr(k))*o) ) * @p_sharpness if (j == 0 && k == 0) m_Weights[l] = m_Weights[l] + 1.0 endif m_Multiplier = m_Multiplier + m_Weights[l] l = l + 1 k = k + 1 endwhile j = j + 1 endwhile m_Multiplier = 1.0 / m_Multiplier endfunc default: title = "Unsharp Mask" int param v_dmj_unsharpmask caption = "Version (DMJ_UnsharpMask)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_unsharpmask < 100 endparam float param p_sharpness caption = "Sharpening Amount" default = 0.25 hint = "Sets the amount of the sharpening effect." endparam float param p_radius caption = "Filter Radius" default = 0.1 hint = "Sets the radius of the filter. A smaller radius will result in less blurring." endparam int param p_samples caption = "Sample Density" default = 9 min = 1 hint = "Sets the density of samples used to compute the blur. Increasing the density will dramatically increase the rendering time (increasing with 4x the square of the density) but for very wide blurs you will need to do this to get even blurring." endparam } class DMJ_MotionBlur(DMJ_VariableConvolutionFilter) { ; Basic Unsharp Mask filter. Provides even, dispersed sharpening. (This is the opposite of Gaussian Blur.) public: import "common.ulb" func DMJ_MotionBlur(Generic pparent) DMJ_VariableConvolutionFilter.DMJ_VariableConvolutionFilter(pparent) int elements = (@p_samples*2+1) setLength(m_Offsets, elements) setLength(m_Weights, elements) endfunc func Init(complex pz) m_Bias = 0.0 m_Multiplier = 0.0 int j = -@p_samples int l = 0 complex v = @p_radius * ((0,1) ^ (@p_angle / 90.0)) / @p_samples complex c = 0 complex t if (@p_type == 1) c = pz - @p_center c = atan2(c) + flip(cabs(c)) elseif (@p_type == 2) v = v * (pz - @p_center) endif while (j <= @p_samples) if (@p_type == 1) t = v * j t = real(t) + flip(imag(t)*imag(c)) t = t + c t = cos(real(t))*imag(t) + flip(sin(real(t))*imag(t)) m_Offsets[l] = t - (pz - @p_center) else m_Offsets[l] = v * j endif if (@p_style == 0) m_Weights[l] = 1 elseif (@p_style == 1) m_Weights[l] = @p_samples + 1 - abs(j) elseif (@p_style == 2) if (j <= 0) m_Weights[l] = @p_samples + 1 - abs(j) else m_Weights[l] = 0 endif endif if (j == 0) m_Weights[l] = m_Weights[l] + @p_centerweight * @p_samples endif m_Multiplier = m_Multiplier + m_Weights[l] l = l + 1 j = j + 1 endwhile m_Multiplier = 1.0 / m_Multiplier endfunc default: title = "Motion/Radial/Zoom Blur" int param v_dmj_motionblur caption = "Version (DMJ_MotionBlur)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_motionblur < 100 endparam int param p_type caption = "Blur Type" default = 0 enum = "motion blur" "radial blur" "zoom blur" hint = "Sets the type of blur. Motion blur is a blur in a single direction regardless of position. Radial and zoom blurs changes the direction of the blur from one position to the next." endparam int param p_style caption = "Blur Style" default = 0 enum = "normal" "weighted" "wind" hint = "Sets the style of the blur. 'Normal' gives a standard motion blur; 'weighted' gives a softer effect; 'wind' gives a one-sided blur." endparam float param p_centerweight caption = "Center Weight" default = 0 hint = "Sets the extra weight that will be applied to the center position. Increasing this will reduce the amount of blur and allow the original image to appear more crisply." endparam float param p_angle caption = "Blur Angle" default = 0.0 hint = "Sets the angle of the blur. Note that for radial and zoom blurs, this is relative to the 'natural' angle for each position." endparam complex param p_center caption = "Blur Center" default = (0,0) hint = "Sets the center of the blur." endparam float param p_radius caption = "Filter Radius" default = 0.1 hint = "Sets the radius of the filter. A smaller radius will result in less blurring." endparam int param p_samples caption = "Sample Density" default = 9 min = 1 hint = "Sets the density of samples used to compute the blur. Increasing the density will dramatically increase the rendering time (increasing with 4x the square of the density) but for very wide blurs you will need to do this to get even blurring." endparam } ; ----------------------------------------------------------------------------------------------------------------------------- ; ColorMerge classes class DMJ_FastNormalMerge(common.ulb:ColorMerge) { ; This color blend class only does Normal merge mode, but it supports IsOpaque() for speed optimizations. public: func DMJ_FastNormalMerge(Generic pparent) ColorMerge.ColorMerge(pparent) endfunc color func Merge(color pbottom, color ptop) return mergenormal(pbottom, ptop) endfunc bool func IsOpaque(color ptop) return (alpha(ptop) == 1.0) endfunc default: title = "Fast Normal Merge" int param v_dmj_fastnormalmerge caption = "Version (DMJ_FastNormalMerge)" default = 100 hint = "This version parameter is used to detect when a change has been made to the formula that is incompatible with the previous version. When that happens, this field will reflect the old version number to alert you to the fact that an alternate rendering is being used." visible = @v_dmj_fastnormalmerge < 100 endparam }