|
| 1 | +# Ball of Confusion Sketch see original by Ben Notiaranni |
| 2 | +# http://lazydog-bookfragments.blogspot.com/2008/12/orb-code.html |
| 3 | +NUMBER_OF_HAIRS = 10_000 |
| 4 | +BODY_R = 25.0 |
| 5 | +MIN_HAIR_LENGTH = 24.0 |
| 6 | +SHORT_HAIR_LENGTH = 30.0 |
| 7 | +LONG_HAIR_LENGTH = 10.0 |
| 8 | +LONG_HAIR_MAX_LENGTH = 30.0 |
| 9 | +LONG_DENDRITE_LENGTH = 20.0 |
| 10 | +LONG_DENDRITE_MAX_LENGTH = 60.0 |
| 11 | + |
| 12 | +attr_reader :rotation_history, :g_frame, :g_lights, :ball_theta, :ball_phi |
| 13 | +attr_reader :camera_r, :camera_theta, :camera_phi, :graphics, :cr |
| 14 | + |
| 15 | +def key_pressed |
| 16 | + return unless key == 'l' |
| 17 | + @g_lights = !g_lights |
| 18 | +end |
| 19 | + |
| 20 | +def settings |
| 21 | + size(800, 600, P3D) |
| 22 | + smooth |
| 23 | +end |
| 24 | + |
| 25 | +def setup |
| 26 | + sketch_title 'Ball of Confusion' |
| 27 | + colorMode(RGB, 1.0) |
| 28 | + background(0.0) |
| 29 | + @rotation_history = RotationHistory.new(200) |
| 30 | + @g_lights = true |
| 31 | + @g_frame = 0 |
| 32 | + @camera_r = 130.0 |
| 33 | + @camera_phi = PI / 4 |
| 34 | + @camera_theta = 0.0 |
| 35 | + @ball_theta = 0 |
| 36 | + @ball_phi = 0 |
| 37 | + @graphics = self.g |
| 38 | + # blend_mode(DIFFERENCE) |
| 39 | +end |
| 40 | + |
| 41 | +def draw |
| 42 | + @camera_r = 180.0 + (sin(g_frame/50.0) + cos(g_frame / 57)) * 25 |
| 43 | + @g_frame += 1.0 |
| 44 | + @ball_theta += 2.0 * sin(g_frame/53.0) |
| 45 | + @ball_phi += 2.0 * sin(g_frame/83.0) |
| 46 | + rotation_history.push(ball_theta, ball_phi) |
| 47 | + background(0.0) |
| 48 | + camera_spherical(camera_r, camera_phi, -camera_theta) |
| 49 | + random_seed(6) |
| 50 | + @cr = 0.0 |
| 51 | + if (g_lights) |
| 52 | + @cr = 1.0 |
| 53 | + lights |
| 54 | + x = sin(g_frame / 30.0) |
| 55 | + y = sin(g_frame / 37.0) |
| 56 | + z = sin(g_frame / 43.0) |
| 57 | + directional_light(1.0, 1.0, 1.0, x, y, z) |
| 58 | + directional_light(0.3, 0.3, 0.3, -x, -y, -z) |
| 59 | + else |
| 60 | + @cr = 0.0 |
| 61 | + no_lights |
| 62 | + end |
| 63 | + draw_body(BODY_R, cr * 1.0, cr * 0.4, cr * 0.1, 1.0) |
| 64 | + begin_shape |
| 65 | + draw_spikes(30, BODY_R, LONG_HAIR_LENGTH, LONG_HAIR_MAX_LENGTH) |
| 66 | + draw_dendrites(15, BODY_R, LONG_DENDRITE_LENGTH, LONG_DENDRITE_MAX_LENGTH) |
| 67 | + draw_fuzz(10_000, MIN_HAIR_LENGTH, SHORT_HAIR_LENGTH) |
| 68 | + end_shape |
| 69 | + save_frame(data_path'####.png') if frame_count > 100 |
| 70 | +end |
| 71 | + |
| 72 | +def draw_dendrites(count, br, l1, l2) |
| 73 | + count.times do |
| 74 | + length = rand(l1..l2) |
| 75 | + theta = rand(0.0..360.0) |
| 76 | + phi = rand(0.0..180.0) |
| 77 | + push_matrix |
| 78 | + rotation_history.record(0).draw(graphics) |
| 79 | + draw_patch(br + 0.1, theta, phi, 4.radians, 16) |
| 80 | + pop_matrix |
| 81 | + c1 = color(sin(g_frame / 100.0).abs, sin(g_frame / 123.0).abs, sin(g_frame / 176.0).abs, 1.0) |
| 82 | + c2 = color(sin(g_frame / 57.0).abs, sin(g_frame / 23.0).abs, sin(g_frame / 67.0).abs, 0.0) |
| 83 | + hair = Hair.new(length*2, br, theta + rand, phi + rand, c1, c2, 1.0) |
| 84 | + hair.draw |
| 85 | + end |
| 86 | +end |
| 87 | + |
| 88 | +def draw_spikes(count, br, l1, l2) |
| 89 | + count.times do |
| 90 | + theta = rand(0.0..360.0) |
| 91 | + phi = rand(0.0..180.0) |
| 92 | + push_matrix |
| 93 | + rotation_history.record(0).draw(graphics) |
| 94 | + draw_patch(br + 0.1, theta, phi, 4.radians, 16) |
| 95 | + pop_matrix |
| 96 | + length = rand(l1..l2) |
| 97 | + (0..10).each do |j| |
| 98 | + l = (j == 9) ? length : rand(l1..length) |
| 99 | + hair = Hair.new(l, br, theta + rand, phi + rand, color(1.0, 1.0, 1.0, 1.0), color(1.0, 0.4, 0.1, 0.1), 1.0) |
| 100 | + hair.draw |
| 101 | + end |
| 102 | + end |
| 103 | +end |
| 104 | + |
| 105 | +def draw_fuzz(count, r1, r2) |
| 106 | + push_matrix |
| 107 | + rotation_history.record(0).draw(graphics) |
| 108 | + begin_shape(LINES) |
| 109 | + count.times do |
| 110 | + theta = rand(0..TWO_PI) |
| 111 | + phi = rand(0..PI) |
| 112 | + x1 = cos(theta) * sin(phi) |
| 113 | + y1 = sin(theta) * sin(phi) |
| 114 | + z1 = cos(phi) |
| 115 | + r = rand(r1..r2) |
| 116 | + c1 = 1.0 |
| 117 | + c2 = 1.0 * 0.4 |
| 118 | + stroke(c1, c1, c1, 0.5) |
| 119 | + vertex(x1 * r1, y1 * r1, z1 * r1) |
| 120 | + stroke(c2, c2, c2, 0.0) |
| 121 | + vertex(x1 * r, y1 * r, z1 * r) |
| 122 | + end |
| 123 | + end_shape |
| 124 | + pop_matrix |
| 125 | +end |
| 126 | + |
| 127 | +def draw_body(r, red, green, blue, alpha) |
| 128 | + no_stroke |
| 129 | + fill(red, green, blue, alpha) |
| 130 | + sphere(r) |
| 131 | +end |
| 132 | + |
| 133 | +def camera_spherical(r, phi, theta) |
| 134 | + x = r * cos(theta) * sin(phi) |
| 135 | + y = r * sin(theta) * sin(phi) |
| 136 | + z = r * cos(phi) |
| 137 | + nx = cos(theta) * cos(phi) |
| 138 | + ny = sin(theta) * cos(phi) |
| 139 | + nz = -sin(phi) |
| 140 | + camera(x, y, z, 0.0, 0.0, 0.0, nx, ny, nz) |
| 141 | +end |
| 142 | + |
| 143 | +def draw_patch(r, theta, phi, ar, smoothness) |
| 144 | + push_matrix |
| 145 | + rotate(theta, 0.0, 0.0, 1.0) |
| 146 | + rotate(phi, 0.0, 1.0, 0.0) |
| 147 | + begin_shape(TRIANGLE_FAN) |
| 148 | + x = r * sin(ar) |
| 149 | + z = r * cos(ar) |
| 150 | + fill(1.0, 1.0, 1.0, 1.0) |
| 151 | + vertex(0.0, 0.0, r) |
| 152 | + fill(1.0, 0.0, 0.0, 0.0) |
| 153 | + smoothness.times do |i| |
| 154 | + vertex(x * cos(TWO_PI * i / smoothness), x * sin(TWO_PI * i / smoothness), z) |
| 155 | + end |
| 156 | + end_shape |
| 157 | + |
| 158 | + pop_matrix |
| 159 | +end |
| 160 | + |
| 161 | +class Hair |
| 162 | + attr_reader :length, :r, :theta, :phi, :sections |
| 163 | + attr_reader :r1, :g1, :b1, :a1, :r2, :g2, :b2, :a2 |
| 164 | + |
| 165 | + def initialize(length, r, theta, phi, c1, c2, smoothness) |
| 166 | + @length = length |
| 167 | + @r = r |
| 168 | + @theta = theta.radians |
| 169 | + @phi = phi.radians |
| 170 | + @r1 = red(c1) |
| 171 | + @g1 = green(c1) |
| 172 | + @b1 = blue(c1) |
| 173 | + @a1 = alpha(c1) |
| 174 | + @r2 = red(c2) |
| 175 | + @g2 = green(c2) |
| 176 | + @b2 = blue(c2) |
| 177 | + @a2 = alpha(c2) |
| 178 | + @sections = (length / smoothness).round |
| 179 | + end |
| 180 | + |
| 181 | + def draw |
| 182 | + push_matrix |
| 183 | + begin_shape(LINE_STRIP) |
| 184 | + sections.times do |i| |
| 185 | + dh = i.to_f / sections |
| 186 | + h = r + length * dh |
| 187 | + x = h * cos(@theta) * sin(@phi) |
| 188 | + y = h * sin(@theta) * sin(@phi) |
| 189 | + z = h * cos(@phi) |
| 190 | + theta = rotation_history.record(-i).theta.radians |
| 191 | + phi = rotation_history.record(-i).phi.radians |
| 192 | + x1= x * cos(phi) + z * sin(phi) |
| 193 | + y1 = y |
| 194 | + z1 = -x * sin(phi) + z * cos(phi) |
| 195 | + x = x1 * cos(theta) - y1 * sin(theta) |
| 196 | + y = x1 * sin(theta) + y1 * cos(theta) |
| 197 | + z = z1 |
| 198 | + stroke(r1 + (r2 - r1) * dh, g1 + (g2 - g1) * dh, b1 + (b2 - b1) * dh, a1 + (a2 - a1) * dh) |
| 199 | + vertex(x, y, z) |
| 200 | + end |
| 201 | + end_shape |
| 202 | + pop_matrix |
| 203 | + end |
| 204 | +end |
| 205 | + |
| 206 | +# Struct to store rotations, with draw(graphics) method to return rotations |
| 207 | +Record = Struct.new(:theta, :phi) do |
| 208 | + def draw(gfx) |
| 209 | + gfx.rotate_z(theta) |
| 210 | + gfx.rotate_y(phi) |
| 211 | + end |
| 212 | +end |
| 213 | + |
| 214 | +class RotationHistory |
| 215 | + attr_reader :history, :size, :frame |
| 216 | + |
| 217 | + def initialize(size = 100) |
| 218 | + @history = (0..size).map { Record.new(0, 0) } |
| 219 | + @size = size |
| 220 | + @frame = 0 |
| 221 | + end |
| 222 | + |
| 223 | + def push(theta, phi) |
| 224 | + @frame += 1 |
| 225 | + history[frame % size].theta = theta |
| 226 | + history[frame % size].phi = phi |
| 227 | + end |
| 228 | + |
| 229 | + def record(idx) |
| 230 | + history[(frame + idx).abs % size] |
| 231 | + end |
| 232 | +end |
0 commit comments