import sys, time, ctypes
import pylase as ol
from math import *
from utils import *
import svg, mesh

class Stars(Effect):
    def __init__(self, demo, start, end):
        Effect.__init__(self, demo, start, end)
        self.random.seed(1)

        bp = [0, 4, 9, 12, 16, 22, 26, 29, 32, 36, 42, 44, 48, 54, 56, 58]

        self.stars = []

        # Synced stars
        for off in (16, 32):
            for b in bp:
                b = off + b / 4.0
                self.stars.append((b, 0, 
                                   self.random.uniform(-self.demo.w, self.demo.w),
                                   self.random.uniform(0.07, self.demo.h)))

        # Reflected stars
        for i in (2, 12, 16, 19, 25, 27): 
            b, c, x, y = self.stars[i]
            self.stars.append((b, c, x, -y))

        # Upper stars
        for i in range(48):
            self.stars.append((48, self.random.uniform(0, 1),
                               self.random.uniform(-self.demo.w, self.demo.w),
                               self.random.uniform(self.demo.h, self.demo.h + 1)))

        # Streaks
        for i in range(260):
            self.stars.append((mapf(self.random.random() ** 0.6, 64.2, 71.8), 0,
                               self.random.gauss(0, 0.2),
                               self.random.gauss(0, 0.2) + 1.0))


    def fuzz(self, coord, color):
        x, y = coord
        x += sin((y - self.ty) * 60 + self.t) * 0.01
        return (x, y), color

    def render(self, t):
        if t < 63.8:
            self.render_main(t)
        else:
            self.render_streaks(t)

    def render_main(self, t):
        ol.pushMatrix3()
        self.ty = -1 * ss(clamp1(unmapf(t, 48, 63)))
        ol.translate((0, self.ty))

        for grp in self.demo.svg_mountains:
            off, distort = map(float, grp.name.split(","))
            off *= 0.8
            off += 3
            bright = clamp1(unmapf(t, off, off+1))
            ol.pushColor()
            ol.multColor(ol.C_GREY(bright * 255))
            if distort:
                ol.setPixelShader(self.fuzz)
            grp.draw()
            ol.popColor()
            ol.setPixelShader(None)

        for start, endoff, x, y in self.stars:
            if t < start:
                continue
            bright = clamp1(0.6 + 0.4 * shuf(t + 1000 * (start + endoff)))
            bright *= clamp1(unmapf(t, 12, 32))
            if y < 0:
                bright *= 0.5
                ol.setPixelShader(self.fuzz)
            ol.pushColor()
            ol.multColor(ol.C_GREY(int(bright * 255)))
            ol.dot((x, y), 3, 0xeeeeff)
            ol.popColor()
            ol.setPixelShader(None)

        ol.popMatrix3()

    def render_streaks(self, t):
        speed = ((clamp1(unmapf(t, 63.8, 69)) ** 2) + 0.05) * 1.4
        nstars = []
        for start, endoff, x, y in self.stars:
            if t < start:
                nstars.append((start, endoff, x, y))
                continue
            start_adj = start
            if start_adj < 63.8:
                start_adj = 63.8
            sfac = mapf(shuf(start + endoff), 2.5, 3)
            dt = (t - start_adj) * speed
            dte = max(0, t - start_adj - 0.2) * speed
            x1 = x * exp(dt**sfac)
            y1 = (y-1) * exp(dt**sfac)
            x0 = x * exp(dte**sfac)
            y0 = (y-1) * exp(dte**sfac)
            mm = max(abs(x0), abs(y0))
            if mm > 1.0:
                continue
            mm = max(abs(x1), abs(y1))
            if mm > 1.0:
                x1 /= mm
                y1 /= mm
            bright = clamp1(0.6 + 0.4 * shuf(t + 1000 * (start + endoff)))
            bright = mapf(clamp1(unmapf(t, 64, 67)), bright, 1.0)
            bright *= clamp1(unmapf(t, 73, 72))
            bright *= clamp1(unmapf(t, start, start+0.3))
            ol.line((x0, y0), (x1, y1), ol.C_GREY(255 * bright))
            nstars.append((start, endoff, x, y))
        # Prune out of bounds stars
        self.stars = nstars

