#+TITLE: Draw two cubes using Kivy with different shaders.
#+DATE: 2014-09-01 12:00:00 UTC 
#+DESCRIPTION: Kivy example draw two cubes with different shaders and vertices so they can be moved seperately.
#+FILETAGS: python:opengl:kivy:glsl
#+CATEGORY: python
#+SLUG: drawing-two-cubes-with-different-shaders-using-kivy
#+BEGIN_COMMENT
.. title: Draw two cubes using Kivy with different shaders.
.. slug: drawing-two-cubes-with-different-shaders-using-kivy
.. date: 2014-09-01 12:00:00 UTC
.. tags: python, opengl, kivy, glsl
.. category: python
.. description: Kivy example draw two cubes with different shaders and vertices so they can be moved seperately.
.. type: text
#+END_COMMENT

#+CAPTION: Multiple cube meshes
[[../../../images/kivy/multi-mesh.png]]


[[http://bazaar.launchpad.net/~oly/python-examples/trunk/files/head:/python-examples/kivy/][Multiple mesh source code]]

Expanding on the last example this code demonstrates loading multiple models and using different shaders per model.

#+BEGIN_SRC python :tangle kivy-multi-mesh-demo.py
import kivy
kivy.require('1.0.7')

from kivy.app import App
from opengl_widget import OpenglWidget


class DemoApp(App):
    pass

if __name__ == '__main__':
    DemoApp().run()
#+END_SRC

#+BEGIN_SRC python :tangle kivy-multi-mesh-demo.kv
#:kivy 1.0
FloatLayout:
    OpenglWidget:
#+END_SRC

First shader to create a green object.

#+BEGIN_SRC glsl :tangle kivy-green.glsl
---VERTEX SHADER-------------------------------------------------------
#ifdef GL_ES
    precision highp float;
#endif

attribute vec3  v_pos;
attribute vec4  v_color;

uniform mat4 modelview_mat;
uniform mat4 projection_mat;

varying vec4 frag_color;

void main (void) {
    vec4 pos = modelview_mat * vec4(v_pos,1.0);
    gl_Position = projection_mat * pos;
    frag_color = v_color;
}


---FRAGMENT SHADER-----------------------------------------------------
#ifdef GL_ES
    precision highp float;
#endif

varying vec4 frag_color;
varying vec2 uv_vec;

uniform sampler2D tex;

void main (void){
    gl_FragColor = vec4(0, 1, 0, 1);
}
#+END_SRC

Second shader creates a blue object.

#+BEGIN_SRC glsl :tangle kivy-blue.glsl
---VERTEX SHADER-------------------------------------------------------
#ifdef GL_ES
    precision highp float;
#endif

attribute vec3  v_pos;
attribute vec4  v_color;

uniform mat4 modelview_mat;
uniform mat4 projection_mat;

varying vec4 frag_color;

void main (void) {
    vec4 pos = modelview_mat * vec4(v_pos,1.0);
    gl_Position = projection_mat * pos;
    frag_color = v_color;
}


---FRAGMENT SHADER-----------------------------------------------------
#ifdef GL_ES
    precision highp float;
#endif

varying vec4 frag_color;
varying vec2 uv_vec;

uniform sampler2D tex;

void main (void){
    gl_FragColor = vec4(0, 0, 1, 1);
}
#+END_SRC


#+BEGIN_SRC python :tangle opengl_widget.py
import os
import sys
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.core.image import Image
from kivy.uix.widget import Widget
from kivy.resources import resource_find
from kivy.graphics.transformation import Matrix
from kivy.graphics.opengl import *
from kivy.graphics import *

from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse

from numpy import array


#store a single vertex in this class
class point:
    def __init__(self, p, c=(1, 0, 0, 1)):
        self.x, self.y, self.z = p
        self.vertex = array([self.x, self.y, self.z, c[0], c[1], c[2], c[3]], 'f')


#simple class to create the vertices for a cube for testing.
class cube:
    def __init__(self, p1, color=(1, 0, 0, 1), size=0.5):
        self.color = array([1, 0, 0, 1], 'f')
        self.points = (
            point((p1[0] - size, p1[1] + size, p1[2] - size), (color)),
            point((p1[0] - size, p1[1] + size, p1[2] + size), (color)), 
            point((p1[0] + size, p1[1] + size, p1[2] + size), (color)),
            point((p1[0] + size, p1[1] + size, p1[2] - size), (color)),
            
            point((p1[0] - size, p1[1] - size, p1[2] - size), (color)),
            point((p1[0] - size, p1[1] - size, p1[2] + size), (color)), 
            point((p1[0] + size, p1[1] - size, p1[2] + size), (color)),
            point((p1[0] + size, p1[1] - size, p1[2] - size), (color)),
            
            )

    def get_data(self):
        return (
            self.points[0].vertex, self.points[2].vertex, self.points[1].vertex, 
            self.points[0].vertex, self.points[3].vertex, self.points[2].vertex, 

            self.points[0].vertex, self.points[1].vertex, self.points[5].vertex, 
            self.points[0].vertex, self.points[5].vertex, self.points[4].vertex,

            self.points[0].vertex, self.points[7].vertex, self.points[3].vertex, 
            self.points[0].vertex, self.points[4].vertex, self.points[7].vertex,

            self.points[6].vertex, self.points[2].vertex, self.points[3].vertex, 
            self.points[6].vertex, self.points[3].vertex, self.points[7].vertex, 

            self.points[6].vertex, self.points[1].vertex, self.points[2].vertex,
            self.points[6].vertex, self.points[5].vertex, self.points[1].vertex,

            self.points[6].vertex, self.points[4].vertex, self.points[5].vertex,
            self.points[6].vertex, self.points[7].vertex, self.points[4].vertex,
        )


#custom widget for render our scene into
class OpenglWidget(Widget):

    def __init__(self, **kwargs):
        self.instructions = InstructionGroup()
        self.canvas = RenderContext(compute_normal_mat=True)
        self.canvas.shader.source = resource_find('kivy.glsl')

        #create first shader for cube 1
        self.cube1 = RenderContext(compute_normal_mat=True)
        self.cube1.shader.source = resource_find('kivy-blue.glsl')

        #create first shader for cube 2
        self.cube2 = RenderContext(compute_normal_mat=True)
        self.cube2.shader.source = resource_find('kivy-green.glsl')

        #create 2 cubes for testing
        cube1 = cube((0, -4, 0), size = 2.0)
        cube2 = cube((0, 4, 0), size = 2.0)

        #generate vertex array for cube 1
        self.vertices_cube1 = []
        for item in cube1.get_data():
            for a in item:
                self.vertices_cube1.append(a)
        
        #generate vertex array for cube 2
        self.vertices_cube2 = []
        for item in cube2.get_data():
            for a in item:
                self.vertices_cube2.append(a)

        #calcualte indices for both cubes
        self.indices_cube1 = range(0, 36)
        self.indices_cube2 = range(0, 36)

        #add our render contexts to an instruction group for drawing
        self.instructions.add(self.cube1)
        self.instructions.add(self.cube2)
        self.canvas.add(self.instructions)
        with self.canvas:
            self.cb = Callback(self.setup_gl_context)
            PushMatrix()
            self.scene()
            PopMatrix()
            self.cb = Callback(self.reset_gl_context)
        Clock.schedule_interval(self.update_glsl, 1 / 60.)

    #create our scene and update our two models
    def scene(self):
        Color(0, 0, 0, 1)
        with self.cube2:
            self.cube1_mesh()
        with self.cube1:
            self.cube2_mesh()

    def setup_gl_context(self, *args):
        glEnable(GL_DEPTH_TEST)

    def reset_gl_context(self, *args):
        glDisable(GL_DEPTH_TEST)

    #setup the projection matrices 
    def update_glsl(self, *largs):
        aspect = float(self.width) / float(self.height)
        projection_mat = Matrix()
        projection_mat.perspective(45.0, aspect, 1.0, 80.0)
        model = Matrix().look_at(
            0.0, 0.0, 25.0,
            0.0, 0.0, 0.0,
            0.0, 1.0, 0.0)

        self.canvas['projection_mat'] = projection_mat
        self.canvas['modelview_mat'] = model

        self.cube1['projection_mat'] = projection_mat
        self.cube1['modelview_mat'] = model

        self.cube2['projection_mat'] = projection_mat
        self.cube2['modelview_mat'] = model
        self.rot_cube1.angle += 1
        self.rot_cube2.angle += 1

    def cube1_mesh(self):
        Color(0, 0, 0, 1)
        PushMatrix()
        
        self.rot_cube1 = Rotate(1, 0, 1, 0)

        vertex_format = [
            ('v_pos', 3, 'float'),
            ('v_color', 4, 'float'),
        ]

        UpdateNormalMatrix()
        self.mesh1 = Mesh(
            vertices=self.vertices_cube1,
            indices=self.indices_cube1,
            fmt=vertex_format,
            mode='triangles',
        )

        PopMatrix()

    def cube2_mesh(self):
        
        PushMatrix()
        Color(0, 0, 0, 1)
        self.rot_cube2 = Rotate(1, 0, -1, 0)

        vertex_format = [
            ('v_pos', 3, 'float'),
            ('v_color', 4, 'float'),
        ]

        UpdateNormalMatrix()
        self.mesh2 = Mesh(
            vertices=self.vertices_cube2,
            indices=self.indices_cube2,
            fmt=vertex_format,
            mode='triangles',
        )
        PopMatrix()

    def setup_scene(self):
        Color(0, 0, 0, 1)
        self.cube1_mesh()
        self.cube1_mesh()
#+END_SRC
