// BUTTERFLY — the frame is split down the middle and the two halves are // WINGS, hinged on the centre line and flapping on the beat: they swing // back through the glass and forward again, foreshortening and dimming as // they go, with a hairline of the flat picture left standing between them // as the body. // // Pattern taught: the plane-in-3D helper (`plane_uv` below, the family's // shared block — the reference copy lives in the Perspective doc) called // TWICE with a shared hinge and opposite angles. Camera at the origin // looking down -z; the RAY and the EYE are pushed into each wing's own // frame by the transposed rotation, where the wing is z = 0 and one divide // gives the hit. At zero flap both wings lie flat and the pair returns the // frame EXACTLY, so a parked butterfly is the untouched picture. // // The flap is a sine of BAR PHASE — a 0..1 signal, so it is locked to the // music and bounded however long the deck has been running, never the raw // beat count. RATE picks a WHOLE number of flaps per bar, which is what // keeps the wing continuous across the bar line. { name: "Butterfly" engine: "screen" seed: 17 input0: "test" speed: 1.0 beat_pulse: 0.6 beat_rate: 1.0 bar_beats: 4 glow: 1.0 // PERFORMANCE DIALS — mid-knob = the stock look, exact. p0: 0.5 p1: 0.5 p2: 0.5 dials: [ {name: "FLAP", bind: "p0", default: 0.5}, {name: "RATE", bind: "p1", default: 0.5}, {name: "TILT", bind: "p2", default: 0.5} ] color_bg: #x05060e color_a: #xffb040 color_b: #x4030a0 color_c: #xfff0d8 stages: [ {kind: "bloom", threshold: 0.6, strength: 1.05, levels: 2} ] shader: draw.DrawVjFxScreen { // ---- THE SHARED HELPER: one ray, one rotated video plane -------- // The plane is the rectangle of half-extents (aspect, 1) * 0.5/f // whose HINGE sits at world (piv.x, piv.y, -d), spun about that // hinge by the Euler angles `ang` (Rz then Ry then Rx, radians). // Returns (plane u, plane v, on-quad 0/1, front-facing 0/1). plane_uv: fn(uv: vec2, ang: vec3, piv: vec2, d: float, f: float) -> vec4 { // The `screen` family's shader carries no aspect uniform (only // the duo and marcher families do), so the plane takes the VJ // canvas aspect. Keep it in step with the output if that ever // stops being 16:9. let a = 1.7777 let c0 = cos(ang.x) let s0 = sin(ang.x) let c1 = cos(ang.y) let s1 = sin(ang.y) let c2 = cos(ang.z) let s2 = sin(ang.z) // The ray through this fragment (y up) and the eye, both // measured from the hinge. let rd = vec3((uv.x - 0.5) * a, 0.5 - uv.y, 0.0 - f) let ro = vec3(0.0 - piv.x, 0.0 - piv.y, d) let r1 = vec3(rd.x * c2 + rd.y * s2, rd.y * c2 - rd.x * s2, rd.z) let o1 = vec3(ro.x * c2 + ro.y * s2, ro.y * c2 - ro.x * s2, ro.z) let r2 = vec3(r1.x * c1 - r1.z * s1, r1.y, r1.x * s1 + r1.z * c1) let o2 = vec3(o1.x * c1 - o1.z * s1, o1.y, o1.x * s1 + o1.z * c1) let r3 = vec3(r2.x, r2.y * c0 + r2.z * s0, r2.z * c0 - r2.y * s0) let o3 = vec3(o2.x, o2.y * c0 + o2.z * s0, o2.z * c0 - o2.y * s0) // Intersect z = 0. A ray running parallel to the plane is // NUDGED, never divided by zero — it lands far off the quad. let den = r3.z + (1.0 - step(0.0001, abs(r3.z))) * 0.001 let k = 0.0 - o3.z / den let hx = o3.x + k * r3.x + piv.x let hy = o3.y + k * r3.y + piv.y let pu = hx * f / a + 0.5 let pv = 0.5 - hy * f let onq = step(0.0, pu) * step(pu, 1.0) * step(0.0, pv) * step(pv, 1.0) * step(0.001, k) return vec4(pu, pv, onq, step(0.0, o3.z)) } fx_color: fn(uv: vec2, content: vec4, cmix: float) -> vec4 { let fl = 1.12 // THE FLAP: a sine of BAR PHASE (0..1, bounded by construction // — never the raw beat count), at a WHOLE number of flaps per // bar so the wing is continuous across the bar line. RATE = 1 // to 8 flaps per bar (mid-knob = 4, i.e. one per beat at the // stock 4/4), FLAP = 0.25..1.25 rad of swing. let rate = floor(1.0 + clamp(self.user.y, 0.0, 1.0) * 7.0) let amp = 0.25 + clamp(self.user.x, 0.0, 1.0) let ph = self.sig.x * 6.2831853 * rate let fp = amp * sin(ph) // The body pitches with TILT and drifts on two slow detuned // sines, so it never sits perfectly still and never wanders. let tm = self.time_beat.x let pitch = (self.user.z - 0.5) * 1.1 + 0.06 * sin(tm * 0.27) let roll = 0.05 * sin(tm * 0.19 + 2.2) // Both wings hinge on the centre line and swing in opposite // senses, which is what makes the pair read as one creature. let pl = self.plane_uv(uv, vec3(pitch, 0.0 - fp, roll), vec2(0.0, 0.0), 1.0, fl) let pr = self.plane_uv(uv, vec3(pitch, fp, roll), vec2(0.0, 0.0), 1.0, fl) let hitl = pl.z * step(pl.x, 0.5) let hitr = pr.z * step(0.5, pr.x) let cl = self.tex0.sample_as_bgra(clamp(vec2(pl.x, pl.y), vec2(0.0, 0.0), vec2(1.0, 1.0))) let cr = self.tex0.sample_as_bgra(clamp(vec2(pr.x, pr.y), vec2(0.0, 0.0), vec2(1.0, 1.0))) // A wing dims as it turns off-axis (exactly 1 at full spread) // and the leading edge catches col_a as it comes forward. let sh = 0.5 + 0.5 * abs(cos(fp)) let lead = clamp(0.0 - fp, 0.0, 1.5) let wing = mix(cl.xyz, cr.xyz, step(0.5, hitr)) * sh + self.col_a.xyz * (lead * 0.10) // The void the butterfly flies in. let vg = clamp(1.0 - length(vec2((uv.x - 0.5) * 1.7777, uv.y - 0.5)) * 1.2, 0.0, 1.0) let bg = self.col_bg.xyz + self.col_b.xyz * (0.10 * vg * vg) let mut rgb = mix(bg, wing, clamp(hitl + hitr, 0.0, 1.0)) // THE BODY: a hairline of the untouched frame standing on the // hinge line, so the two wings read as joined. let body = 1.0 - smoothstep(0.004, 0.011, abs(uv.x - 0.5)) rgb = mix(rgb, content.xyz + self.col_c.xyz * 0.12, body) // Without real content (cmix 0) the fallback pattern is all // there is, so lift it a little to keep the frame alive. let lift = 1.0 + (1.0 - clamp(cmix, 0.0, 1.0)) * 0.18 return vec4(rgb * (self.fog.y * lift), 1.0) } } }