class Logo(Effect):
    BURN_LEN = 14.0
    def __init__(self, demo, start, end):
        Effect.__init__(self, demo, start, end)

        self.sparks = []

        # Big sparks
        for i in range(int(self.BURN_LEN * 80)):
            t = i / 80.0
            lase = self.demo.svg_logo.lase[0]
            x, y = lase.evaluate(t / self.BURN_LEN)
            self.sparks.append((t, self.random.uniform(0.2, 0.7),
                                (self.random.uniform(0.1, 0.18), x, y,
                                self.random.uniform(-1, 1),
                                self.random.uniform(-1, 1))))

        # Bursts
        for t in (0, 8):
            lase = self.demo.svg_logo.lase[0]
            x, y = lase.evaluate(t / self.BURN_LEN)
            for i in range(30):
                self.sparks.append((t, 1.5,
                                    (self.random.uniform(0.1, 0.25), x, y,
                                    self.random.uniform(-1, 1),
                                    self.random.uniform(-1, 1))))

        # Small sparks
        re = self.demo.svg_logo.re
        for t in range(8*150):
            t = t/150.0 * 16
            x0, y0, x1, y1 = re.bbox()
            x = mapf(unmapf(t, 16, 22.1), x0, x1)
            for i in range(9):
                y = mapf(unmapf(i, 0.0, 8.0), y0, y1)
                for obj in list(re[0])+list(re[1]):
                    ox0, oy0, ox1, oy1 = obj.bbox()
                    if abs(oy0 - y) > 0.01:
                        continue
                    if ox0 <= x <= ox1:
                        break
                else:
                    continue
                self.sparks.append((t, self.random.uniform(0.2, 0.4),
                                    (self.random.uniform(0.08, 0.15), x, y,
                                    self.random.uniform(-1, 1),
                                    self.random.uniform(-1, 1))))

    def eval_spark(self, t, spark):
        gravity = 1
        drag = 4
        v, x, y, px, py = spark
        vx = v * sin(px)
        vy = v * sin(py)
        vz = v * cos(px) * cos(py)
        x += vx * (1-exp(-t * drag))
        y += vy * (1-exp(-t * drag)) - gravity * t * t / 2.0
        z = vz * (1-exp(-t * drag))
        return x, y, z

    def render(self, t):
        self.demo.push_params()
        ol.pushColor()
        bright = clamp1(unmapf(t, 33, 31))
        ol.multColor(ol.C_GREY(int(bright * 255)))
        ol.pushMatrix3()
        ol.perspective(60, 1, 1, 100)
        self.demo.params.flatness = 0.000005
        ol.setRenderParams(self.demo.params)

        lase = self.demo.svg_logo.lase[0]
        cp = clamp1(t / self.BURN_LEN)
        l, m = lase.cut(cp)

        cp_x = l.endpos()[0] * (1 - ss(clamp1(t / self.BURN_LEN * 1.1)**0.5))
        cp_y = l.endpos()[1] * (1 - ss(clamp1(t / self.BURN_LEN * 1.1)**0.5))
        cp_z = mapf(ss(clamp1(t / 23)) ** 0.7, 1.0, 2)
        ol.translate3((-cp_x, -cp_y, -cp_z))
        if 22.9 <= t:
            a = 0.5 + cos(clamp1(unmapf(t, 22.9, 24.2)) * pi) / -2
            ol.rotate3Y(-a * 2 * pi)

            bright = clamp1(unmapf(a, 0, 0.2))
            c = rgb(0, bright * 40, bright * 255)
            if bright > 0.01:
                ol.pushMatrix3()
                z = exp(max(0, t - 31.9) * 5)
                ol.translate3((0, 0, 1-z))
                ol.begin(ol.LINESTRIP)
                ol.vertex3((-self.demo.w, -self.demo.h, 0), c)
                ol.vertex3((self.demo.w, -self.demo.h, 0), c)
                ol.vertex3((self.demo.w, self.demo.h, 0), c)
                ol.vertex3((-self.demo.w, self.demo.h, 0), c)
                ol.vertex3((-self.demo.w, -self.demo.h, 0), c)
                ol.end()
                ol.popMatrix3()

            if a > 0.75:
                self.demo.params.flatness = 0.000005
                self.demo.params.on_speed = 2/90.0
                self.demo.params.off_speed = 2/40.0
                ol.setRenderParams(self.demo.params)
                font = ol.getDefaultFont()
                text = "A realtime laser demo"
                w = ol.getStringWidth(font, 0.25, text)
                ol.pushMatrix3()
                z = exp(max(0, t - 30.9) * 5)
                ol.translate3((0, 0, 1-z))
                ol.drawString(font, (-w/2, -0.3), 0.25, ol.C_WHITE, text)
                ol.popMatrix3()

        ol.pushMatrix3()
        z = exp(max(0, t - 29.9) * 5)
        ol.translate3((0, 0, 1-z))
        l.draw()
        ol.popMatrix3()

        for start, duration, args in self.sparks:
            if t < start:
                continue
            if t > (start + duration):
                continue
            t1 = t - start
            t0 = max(0, t1 - 0.1)
            p0 = self.eval_spark(t0, args)
            p1 = self.eval_spark(t1, args)
            r = clamp1(unmapf(t, start + duration, start))
            g = r**2
            b = 0
            if duration >= 1:
                b = g
                g = r
                r = 1.0
            c = rgb(r * 255, g * 255, b * 255)
            ol.begin(ol.LINESTRIP)
            ol.vertex3(p0, c)
            ol.vertex3(p1, c)
            ol.end()

        re = self.demo.svg_logo.re
        if 16 <= t <= 22.1:
            x0, y0, x1, y1 = re.bbox()
            x = mapf(unmapf(t, 16, 22.1), x0, x1)
            for i in range(9):
                y = mapf(unmapf(i, 0.0, 8.0), y0, y1)
                for obj in list(re[0])+list(re[1]):
                    ox0, oy0, ox1, oy1 = obj.bbox()
                    if abs(oy0 - y) > 0.01:
                        continue
                    if ox0 <= x <= ox1:
                        break
                else:
                    continue
                ol.begin(ol.LINESTRIP)
                ol.vertex3((x, y, 0), 0xff8000)
                ol.vertex3((0, -self.demo.h, 0), 0xff8000)
                ol.end()

            tx, _, _ = ol.transformVertex3(x, 0, 0)
            ol.setScissor((-self.demo.w,-self.demo.h),(tx,self.demo.h))
        if 16 <= t:
            ol.pushMatrix3()
            z = exp(max(0, t - 27.9) * 5)
            ol.translate3((0, 0, 1-z))
            re[0].draw()
            ol.popMatrix3()
            ol.pushMatrix3()
            z = exp(max(0, t - 28.9) * 5)
            ol.translate3((0, 0, 1-z))
            re[1].draw()
            ol.popMatrix3()

        ol.setScissor((-self.demo.w,-self.demo.h),(self.demo.w,self.demo.h))
        ol.popMatrix3()
        self.demo.pop_params()
        ol.popColor()

        if 16 <= t <= 33:
            self.demo.params.render_flags |= ol.RENDER_NOREORDER
            ol.setRenderParams(self.demo.params)

