#!/usr/bin/env qore

# This is basically a direct port of a QT example program to Qore
# using Qore's "qt-opengl" module.

# Note that Qore's "qt-opengl" module requires QT 4.3 or above with OpenGL support

# use the "qt-opengl" module (automatically loads the "qt-gui" and "opengl" modules)
%requires qt-opengl
# use the "qt-svg" module
%requires qt-svg

# this is an object-oriented program, the application class is "framebufferobject"
%exec-class framebufferobject
# require all variables to be explicitly  declared
%require-our
# enable all parse warnings
%enable-all-warnings

const AMP = 5;

class GLWidget inherits QGLWidget
{
    private $.anchor, $.scale, $.rot_x, $.rot_y, $.rot_z,
    $.tile_list, $.wave, $.logo, $.anim, $.svg_renderer;

    constructor($parent) : QGLWidget(new QGLFormat(QGL::SampleBuffers|QGL::AlphaChannel), $parent)
    {
	$.anchor = new QPoint();

	$.setWindowTitle(TR("OpenGL framebuffer objects"));
	$.makeCurrent();
	$.fbo = new QGLFramebufferObject(512, 512);
	$.rot_x = $.rot_y = $.rot_z = 0.0;
	$.scale = 0.1;
	$.anim = new QTimeLine(750, $self);
	$.anim.setUpdateInterval(20);
	$.connect($.anim, SIGNAL("valueChanged(qreal)"), SLOT("animate(qreal)"));
	$.connect($.anim, SIGNAL("finished()"), SLOT("animFinished()"));

	$.svg_renderer = new QSvgRenderer($dir + "images/bubbles.svg", $self);
	#printf("svg_renderer valid=%N\n", $.svg_renderer.isValid());
	$.connect($.svg_renderer, SIGNAL("repaintNeeded()"), SLOT("draw()"));

	$.logo = new QImage($dir + "images/qt4-logo.png");
	#printf("logo valid=%N\n", !$.logo.isNull());
	$.logo = $.logo.convertToFormat(QImage::Format_ARGB32);
	#printf("logo valid=%N\n", !$.logo.isNull());

	$.tile_list = glGenLists(1);

	glNewList($.tile_list, GL_COMPILE);
	glBegin(GL_QUADS);
	{
	    glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0,  1.0);
	    glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0,  1.0);
	    glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0,  1.0);
	    glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0,  1.0);

	    glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, -1.0);
	    glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0, -1.0);
	    glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0, -1.0);
	    glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0, -1.0);

	    glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0);
	    glTexCoord2f(0.0, 0.0); glVertex3f(-1.0,  1.0,  1.0);
	    glTexCoord2f(1.0, 0.0); glVertex3f( 1.0,  1.0,  1.0);
	    glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0);

	    glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, -1.0, -1.0);
	    glTexCoord2f(0.0, 1.0); glVertex3f( 1.0, -1.0, -1.0);
	    glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0);
	    glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0);

	    glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0, -1.0);
	    glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0);
	    glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0,  1.0);
	    glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0);

	    glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, -1.0);
	    glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0);
	    glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0,  1.0);
	    glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0);
	}
	glEnd();
	glEndList();

	my $size = $.logo.width() * $.logo.height();
	my $str = "";
	for (my $i = 0; $i < $size; ++$i)
	    $str += chr(0);

	$.wave = binary($str);

	$.startTimer(30); # $.wave timer
    }

    destructor()
    {
	glDeleteLists($.tile_list, 1);
    }

    paintEvent()
    {
	$.draw();
    }

    draw()
    {
	my $p = new QPainter($self); # used for text overlay

	# save the GL state set for QPainter
	$.saveGLState();

	# render the 'bubbles.svg' file into our framebuffer object
	$.fbo_painter = new QPainter($.fbo);
	$.svg_renderer.render($.fbo_painter);
	$.fbo_painter.end();

	# draw into the GL widget
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-1, 1, -1, 1, 10, 100);
	glTranslatef(0.0, 0.0, -15.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, $.width(), $.height());
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glBindTexture(GL_TEXTURE_2D, $.fbo.texture());
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_MULTISAMPLE);
	glEnable(GL_CULL_FACE);

	# draw background
	glPushMatrix();
	glScalef(1.7, 1.7, 1.7);
	glColor4f(1.0, 1.0, 1.0, 1.0);
	glCallList($.tile_list);
	glPopMatrix();

	my $w = $.logo.width();
	my $h = $.logo.height();
	#printf("h=%n, w=%n\n", $h, $w);

	glRotatef($.rot_x, 1.0, 0.0, 0.0);
	glRotatef($.rot_y, 0.0, 1.0, 0.0);
	glRotatef($.rot_z, 0.0, 0.0, 1.0);
	glScalef($.scale/$w, $.scale/$w, $.scale/$w);

	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);
	# draw the Qt icon
	glTranslatef(-$w+1, -$h+1, 0.0);
	for (my $y=$h-1; $y>=0; --$y) {
	    my $line = $.logo.scanLine($y);
	    #printf("y=%n line=%n (%s)\n", $y, $line, makeBase64String($line));
	    my $end = elements $line / 4;
	    my $x = 0;
	    while ($x < $end) {
		my $word = getWord32($line, $x);
		#printf("line=%N (%d), x=%n word=%n\n", $line, elements $line, $x, $word);
		glColor4ub(qRed($word), qGreen($word), qBlue($word), qAlpha($word)* 0.9);
		glTranslatef(0.0, 0.0, $.wave[$y*$w+$x]);
		if (qAlpha($word) > 128)
		    glCallList($.tile_list);
		glTranslatef(0.0, 0.0, -$.wave[$y*$w+$x]);
		glTranslatef(2.0, 0.0, 0.0);
		++$x;
	    }
	    glTranslatef(-$w * 2.0, 2.0, 0.0);
	}
	# restore the GL state that QPainter expects
	$.restoreGLState();

	# draw the overlayed text using QPainter
	$p.setPen(new QColor(197, 197, 197, 157));
	$p.setBrush(new QColor(197, 197, 197, 127));
	$p.drawRect(new QRect(0, 0, $.width(), 50));
	$p.setPen(Qt::black);
	$p.setBrush(Qt::NoBrush);
	my $str1 = TR("A simple OpenGL framebuffer object example.");
	my $str2 = TR("Use the mouse wheel to zoom, press buttons and move mouse to rotate, double-click to flip.");
	my $fm = new QFontMetrics($p.font());
	$p.drawText($.width()/2 - $fm.width($str1)/2, 20, $str1);
	$p.drawText($.width()/2 - $fm.width($str2)/2, 20 + $fm.lineSpacing(), $str2);
	$.show();
    }

    mousePressEvent($e)
    {
	$.anchor = $e.pos();
    }

    mouseMoveEvent($e)
    {
	my $diff = $e.pos().subtract($.anchor);
	if ($e.buttons() & Qt::LeftButton) {
	    $.rot_x += $diff.y
		()/5.0;
	    $.rot_y += $diff.x()/5.0;
	} else if ($e.buttons() & Qt::RightButton) {
	    $.rot_z += $diff.x()/5.0;
	}

	$.anchor = $e.pos();
	$.draw();
    }

    wheelEvent($e)
    {
	if ($e.delta() > 0)
	    $.scale += $.scale * 0.1;
	else
	    $.scale -= $.scale * 0.1;
	$.draw();
    }

    mouseDoubleClickEvent()
    {
	$.anim.start();
    }

    animate($val)
    {
	$.rot_y = $val * 180;
	$.draw();
    }

    animFinished()
    {
	if ($.anim.direction() == QTimeLine::Forward)
	    $.anim.setDirection(QTimeLine::Backward);
	else
	    $.anim.setDirection(QTimeLine::Forward);
    }

    saveGLState()
    {
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
    }

    restoreGLState()
    {
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	glPopAttrib();
    }

    timerEvent()
    {
	if (QApplication::mouseButtons() != 0)
	    return;

	if ($scale_in && $.scale > 35.0)
	    $scale_in = False;
	else if (!$scale_in && $.scale < 0.5)
	    $scale_in = True;

	$.scale = $scale_in ? $.scale + $.scale * 0.01 : $.scale-$.scale * 0.01;
	$.rot_z += 0.3;
	$.rot_x += 0.1;

	my ($dx, $dy); # disturbance point
	my ($s, $v, $W, $t);
	our $wt;
	my $width = $.logo.width();

	$dx = $dy = $width >> 1;

	$W = 0.3;
	$v = -4; # wave speed

	for (my $i = 0; $i < $width; ++$i) {
	    for (my $j = 0; $j < $width; ++$j) {
		$s = sqrt((($j - $dx) * ($j - $dx) + ($i - $dy) * ($i - $dy)));
		$wt[$i][$j] += 0.1;
		$t = $s / $v;
		if ($s != 0)
		    $.wave[$i*$width + $j] = AMP * sin(2 * M_PI * $W * ($wt[$i][$j] + $t)) / (0.2*($s + 2));
		else
		    $.wave[$i*$width + $j] = AMP * sin(2 * M_PI * $W * ($wt[$i][$j] + $t));
         }
     }
 }
}

class framebufferobject inherits QApplication
{
    constructor() {
        our $dir = get_script_dir();
	our $scale_in = True;

	if (!QGLFormat::hasOpenGL()) {
	    QMessageBox::information(0, "OpenGL framebuffer objects",
				    "this system does not support OpenGL");
	    return -1;
	}
	if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) {
	    QMessageBox::information(0, "OpenGL framebuffer objects",
				    "this system does not support framebuffer objects.");
	    return -1;
	}

	my $widget = new GLWidget();
	$widget.resize(640, 480);
	$widget.show();
        $.exec();
    }
}