1 import ddf.minim.Minim 2 import ddf.minim.analysis.FFT 3 import org.openrndr.Fullscreen 4 import org.openrndr.Program 5 import org.openrndr.UnfocusBehaviour 6 import org.openrndr.animatable.Animatable 7 import org.openrndr.animatable.easing.Easing 8 import org.openrndr.application 9 import org.openrndr.color.ColorRGBa 10 import org.openrndr.draw.LineCap 11 import org.openrndr.math.IntVector2 12 import org.openrndr.extensions.Screenshots 13 import org.openrndr.extra.gui.GUI 14 import org.openrndr.extra.olive.Olive 15 import org.openrndr.extra.parameters.* 16 import org.openrndr.extra.shadestyles.linearGradient 17 import org.openrndr.extra.shadestyles.radialGradient 18 import org.openrndr.ffmpeg.ScreenRecorder 19 import org.openrndr.math.Polar 20 import org.openrndr.math.Vector2 21 import org.openrndr.math.asDegrees 22 import org.openrndr.panel.ControlManager 23 import org.openrndr.panel.layout 24 import org.openrndr.panel.style.* 25 import org.openrndr.shape.shape 26 import java.io.File 27 import java.io.FileInputStream 28 import java.io.InputStream 29 import java.lang.Math.random 30 import kotlin.math.acos 31 import kotlin.math.pow 32 import kotlin.math.sqrt 33 34 fun main() = application { 35 36 // The setup 37 configure { 38 width = 768 39 height = 768 40 position = IntVector2(400, 200) 41 fullscreen = Fullscreen.DISABLED 42 windowResizable = true 43 title = "OpenRNDR Tutorial" 44 unfocusBehaviour = UnfocusBehaviour.THROTTLE // behaviour when program window is unfocused. NORMAL keeps it running at full speed 45 46 } 47 48 // The draw, executed once 49 program { 50 val R = width / 3.0 51 val nLines = 100 52 53 val animation = object : Animatable() { 54 var r = 475.89 55 } 56 57 // 58 // GUI 59 // 60 61 val gui = GUI() 62 val settings = object { 63 @DoubleParameter("Chord Length", 0.0, 770.0) 64 var refR: Double = 400.0 65 66 @DoubleParameter("Stroke Weight", 0.1, 5.0) 67 var strokeWeight: Double = 0.5 68 69 @DoubleParameter("Animation Duration", 0.0, 10000.0) 70 var animationDuration: Double = 1000.0 71 72 @DoubleParameter("Animation Amplitude", 0.0, 50.0) 73 var animationAmplitude: Double = 1.0 74 75 @BooleanParameter("Animate") 76 var animate = false 77 78 @BooleanParameter("Curves") 79 var curves = false 80 81 @BooleanParameter("Relative curve control") 82 var rcc = false 83 84 @XYParameter("Curve control", -400.0, 400.0, -400.0, 400.0, showVector = true, invertY = false) 85 var controlPoint = Vector2(0.0, 0.0) 86 87 @DoubleParameter("Curve control weighting", 0.0, 1.0) 88 var controlWeight = 0.5 89 } 90 91 val colors = object { 92 @ColorParameter("Background color 1", order = 0) 93 var bgcol1 = randomColor() 94 95 @ColorParameter("Background color 2", order = 1) 96 var bgcol2 = randomColor() 97 98 @ColorParameter("Circle color 1", order = 3) 99 var circlecol1 = randomColor() 100 101 @ColorParameter("Circle color 2", order = 4) 102 var circlecol2 = randomColor() 103 104 @ColorParameter("Chords color", order = 5) 105 var chordcol = randomColor() 106 107 @ActionParameter("Randomize Colors", order = 6) 108 fun randomizeColors() { 109 bgcol1 = randomColor() 110 bgcol2 = randomColor() 111 circlecol1 = randomColor() 112 circlecol1 = randomColor() 113 chordcol = randomColor() 114 } 115 } 116 117 gui.compartmentsCollapsedByDefault = false 118 gui.add(settings, "Settings") 119 gui.add(colors, "Colors") 120 extend(gui) 121 122 123 // Extensions. With optional { options setup } 124 extend(Screenshots()) { 125 // scale = 4.0 126 } 127 extend(ScreenRecorder()) {} 128 129 // 130 // AUDIO 131 // 132 /* 133 134 val minim = Minim(object : Object() { 135 fun sketchPath(fileName: String): String { 136 return fileName 137 } 138 fun createInput(fileName: String): InputStream { 139 return FileInputStream(File(fileName)) 140 } 141 }) 142 val lineIn = minim.lineIn 143 val fft = FFT(lineIn.bufferSize(), lineIn.sampleRate()) 144 */ 145 146 147 // The actual drawing loop 148 extend { 149 150 151 152 //Background 153 // drawer.shadeStyle = radialGradient(SketchColors.eggplant, SketchColors.seaGreen, length = 0.5) 154 drawer.shadeStyle = radialGradient(colors.bgcol1, colors.bgcol2, length = 0.5) 155 drawer.rectangle(0.0, 0.0, width.toDouble(), height.toDouble()) 156 157 if (settings.animate) 158 animation.updateAnimation() 159 160 if (!animation.hasAnimations()) { 161 animation.apply { 162 ::r.animate(sqrt(settings.animationAmplitude), settings.animationDuration.toLong(), Easing.CubicInOut) 163 ::r.complete() 164 ::r.animate(-sqrt(settings.animationAmplitude), settings.animationDuration.toLong(), Easing.CubicInOut) 165 ::r.complete() 166 } 167 } 168 169 drawer.stroke = null 170 171 drawer.translate(width / 2.0, height / 2.0) 172 173 drawer.shadeStyle = radialGradient(colors.circlecol1, colors.circlecol2, length = 0.5) 174 // drawer.fill = SketchColors.eggplant 175 drawer.circle(0.0, 0.0, R) 176 177 val startPoint = Vector2(R, 0.0) 178 val chordTheta: Double = if (settings.animate) 179 triangulate(R, R, animation.r/10 + settings.refR) 180 else 181 triangulate(R, R, settings.refR) 182 183 var theta = 0.0 184 var previousPoint: Vector2 185 var nextPoint = Vector2.ZERO 186 187 if (!chordTheta.isNaN() && chordTheta != 0.0) { 188 189 190 val s = shape { 191 val c = contour { 192 moveTo(startPoint) 193 194 for (i in 0..nLines) { 195 theta += chordTheta 196 previousPoint = nextPoint 197 nextPoint = Vector2.fromPolar(Polar(theta.asDegrees, R)) 198 199 if (settings.curves) { 200 if (settings.rcc) { 201 val p = settings.controlWeight 202 curveTo((nextPoint.times(p) - previousPoint.times(1.0 - p)) / 2.0, nextPoint) 203 } else { 204 curveTo(settings.controlPoint, nextPoint) 205 } 206 } else 207 lineTo(nextPoint) 208 } 209 210 // close() 211 } 212 213 } 214 // drawer.fill = SketchColors.lightSalmon 215 drawer.stroke = colors.chordcol 216 // drawer.shadeStyle = linearGradient(ColorRGBa.BLUE, ColorRGBa.RED, rotation = -90.0) 217 drawer.strokeWeight = settings.strokeWeight 218 drawer.lineCap = LineCap.BUTT 219 // drawer.contour(c) 220 drawer.shape(s) 221 // drawer.contour(s.outline) 222 223 } 224 } 225 226 227 228 // 229 // UI 230 // 231 232 233 } 234 } 235 236 fun triangulate(a: Double, b: Double, c: Double):Double { 237 // Returns an angle in radians 238 // a and b are adjacent sides 239 // c is the opposite side to the desired angle 240 return -acos((a.pow(2.0) + b.pow(2.0) - c.pow(2.0))/(2 * a * b)) 241 } 242 243 244 class SketchColors { 245 companion object { 246 val seaGreen = ColorRGBa.fromHex(0x13efb4) 247 val mintGreen = ColorRGBa.fromHex(0x98FF98) 248 val eggplant = ColorRGBa.fromHex(0x60495A) 249 val lightSalmon = ColorRGBa.fromHex(0xFF9B71) 250 } 251 } 252 253 fun randomColor(): ColorRGBa { 254 return ColorRGBa(random(), random(), random()) 255 }