class Rain(Effect):
    def __init__(self, demo, start, end):
        Effect.__init__(self, demo, start, end)

        self.rain = []
        for i in xrange(32 * 50):
            t = i / 50.0
            self.rain.append((t, self.random.uniform(-self.demo.w, self.demo.w)*1.3 - 1.3,
                                 self.random.uniform(-self.demo.h, self.demo.h)*1.3 + 1.3,
                                 self.random.uniform(4.2, 4.22),
                                 self.random.uniform(-5.9, -5.92),
                                 self.random.uniform(0.35, 0.78)
                                 ))

        self.lightning = [
            (15.8, 2.0, self.demo.svg_house.lightning1),
            (19.4, 1.0, self.demo.svg_house.lightning2),
            (23.4, 1.5, self.demo.svg_house.lightning3),
            (23.9, 0.7, self.demo.svg_house.lightning4),
            (27.4, 1.1, self.demo.svg_house.lightning5),
            (27.9, 0.9, self.demo.svg_house.lightning6),
            (29.4, 2.1, None),
        ]
        
        self.lt_curve = [
            (0.0, 0, (0.0,)),
            (0.1, 0, (0.7,)),
            (0.2, 0, (0.2,)),
            (0.3, 0, (1.0,)),
            (1.0, 0, (0.0,)),
        ]

    def wind(self, coord, color):
        x, y = coord
        x /= self.cz
        y /= self.cz
        bx, by, _, _ = self.demo.svg_house.markers.tree_base.bbox()
        x -= bx - self.cx
        y -= by - self.cy
        d = sqrt(x**2 + y**2)
        a = (-0.9 + sin(self.t) * d) * 0.1
        x1 = x * cos(a) - y * sin(a) + bx - self.cx + self.tx
        y1 = y * cos(a) + x * sin(a) + by - self.cy + self.ty
        return (x1 * self.cz, y1 * self.cz), color

    def render(self, t):
        self.demo.push_params()
        self.demo.params.on_speed = 2/100.0
        self.demo.params.off_speed = 2/40.0
        ol.setRenderParams(self.demo.params)

        # Warning: whole pile of ugly transforms ahead, faking parallax to make
        # the camera zoom look 3D-ish when the whole scene is total 2D.
        # I should've done this scene with perspective mode but ah well, no time.
        ol.pushMatrix3()
        ct = ssout(clamp1(unmapf(t, 25, 32)))
        self.cx, self.cy = self.demo.svg_house.markers.camera_path.evaluate(ct ** 0.4)
        self.cz = 1.0 / mapf(ct, 1.0, 1.0/32)
        self.tx = -2*self.cx
        self.ty = -self.cx

        bright = 0
        ol.pushMatrix3()
        ol.scale3(((self.cz-1) * 0.18 + 1, (self.cz-1) * 0.18 + 1, 0))
        ol.translate3((-self.cx, -0.4*self.cy, 0))
        for start, duration, obj in self.lightning:
            end = start + duration
            if start < t <= end:
                bright = interp(clamp1(unmapf(t, start, end)), self.lt_curve)
                if obj:
                    ol.pushColor()
                    ol.multColor(ol.C_GREY(bright * 255))
                    obj.draw()
                    ol.popColor()
        ol.popMatrix3()
        ol.pushColor()
        r = mapf(bright, 0.4, 0.8) * 255
        g = mapf(bright, 0.4, 0.9) * 255
        b = mapf(bright, 0.4, 1.0) * 255
        ol.multColor(rgb(r, g, b))

        for start, x, y, dx, dy, bright in self.rain:
            bright *= ss(clamp1(unmapf(t, 32, 31)))
            if t < start:
                continue
            t0 = t - start
            t1 = t - start - 0.1
            x0, y0 = x + dx * t0, y + dy * t0
            x1, y1 = x + dx * t1, y + dy * t1
            if x1 > 1 or y1 > 1:
                continue
            # 2D, not affected by 3D matrix
            ol.line((x0, y0), (x1, y1), ol.C_GREY(bright * 255))

        ol.pushMatrix3()
        ol.scale3((self.cz, self.cz, 0))
        ol.translate3((-self.cx, -self.cy, 0))
        self.demo.svg_house.house.cut(clamp1(unmapf(t, 2, 6)))[0].draw()
        left, bot, right, top = self.demo.svg_house.markers.window.bbox()
        left, bot, _ = ol.transformVertex3(left, bot, 0)
        right, top, _ = ol.transformVertex3(right, top, 0)
        ol.scale3((self.cz, self.cz, 0)) # double scale the tree
        if t < 27:
            ol.setPixelShader(self.wind)
            self.demo.svg_house.tree.cut(clamp1(unmapf(t, 4, 8)))[0].draw()
            ol.setPixelShader(None)
            p1x, p1y, _, _ = self.demo.svg_house.markers.hcleft.bbox()
            s1x, s1y, _ = ol.transformVertex3(p1x, p1y, 0)
            (s1x, s1y), _ = self.wind((s1x, s1y), 0)
            p2x, p2y, _, _ = self.demo.svg_house.markers.hcright.bbox()
            s2x, s2y, _ = ol.transformVertex3(p2x, p2y, 0)
            (s2x, s2y), _ = self.wind((s2x, s2y), 0)
        ol.popMatrix3()
        if 8 < t < 32:
            ol.pushColor()
            bright = clamp1(unmapf(t, 8, 9))
            ol.multColor(ol.C_GREY(int(bright * 255)))
            # horrible hack to make the tree "opaque" to the horizon
            ol.pushMatrix3()
            ol.scale3((self.cz, self.cz, 0))
            ol.translate3((-self.cx, -0.3*self.cy, 0))
            if t < 27:
                ol.setScissor((-self.demo.w,-self.demo.h),(min(s1x, self.demo.w),self.demo.h))
                self.demo.svg_house.horizon.ha.draw()
                ol.setScissor((max(s2x, -self.demo.w),-self.demo.h),(self.demo.w,self.demo.h))
                self.demo.svg_house.horizon.hb.draw()
                ol.setScissor((-self.demo.w,-self.demo.h),(self.demo.w,self.demo.h))
            else:
                self.demo.svg_house.horizon.hb.draw()
            self.demo.svg_house.horizon.hc.draw()
            ol.popMatrix3()
            ol.popColor()

        #ol.scale3(((self.cz, self.cz, 0))
        ol.translate3((-2*self.cx, -0.4*self.cy + (self.cz - 1) * 0.2, 0))
        self.demo.svg_house.clouds.cut(clamp1(unmapf(t, 1.5, 2.5)))[0].draw()

        ol.popColor()
        ol.popMatrix3()
        
        # Scissor the next effect to the window...
        ol.setScissor((max(-self.demo.w, left),max(-self.demo.h, bot)),
                      (min(self.demo.w, right), min(self.demo.h, top)))

class Room(Effect):
    def __init__(self, demo, start, end):
        Effect.__init__(self, demo, start, end)

        self.i_camera = [
            (-8.0, (1, 1, 1), (1/1.0,  0.0, 1.0)),
            (-2.0, (0, 0, 0), (1/1.0,  1.0, 1.0)),
            ( 6.0, (1, 1, 1), (1/9.5,  3.0, 1.0)),
            (24.0, (1, 1, 1), (1/8.0,  3.0, 1.0)),
            #(26.0, (1, 1, 1), (1/4.5,  3.2, 1.0)),
            (29.0, (1, 1, 1), (1/13.0, 4.0, 1.0)),
            (29.5, (1, 1, 1), (1/13.0, 4.0, 1.0)),
            (34.0, (1, 1, 1), (1/1.7,  5.0, 1.0)),
            (50.0, (1, 1, 1), (1/1.68, 6.0, 1.0)),
            (64.0, (1, 1, 1), (1/1.68, 6.0, 1.0)),
            (72.0, (1, 0, 1), (1/3.0,  6.25, 1.0)),
            (80.0, (1, 0, 1), (1/2.8,  6.5, 1.0)),
            (88.0, (1, 0, 1), (1/2.0,  6.75, 0.0)),
            (96.0, (0, 1, 1), (1/5.0,  7.0, 0.0)),
        ]

        self.i_frame = [
            (90.0, 1, (1.0,)),
            (93.0, 0, (0.0,)),
        ]

        self.i_tux = [
            (1.6, 0, (0.0,)),
            (2.0, 1, (1.0,)),
            (4.3, 1, (1.0,)),
            (5.0, 0, (0.0,)),
        ]

        self.i_url = [
            ( 5.5, 0, (0.0, 0.0)),
            ( 6.0, 1, (1.0, 0.0)),
            ( 7.0, 0, (1.0, 0.0)),
            (10.0, 1, (1.0, 1.0)),
            (11.0, 0, (1.0, 1.0)),
            (11.0, 0, (0.0, 1.0)),
        ]

        self.i_openlase = [
            (11.1, 0, (0.0,)),
            (11.5, 1, (1.0,)),
            (20.0, 1, (1.0,)),
            (21.5, 0, (0.0,)),
        ]

        self.i_progress = [
            (12.0, 1, (0.0, 0.0,)),
            (12.5, 1, (1.0, 0.0,)),
            (18.0, 1, (1.0, 1.0,)),
            (20.0, 1, (1.0, 1.0,)),
            (21.5, 0, (0.0, 1.0,)),
        ]

        self.i_command = [
            (22.0, 0, (0.0, 0.0)),
            (22.0, 0, (1.0, 0.0)),
            (22.5, 1, (1.0, 0.0)),
            (24.5, 1, (1.0, 1.0)),
            (63.0, 0, (1.0, 1.0)),
            (64.0, 0, (0.0, 0.0)),
        ]
        
        self.i_cube = [
            (15.5, 0, (2.0, 1.0)),
            (17.5, 1, (0.0, 1.0)),
            (27.0, 1, (0.0, 1.0)),
            (28.0, 0, (0.0, 0.0)),
        ]

        self.i_nyan = [
            (27.3, 0, (-2.0,-0.4, 1.0)),
            (28.5, 1, (-0.3, 0.0, 1.0)),
            (30.5, 1, ( 0.0,-0.2, 1.0)),
            (31.8, 0, ( 7.0, 1.6, 1.0)),
        ]

        self.screen_mtx = points_to_matrix(
            self.demo.svg_room.markers.screen_bl.bbox()[0:2],
            self.demo.svg_room.markers.screen_br.bbox()[0:2],
            self.demo.svg_room.markers.screen_tr.bbox()[0:2],
            self.demo.svg_room.markers.screen_tl.bbox()[0:2])

        self.tracer = ol.Tracer(256, 256)
        self.tracer.mode = ol.TRACE_THRESHOLD
        self.tracer.threshold = 200

        self.raster_fx = mesh.raster_fx
        self.mb_buf = ctypes.create_string_buffer(256 * 256)

    def minmax(self, coord, color):
        x, y = coord
        if self.cx0 <= x <= self.cx1:
            bo_x, bo_y = self.beam_origin
            dx, dy = x - bo_x, y - bo_y
            slope = dy / dx
            if self.bmin is None:
                self.bmin = self.bmax = (coord, slope, color)
            else:
                if slope < self.bmin[1]:
                    self.bmin = (coord, slope, color)
                if slope > self.bmax[1]:
                    self.bmax = (coord, slope, color)
        return coord, color

    def add_beams(self):
        if self.bmin is None:
            return
        if self.bmax != self.bmin:
            self.beams.append((self.bmax[0], self.bmax[2]))
        self.beams.append((self.bmin[0], self.bmin[2]))
        self.bmin = self.bmax = None

    def metaballs(self, t1, t2):
        self.raster_fx.render_metaballs(self.mb_buf, ctypes.c_float(t1),
                                        ctypes.c_float(t2))
        objects = self.tracer.trace(self.mb_buf.raw)
        ol.pushMatrix()
        ol.translate((-1, -1))
        ol.scale((1/128.0, 1/128.0))
        for o in objects:
            ol.begin(ol.POINTS)
            for point in o[::2]:
                ol.vertex(point, 0x00ff00)
            ol.end()
            self.add_beams()
        ol.popMatrix()

    def cubes(self, time):
        ol.pushMatrix3()
        ol.loadIdentity3()
        ol.perspective(60, 1, 1, 100)
        ol.translate3((0, 0, -2.1))
        for i, mult in enumerate([1, 1.5, -1.5]):
            if i > 0:
                ol.pushMatrix3()
            ol.scale3((0.6, 0.6, 0.6))
            if i > 0:
                tx = sin(time + (i-1) * pi);
                ty = cos(time + (i-1) * pi);
                tz = sin(time + (i-1) * pi);
                s = sin(0.6 * time)
                ol.translate3((tx*s, ty*s, tz*s))
                ol.scale3((0.3, 0.3, 0.3))

            ol.pushColor()
            if i == 0:
                ol.multColor(ol.C_GREY(120));

            ol.rotate3Z(mult*time * pi * 0.1 / 3.0)
            ol.rotate3Y(mult*time * pi * 0.8 / 3.0)
            ol.rotate3X(mult*time * pi * 0.73 / 3.0)

            ol.begin(ol.LINESTRIP)
            ol.vertex3((-1, -1, -1), ol.C_GREEN)
            ol.vertex3(( 1, -1, -1), ol.C_GREEN)
            ol.vertex3(( 1,  1, -1), ol.C_GREEN)
            ol.vertex3((-1,  1, -1), ol.C_GREEN)
            ol.vertex3((-1, -1, -1), ol.C_GREEN)
            ol.vertex3((-1, -1,  1), ol.C_GREEN)
            ol.end()

            ol.begin(ol.LINESTRIP)
            ol.vertex3(( 1,  1,  1), ol.C_GREEN)
            ol.vertex3((-1,  1,  1), ol.C_GREEN)
            ol.vertex3((-1, -1,  1), ol.C_GREEN)
            ol.vertex3(( 1, -1,  1), ol.C_GREEN)
            ol.vertex3(( 1,  1,  1), ol.C_GREEN)
            ol.vertex3(( 1,  1, -1), ol.C_GREEN)
            ol.end()

            ol.begin(ol.LINESTRIP)
            ol.vertex3(( 1, -1, -1), ol.C_GREEN)
            ol.vertex3(( 1, -1,  1), ol.C_GREEN)
            ol.end()

            ol.begin(ol.LINESTRIP)
            ol.vertex3((-1,  1,  1), ol.C_GREEN);
            ol.vertex3((-1,  1, -1), ol.C_GREEN);
            ol.end()

            if i > 0:
                ol.popMatrix3()

            ol.popColor()
            self.add_beams()

        ol.popMatrix3()

    def render_retro(self, t):
        c = 0xff0000
        ol.begin(ol.LINESTRIP)
        ol.vertex3((-1, -1, 0), c)
        ol.vertex3((1, -1, 0), c)
        ol.vertex3((1, 1, 0), c)
        ol.vertex3((-1, 1, 0), c)
        ol.vertex3((-1, -1, 0), c)
        ol.end()

        ol.setPixelShader(self.minmax)

        if t < 16:
            self.metaballs(t + 0.2, 16 - t);
        dx, bright = interp(t, self.i_cube)

        if bright:
            ol.pushMatrix()
            ol.translate(((dx, 0)))
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * bright))
            self.cubes(t)
            ol.popColor()
            ol.popMatrix()

        dx, dy, bright = interp(t, self.i_nyan)
        if -2 < dx < 2:
            frame = int(t * 6.0 + 4) % 6
            ol.pushMatrix()
            ol.translate(((dx, dy)))
            self.demo.svg_nyan["frame%d"%frame].draw()
            self.add_beams()
            ol.popMatrix()

        ol.setPixelShader(None)


    def render(self, t):
        self.t = t = t - 8
        self.demo.push_params()
        self.demo.params.on_speed = 2/130.0
        self.demo.params.off_speed = 2/80.0
        self.demo.params.corner_dwell = 4
        self.demo.params.end_wait = 4
        self.demo.params.start_wait = 4
        self.demo.params.start_dwell = 1
        self.demo.params.end_dwell = 1
        self.demo.params.max_framelen = self.demo.params.rate / 25.0
        self.demo.params.curve_angle = cos(20.0 * (pi / 180.0))
        ol.setRenderParams(self.demo.params)

        ol.pushMatrix()
        ol.pushMatrix3()

        # initial zoom-in
        if t < 0:
            ct = ss(clamp1(unmapf(t, -7, 0)))
            px, py = self.demo.svg_house.markers.camera_path.evaluate(ct ** 0.4)
            dx, dy = self.demo.svg_house.markers.camera_path.evaluate(1)
            cx, cy = px-dx, py-dy
            cz = 1.0 / mapf(ct, 7.0, 1.0)
            #cz = (1 + 8 * (ctz ** 2)) / 9
            ol.translate((-cx, -cy))
            ol.scale((cz, cz))

        self.cz, e, global_bright = interp(t, self.i_camera)
        ol.pushColor()
        ol.multColor(ol.C_GREY(255 * global_bright))

        self.cz = 1/self.cz
        cam = self.demo.svg_room.markers.camera
        self.cx, self.cy = cam.evaluate(e / len(cam.segments))
        ol.scale((self.cz, self.cz))
        ol.translate((-self.cx, -self.cy))

        # general geometry
        self.demo.svg_room.lv0.draw()
        l1 = clamp1(unmapf(self.cz, 1.5, 1.7))
        if l1 > 0:
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * l1))
            self.demo.svg_room.lv1.draw()
            ol.popColor()
        l2 = clamp1(unmapf(self.cz, 3.5, 4.5))
        if l2 > 0:
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * l2))
            self.demo.svg_room.lv2.draw()
            ol.popColor()

        # objects
        self.draw_fade(self.demo.svg_room.tux, self.i_tux)
        self.draw_fade(self.demo.svg_room.openlase, self.i_openlase)

        font = ol.getDefaultFont()

        # URL text
        bright, pos = interp(t, self.i_url)

        if bright > 0:
            self.demo.push_params()
            self.demo.params.flatness = 0.000004
            self.demo.params.on_speed = 2/90.0
            self.demo.params.off_speed = 2/40.0
            ol.setRenderParams(self.demo.params)
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * bright))
            text = "http://openlase.org"
            text = text[:int(len(text) * pos + 0.5)] + "|"
            x, y, _, _ = self.demo.svg_room.markers.url.bbox()
            ol.drawString(font, (x, y), 0.015, 0x44ccff, text)
            ol.popColor()
            self.demo.pop_params()

        # command text
        bright, pos = interp(t, self.i_command)

        if bright > 0:
            self.demo.push_params()
            self.demo.params.flatness = 0.000004
            self.demo.params.on_speed = 2/90.0
            self.demo.params.off_speed = 2/40.0
            ol.setRenderParams(self.demo.params)
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * bright))
            text = "./lase"
            text = "$ " + text[:int(len(text) * pos + 0.5)]
            x, y, _, _ = self.demo.svg_room.markers.command.bbox()
            ol.drawString(font, (x, y), 0.025, 0x008000, text)
            ol.popColor()
            self.demo.pop_params()

        # warning text
        bright = clamp1(unmapf(self.cz, 4.5, 8.0))
        if bright > 0:
            self.demo.push_params()
            self.demo.params.flatness = 0.000004
            self.demo.params.on_speed = 2/90.0
            self.demo.params.off_speed = 2/40.0
            ol.setRenderParams(self.demo.params)
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * bright))
            text = "WARNING: Class IV Laser Radiation"
            x, y, _, _ = self.demo.svg_room.markers.warning_text.bbox()
            x -= ol.getStringWidth(font, 0.004, text) / 2
            ol.drawString(font, (x, y), 0.004, 0xffff00, text)
            ol.popColor()
            self.demo.pop_params()

        ol.pushColor()
        ol.multColor(ol.C_GREY(255 * (1-bright) * l1))
        self.demo.svg_room.fake_warning.draw()
        ol.popColor()

        # Loading bar
        bright, pos = interp(t, self.i_progress)
        if bright > 0:
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * bright))
            left, bot, right, top = self.demo.svg_room.markers.progress.bbox()
            w = (right - left) * pos
            ol.rect((left, bot), (left+w, top), 0x00ccff)
            ol.popColor()

        # clock
        bright = clamp1(unmapf(t, -3, -1))
        if bright > 0:
            self.demo.push_params()
            self.demo.params.on_speed = 2/200.0
            self.demo.params.corner_dwell = 5
            self.demo.params.start_dwell = 3
            self.demo.params.end_dwell = 3
            ol.setRenderParams(self.demo.params)
            ol.pushColor()
            ol.multColor(ol.C_GREY(255 * bright))
            lt = time.localtime()
            h0, h1 = lt.tm_hour // 10, lt.tm_hour % 10
            m0, m1 = lt.tm_min // 10, lt.tm_min % 10
            sf = time.time() % 1
            if h0 > 0:
                self.demo.svg_room.clock0[h0].draw()
            self.demo.svg_room.clock1[h1].draw()
            self.demo.svg_room.clock2[m0].draw()
            self.demo.svg_room.clock3[m1].draw()
            if sf > 0.5:
                self.demo.svg_room.cdots.draw()
            self.demo.pop_params()
            ol.popColor()

        bo = self.demo.svg_room.markers.beam_origin.bbox()[:2]
        bo_x, bo_y = self.beam_origin = ol.transformVertex(*bo)
        bc = self.demo.svg_room.markers.beam_clip.bbox()[:2]
        beam_clip = ol.transformVertex(*bc)

        # We must go deeper
        self.bmin = None
        self.bmax = None
        self.beams = []
        if t >= 31:
            # The perspective transform of the screen is done on the 2D transform mtx
            ol.pushMatrix()
            ol.multMatrix(self.screen_mtx)
            ol.scale((0.5, 0.5))
            ol.translate((1.0, 1.0))
            self.cx0, _ = ol.transformVertex(-1, -1)
            self.cx1, _ = ol.transformVertex(1, 1)
            cx0 = max(-self.demo.w, self.cx0)
            cx1 = min(self.demo.w, self.cx1)
            ol.setScissor((cx0,-self.demo.h),(cx1,self.demo.h))
            self.render_retro(t - 32)
            ol.popMatrix()
            ol.setScissor((-self.demo.w,-self.demo.h),(self.demo.w,self.demo.h))

        ol.popColor()
        self.draw_fade(self.demo.svg_room.frame, self.i_frame)
        ol.pushColor()
        ol.multColor(ol.C_GREY(255 * l1))
        self.demo.svg_room.tokyotower.draw()
        ol.popColor()

        ol.popMatrix3()
        ol.popMatrix()

        # Beams are drawn in raw screen space
        ol.loadIdentity()
        ol.pushColor()
        ol.multColor(ol.C_GREY(96))
        for (x, y), color in self.beams:
            y0 = mapf(unmapf(beam_clip[0], bo_x, x), bo_y, y)
            ol.line((beam_clip[0], y0), (x, y), color)
        ol.popColor()

        if 0 < t:
            self.demo.params.render_flags |= ol.RENDER_NOREORDER
            ol.setRenderParams(self.demo.params)

class Tower(Effect):
    def __init__(self, demo, start, end):
        Effect.__init__(self, demo, start, end)

        self.zbuf = mesh.ZBuffer(300)

    def render(self, t):
        bright = clamp1(unmapf(t, 71, 70))
        if bright == 0:
            return

        self.demo.MAX_FPS = 35
        self.demo.push_params()
        self.demo.params.on_speed = 2/130.0
        self.demo.params.off_speed = 2/80.0
        self.demo.params.corner_dwell = 4
        self.demo.params.end_wait = 3
        self.demo.params.start_wait = 3
        self.demo.params.start_dwell = 3
        self.demo.params.end_dwell = 3
        self.demo.params.max_framelen = self.demo.params.rate / 20.0
        self.demo.params.curve_angle = cos(20.0 * (pi / 180.0))
        ol.setRenderParams(self.demo.params)

        ol.pushColor()
        ol.multColor(ol.C_GREY(255 * bright))
        ol.pushMatrix()
        ol.pushMatrix3()
        ol.perspective(80, 1, 0.1, 100)

        # Time for a bunch of camera fun

        if t < 16:
            z = interp(t, [
                (0.0, 0, (-25,)),
                (16.0, 0, (-10,))])
            dy, rx, ry = interp(t, [
                ( 0.0, (0, 0, 0), ( 0.0, 0.0, 0.0)),
                ( 8.0, (0, 0, 1), (10.0, 0.3, 0.0)),
                (16.0, (0, 0, 0), (15.0, 0.6, 1.0)),
            ])
            ol.rotate3X(rx)
            ol.translate3((0, -9-dy, z))
            ol.rotate3Y(ry)
        elif 16 <= t < 32:
            y = mapf(unmapf(t, 16, 32), -1.5, -18)
            ol.translate3((0, y, -5))
            ol.rotate3Y(t)
        elif 32 <= t < 48:
            y = mapf(unmapf(t, 32, 48), 1.3, -6)
            ol.rotate3X(-pi/2)
            ol.translate3((0, y, -0.8))
            ol.rotate3Y(-pi/2)
        elif 48 <= t <= 64:
            a = mapf(unmapf(t, 48, 64), -1.5, 1.0)
            d = mapf(unmapf(t, 48, 64), -10, -13)
            y = mapf(unmapf(t, 48, 64), 0, -2)
            ol.translate3((0, y, d))
            ol.rotate3X(a)
            ol.translate3((0, -9, 0))
            ol.rotate3Y(-t/2)
        elif 64 <= t:
            z = interp(t, [
                (64.0, 0, (-20,)),
                (72.0, 1, (-40,))])
            ol.translate3((0, -9, z))
            ol.translate((-0.5, 0))
        #ol.rotate3Y(t)


        ol.rotate3X(-pi / 2)
        self.zbuf.clear()

        self.demo.mesh_tower.frame_r.apply_xform()
        self.demo.mesh_tower.frame_w.apply_xform()
        self.demo.mesh_tower.deck.apply_xform()
        self.demo.mesh_tower.shaft_r.apply_xform()
        self.demo.mesh_tower.shaft_w.apply_xform()
        self.demo.mesh_tower.lower_r.apply_xform()
        self.demo.mesh_tower.disc_w.apply_xform()
        self.demo.mesh_tower.disc_r.apply_xform()

        self.demo.mesh_tower.deck.render_z(self.zbuf)
        self.demo.mesh_tower.shaft_w.render_z(self.zbuf)
        self.demo.mesh_tower.lower_r.render_z(self.zbuf)
        if not 32 <= t < 48:
            self.demo.mesh_tower.shaft_r.render_z(self.zbuf)
            self.demo.mesh_tower.disc_w.render_z(self.zbuf)
            self.demo.mesh_tower.disc_r.render_z(self.zbuf)

        self.zbuf.enable()
        self.demo.mesh_tower.frame_r.draw_edges(0xff0000)
        self.demo.mesh_tower.frame_w.draw_edges(0x999999)
        self.demo.mesh_tower.deck.draw_edges(0xffffff)
        self.demo.mesh_tower.lower_r.draw_edges(0xff0000)
        self.demo.mesh_tower.shaft_w.draw_edges(0x666666)
        if not 32 <= t < 48:
            self.demo.mesh_tower.shaft_r.draw_edges(0xaa0000)
            self.demo.mesh_tower.disc_w.draw_edges(0x999999)
            self.demo.mesh_tower.disc_r.draw_edges(0xff0000)

        self.zbuf.disable()

        ol.popMatrix3()
        ol.popMatrix()
        ol.popColor()

class Owari(Effect):
    def __init__(self, demo, start, end):
        Effect.__init__(self, demo, start, end)

        self.i_frame = [
            (6.0, 0, (0.0,)),
            (7.0, 1, (1.0,)),
        ]

        self.i_tower = [
            (6.0, 0, (0.0,)),
            (7.0, 1, (1.0,)),
        ]

        self.i_bright = [
            (17.0, 1, (1.0,)),
            (30.0, 0, (0.0,)),
        ]

    def render(self, t):
        self.demo.MAX_FPS = 50
        self.demo.params.on_speed = 2/150.0
        self.demo.params.off_speed = 2/90.0
        self.demo.params.corner_dwell = 5
        self.demo.params.end_wait = 2
        self.demo.params.start_wait = 2
        self.demo.params.start_dwell = 4
        self.demo.params.end_dwell = 4
        self.demo.params.curve_angle = cos(5.0 * (pi / 180.0))
        self.demo.params.max_framelen = self.demo.params.rate / 20.0
        ol.setRenderParams(self.demo.params)

        bright = interp(t, self.i_bright)
        ol.pushColor()
        ol.multColor(ol.C_GREY(255 * bright))

        self.draw_fade(self.demo.svg_owari.frame, self.i_frame)
        self.draw_fade(self.demo.svg_owari.tokyotower, self.i_tower)

        frac = clamp1(unmapf(t, 8, 14))
        if frac > 0:
            f = self.demo.svg_owari.tdf.flatten()
            for obj in f:
                obj.cut(frac)[0].draw()

        ol.popColor()

class Relase(Demo):
    RATE = 48000
    AUDIO = "audio.wav"
    BPM = 138
    FRAME = False
    ASPECT = 16.0/9.0
    MAX_FPS = 100
    LEN = 83.0
    START_TIME = float(sys.argv[1]) if len(sys.argv) > 1 else 0

    def load_resources(self):
        self.svg_mountains = svg.load_svg("mountains.svg")
        self.svg_logo = svg.load_svg("logo.svg")
        self.svg_house = svg.load_svg("house.svg")
        self.svg_room = svg.load_svg("room.svg")
        self.svg_nyan = svg.load_svg("nyan.svg")
        self.svg_owari = svg.load_svg("owari.svg")
        self.mesh_tower = mesh.load_mesh("tower.json")

    def init_effects(self):
        self.add_effect(Stars(self,  0.0, 20.0))
        self.add_effect(Logo(self,  18.0, 27.0))
        self.add_effect(Rain(self,  27.0, 35.0))
        self.add_effect(Room(self,  33.0, 59.0))
        self.add_effect(Tower(self, 59.0, 77.0))
        self.add_effect(Owari(self,  75.0, 83.0))

    def init_params(self):
        self.params.render_flags = 0
        self.params.on_speed = 2/150.0
        self.params.off_speed = 2/50.0
        self.params.flatness = 0.000001
        self.params.corner_dwell = 2
        self.params.curve_dwell = 0
        self.params.curve_angle = cos(45.0 * (pi / 180.0))
        self.params.snap = 0.0001

    def frame(self):
        pass

if __name__ == "__main__":
    d = Relase()
    d.load()
    d.play()
