Recently I've been looking at Povray, pyprocessing, and cfdg (version 3.0) as tools for creating digital images. I have branched two separate blogs where I mainly explore jruby + processing and processing.py

Thursday, 22 November 2012

Whither the MouseWheel in Processing-2.0

What really seems to have buggered things up just recently is the recent adoption of  jogl NEWT (Native Windowing Toolkit), which could be a showstopper for MouseWheel users as the mousewheel listeners (java.awt.etc) are now silently failing......
I kind of hope this issue will just go a away (but I'm not optimistic), so I'm starting of with this posting to my blog, instead of posting to the discussion forum which often gets swamped with homework etc.

It was always the case that library users, or anyone else for that matter who wanted to use the mouse wheel in processing had to write their own MouseWheelListener, however since the introduction of the new processing.event.Event, there was the possibility that we might be able to extend the Event class. Sadly I think there has been a decision not to support the MouseWheel (possibly in favor of the new TouchEvent?).  When I experimented in NetBeans it seemed as though we might be able to access a mouseWheelMoved method of the NEWTMouseAdapter, only to find it had not been implemented. Somewhat annoyingly but probably for good practical reasons it does not throw an unsupportedOperationException, but just silently fails. Here is the the offending line/s of code in PGL.java (r10447):-


  @Override
    public void mouseWheelMoved(com.jogamp.newt.event.MouseEvent e) {
      // Not supported in Processing.
    }

This is the sort of hack that may be required (but probably won't work either because we also need to register the native event listener in the first place).
17     @Override
18     public void mouseWheelMoved(com.jogamp.newt.event.MouseEvent e) {
19       nativeMouseEvent(e, MouseEvent.ROTATE);
20     }
....................
   1 package processing.event;
   2 
   3 public class MouseWheelEvent extends Event {
   4     int rotate = 0;
   5     public MouseWheelEvent(Object nativeObject, long millis, int action, int modifiers) {
   6         super(nativeObject, millis, action, modifiers);
   7         this.flavor = MOUSE;
   8         this.rotate += ((com.jogamp.newt.event.MouseEvent)nativeObject).getWheelRotation();
   9     }
  10     
  11     public int getRotation(){
  12         return rotate;    
  13     }
  14 }
 
Update 2 December 2012, I have proposed an enhancement to processing-2.0 to implement the mouseWheelMoved in processing-2.0 to vote for this enhancement follow this link.
Update 18 January just tried latest svn release (includes preliminary support for MouseWheel) looking good!!!!


   public void mouseEvent(MouseEvent event) {
        if (event.getAction() == MouseEvent.WHEEL) {
            System.out.println(event.getAmount());
            }
        }

Update 11 February 2013 processing development got moved to github see issue/request here:- https://github.com/processing/processing/issues/1461

Sunday, 11 November 2012

Simple object import processing.py

Since processing-2.0 object (wavefront) import is built in. Checkout this sketch, vanilla processing version was a rocket:-
   1 ##
   2 # Load and Display an OBJ Shape. In this a cow
   3 ##
   4 ry = 0
   5 
   6 def setup():
   7   size(640, 360, P3D)
   8   global cow 
   9   cow = loadShape("/home/tux/cow.obj")
  10   
  11   
  12 def draw():
  13   background(0)
  14   lights()
  15   global ry
  16   translate(width/2, height/2, -200)
  17   rotateZ(PI)
  18   rotateY(ry)
  19   scale(50)
  20   shape(cow)  
  21   ry += 0.02

Retained menger processing.py

If I had any lingering doubts about which python implementation of processing was best between pyprocessing and processing.py this sketch sealed it for me. Since processing.py was updated (not complete yet) to support processing-2.0 it is streets ahead in performance, especially now it supports retained shape (FBO object). Theoreticaly I think something similar should be possible via pyglet, however it is much harder to do, and the jvm will will probably still outperform regular python and probably pypy as well. This sketch was developed using jEdit and the development version of processing.py see previous sketch.
   1 #################
   2 # Retained menger by Martin Prout
   3 # using processing.py
   4 # featuring processing-2.0 core
   5 ################
   6 
   7 angle = 0.0
   8 STEP = PI / 360.0
   9 MIN_SIZE = 20
  10 
  11 def setup():
  12     size(800, 600, P3D)  
  13     camera(width/2, height/2, height, 0, 0, 0, 0, -1, 0)
  14     smooth(8)
  15     global menger
  16     menger = createShape(PShape.GROUP)
  17     createMenger(0, 0, 0, height * 0.8)
  18     
  19     
  20 def draw():
  21     background(20, 20, 200)
  22     noStroke()
  23     lights()
  24     defineLights()
  25     global angle, menger
  26     angle = (angle + STEP) % TWO_PI
  27     rotateZ(angle)
  28     rotateY(angle) 
  29     shape(menger)
  30 
  31 def createMenger(xx, yy, zz, sz):
  32     """
  33     A recursive function creates and stores custom cubes
  34     to menger PShape.GROUP
  35     """
  36     u = sz / 3
  37     if (sz < MIN_SIZE): # recursion limited by minimum cube size
  38         noStroke()
  39         menger.addChild(createCube(xx, yy, zz, sz)) # create and add a cube        
  40     else:
  41         for i in range(-1, 2, 1):
  42             for j in range(-1, 2, 1):
  43                 for k in range(-1, 2, 1):
  44                     if ((abs(i) + abs(j) + abs(k)) > 1):
  45                         createMenger(xx + (i * u), yy + (j * u), zz + (k * u), u)             
  46                      
  47                         
  48 def createCube(xx, yy, zz, sz):
  49     """
  50     Function returns a custom cube PShape
  51     Requires processing 
  52     """
  53     cube = createShape(QUADS)
  54     cube.ambient(50)    
  55     cube.specular(30) 
  56     
  57     # Front face
  58     
  59     cube.fill(255)
  60     cube.normal(0, 0, 1)
  61     cube.vertex(-sz / 2 + xx, -sz / 2 + yy, -sz / 2 + zz)
  62     cube.vertex(+sz / 2 + xx, -sz / 2 + yy, -sz / 2 + zz)
  63     cube.vertex(+sz / 2 + xx, +sz / 2 + yy, -sz / 2 + zz)
  64     cube.vertex(-sz / 2 + xx, +sz / 2 + yy, -sz / 2 + zz)
  65     
  66     # Back face
  67     
  68     cube.normal(0, 0, -1)
  69     cube.vertex(-sz / 2 + xx, -sz / 2 + yy, +sz / 2 + zz)
  70     cube.vertex(+sz / 2 + xx, -sz / 2 + yy, +sz / 2 + zz)
  71     cube.vertex(+sz / 2 + xx, +sz / 2 + yy, +sz / 2 + zz)
  72     cube.vertex(-sz / 2 + xx, +sz / 2 + yy, +sz / 2 + zz)
  73     
  74     # Left face
  75     
  76     cube.normal(1, 0, 0)
  77     cube.vertex(-sz / 2 + xx, -sz / 2 + yy, -sz / 2 + zz)
  78     cube.vertex(-sz / 2 + xx, -sz / 2 + yy, +sz / 2 + zz)
  79     cube.vertex(-sz / 2 + xx, +sz / 2 + yy, +sz / 2 + zz)
  80     cube.vertex(-sz / 2 + xx, +sz / 2 + yy, -sz / 2 + zz)
  81     
  82     # Right face
  83     
  84     cube.normal(-1, 0, 0)
  85     cube.vertex(+sz / 2 + xx, -sz / 2 + yy, -sz / 2 + zz)
  86     cube.vertex(+sz / 2 + xx, -sz / 2 + yy, +sz / 2 + zz)
  87     cube.vertex(+sz / 2 + xx, +sz / 2 + yy, +sz / 2 + zz)
  88     cube.vertex(+sz / 2 + xx, +sz / 2 + yy, -sz / 2 + zz)
  89     
  90     # Top face
  91     
  92     cube.normal(0, 1, 0)
  93     cube.vertex(-sz / 2 + xx, -sz / 2 + yy, -sz / 2 + zz)
  94     cube.vertex(+sz / 2 + xx, -sz / 2 + yy, -sz / 2 + zz)
  95     cube.vertex(+sz / 2 + xx, -sz / 2 + yy, +sz / 2 + zz)
  96     cube.vertex(-sz / 2 + xx, -sz / 2 + yy, +sz / 2 + zz)
  97     
  98     # Bottom face
  99     
 100     cube.normal(0, -1, 0)
 101     cube.vertex(-sz / 2 + xx, +sz / 2 + yy, -sz / 2 + zz)
 102     cube.vertex(+sz / 2 + xx, +sz / 2 + yy, -sz / 2 + zz)
 103     cube.vertex(+sz / 2 + xx, +sz / 2 + yy, +sz / 2 + zz)
 104     cube.vertex(-sz / 2 + xx, +sz / 2 + yy, +sz / 2 + zz)
 105     cube.end()
 106     return cube
 107 
 108 
 109 def defineLights():
 110     # Orange point light on the right
 111     pointLight(150, 100, 0,  200, -150, 0)     
 112     # Blue directional light from the left
 113     directionalLight(0, 102, 255, 1, 0, 0)      
 114     # Yellow spotlight from the front
 115     spotLight(255, 255, 109, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2) 

Using FBO in processing.py

Since processing-2.0 there is the possibility of creating a retained shape (or FBO) this is achieved by creating a PShape object, here is example of that in processing.py. Note: I'm using the current development version on github, except I re-compiled it using jython-2.5.3 (standalone) and the latest version of the processing core.jar (all compiled with jdk1.7.0_09).
   1 from arcball.arcball import ArcBall
   2 
   3 """
   4 test_arcball.py by Martin Prout a processing.py sketch
   5 Sketch features the use of ArcBall class, provides intuitive manipulation of sketch object
   6 ArcBall class uses Quaternions class for efficient calculation of rotation, hold down x, y or z
   7 keys to constrain rotation to that plane otherwise drag mouse for smooth rotation
   8 @todo add mousewheel zoom?
   9 """
  10 X = 0
  11 Y = 1
  12 Z = 2
  13 
  14 def setup():
  15     size(800, 600, P3D)
  16     smooth(16)
  17     global arcball, my_cube
  18     arcball = ArcBall(width/2.0, height/2.0, min(width - 20, height - 20) * 0.5)
  19     my_cube = cube(arcball.radius)
  20 
  21 def draw():
  22     background(0xff66c0ff)
  23     translate(width/2.0, height/2.0)
  24     defineLights()
  25     update()
  26     lights()
  27     shape(my_cube)
  28     
  29     
  30 def update():
  31     """
  32     wrap arcball update and rotation as a local function
  33     """
  34     theta, x, y, z = arcball.update()
  35     rotate(theta, x, y, z)    
  36 
  37 def mousePressed():
  38     arcball.mousePressed(mouseX, mouseY)
  39   
  40 def mouseDragged():
  41     arcball.mouseDragged(mouseX, mouseY) 
  42 
  43 def defineLights():
  44     """
  45     Light up the cube
  46     """
  47     ambientLight(50, 50, 50)
  48     pointLight(150, 100, 0, 200, -150, 0)
  49     directionalLight(0, 102, 255, 1, 0, 0)
  50     spotLight(255, 255, 109, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
  51 
  52 def keyPressed():
  53     """
  54     Constrain axis of rotation by holding down key 
  55     corresponding to axis
  56     """
  57     if (key == 'x'):  
  58         arcball.selectAxis(X)
  59     if (key == 'y'):  
  60         arcball.selectAxis(Y)
  61     if (key == 'z'):  
  62         arcball.selectAxis(Z)
  63 
  64 def keyReleased():
  65     """
  66     Release axis constraint
  67     """
  68     arcball.selectAxis(-1)
  69 
  70 def cube(sz):
  71     """
  72     Create and return the FBO cub
  73     """
  74     sz *= 0.5       
  75     cub = createShape(QUADS)
  76     cub.fill(200,  200,  200,  255)
  77     cub.ambient(50)
  78     cub.specular(30)
  79     cub.vertex(-sz, -sz, -sz)
  80     cub.vertex(+sz, -sz, -sz)
  81     cub.vertex(+sz, +sz, -sz)
  82     cub.vertex(-sz, +sz, -sz)
  83     cub.vertex(-sz, -sz, +sz)
  84     cub.vertex(+sz, -sz, +sz)
  85     cub.vertex(+sz, +sz, +sz)
  86     cub.vertex(-sz, +sz, +sz)
  87     cub.vertex(-sz, -sz, -sz)
  88     cub.vertex(-sz, -sz, +sz)
  89     cub.vertex(-sz, +sz, +sz)
  90     cub.vertex(-sz, +sz, -sz)
  91     cub.vertex(+sz, -sz, -sz)
  92     cub.vertex(+sz, -sz, +sz)
  93     cub.vertex(+sz, +sz, +sz)
  94     cub.vertex(+sz, +sz, -sz)
  95     cub.vertex(-sz, -sz, -sz)
  96     cub.vertex(+sz, -sz, -sz)
  97     cub.vertex(+sz, -sz, +sz)
  98     cub.vertex(-sz, -sz, +sz)
  99     cub.vertex(-sz, +sz, -sz)
 100     cub.vertex(+sz, +sz, -sz)
 101     cub.vertex(+sz, +sz, +sz)
 102     cub.vertex(-sz, +sz, +sz)
 103     cub.end()
 104     return cub

The Quaternion
   1 from math import acos, sqrt
   2 from processing.core import PConstants
   3 
   4 class Quaternion(object):
   5     """
   6     Helper class, originally written for use by ArcBall class
   7     """
   8  
   9     def __init__(self, w = 1.0,  x = 0,  y = 0,  z = 0):   
  10         """
  11         Important to initialize default with unit matrix
  12         """
  13         self.w, self.x, self.y, self.z = w,  x,  y,  z
  14     
  15     def reset(self):
  16         """
  17         Reset quaternion to unit matrix
  18         """
  19         self.w = 1.0
  20         self.x = 0.0
  21         self.y = 0.0
  22         self.z = 0.0
  23         
  24     def set(self,  w, v):
  25         """
  26         Set quaternion given w float and PVector
  27         """
  28         self.w = w
  29         self.x = v.x
  30         self.y = v.y
  31         self.z = v.z
  32         
  33     def copy(self,  q):
  34         """
  35         Copy quaternion q
  36         """
  37         self.w = q.w
  38         self.x = q.x
  39         self.y = q.y
  40         self.z = q.z  
  41         
  42     @classmethod  
  43     def mult(clas, q1, q2): 
  44         """
  45         class method returns a new instance of Quaternion as a product of 
  46         two input Quaternions
  47         """        
  48         x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y
  49         y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z
  50         z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x
  51         w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
  52         return Quaternion(w,  x,  y,  z)
  53         
  54     def getValue(self):
  55         """
  56         Using processing epsilon
  57         """
  58         sa = sqrt(1.0 - self.w * self.w)
  59   
  60         if (sa < PConstants.EPSILON):
  61             sa = 1.0
  62         return [acos(self.w) * 2, self.x / sa, self.y / sa, self.z / sa]
  63  
  64 

The ArcBall class
   1 from processing.core import PVector
   2 from quaternion import Quaternion
   3 from math import sqrt
   4 
   5 class ArcBall(object):
   6     """
   7     Class contains ArcBall logic see test_arcball.py for usage
   8     """
   9     def __init__(self, cx, cy, radius):
  10         """
  11         Initialize instance of ArcBall with no constraint on axis of rotation
  12         """
  13         self.center_x = cx
  14         self.center_y = cy
  15         self.radius = radius
  16         self.v_down = PVector()
  17         self.v_drag = PVector()
  18         self.q_now = Quaternion()
  19         self.q_down = Quaternion()
  20         self.q_drag = Quaternion() 
  21         self.axis_set = [PVector(1.0, 0.0, 0.0), PVector(0.0, 1.0, 0.0), PVector(0.0, 0.0, 1.0)]
  22         self.axis = -1  
  23         
  24     def selectAxis(self, axis):
  25         """
  26         call this from sketch (typically in keyPressed() to constrain rotation to one axis)
  27         valid input 0, 1, 2 or -1
  28         """
  29         if ((axis == -1) or (axis == 0) or (axis == 1) or (axis == 2)):
  30           self.axis = axis
  31         
  32     def __mouse2sphere(self, x, y):
  33         """
  34         private map mouse to ArcBall (sphere)
  35         """
  36         v = PVector()
  37         v.x = (x - self.center_x) / self.radius
  38         v.y = (y - self.center_y) / self.radius
  39         mag = v.x * v.x + v.y * v.y
  40         if (mag > 1.0) :
  41             v.normalize()
  42         else:
  43             v.z = sqrt(1.0 - mag)
  44         if (self.axis != -1):
  45             v = self.__constrain(v, self.axis_set[self.axis])
  46         return  v  
  47     
  48     def mousePressed(self, x, y):
  49         """
  50         pass in mouse.x and mouse.y parameters from sketch
  51         """
  52         self.v_down = self.__mouse2sphere(x, y)
  53         self.q_down.copy(self.q_now)
  54         self.q_drag.reset()
  55 
  56     def mouseDragged(self, x, y):
  57         """
  58         pass in mouse.x and mouse.y parameters from sketch
  59         """
  60         self.v_drag = self.__mouse2sphere(x, y)
  61         self.q_drag.set(PVector.dot(self.v_down, self.v_drag), self.v_down.cross(self.v_drag))
  62         
  63     def __constrain(self, vector, axis):
  64         """
  65         private constrain (used to constrain axis)
  66         """
  67         vector.sub(axis.mult(axis, PVector.dot(axis, vector)))
  68         vector.normalize()
  69         return vector
  70         
  71     def update(self):
  72         """
  73         Call this function in the sketch draw loop to get rotation matrix as an array 
  74         """
  75         self.q_now = Quaternion.mult(self.q_drag, self.q_down)
  76         return self.__quat2matrix(self.q_now)
  77 
  78     def __quat2matrix(self,  q) :
  79         """
  80         private return matrix as array
  81         """
  82         rot = q.getValue()
  83         return rot

Followers

Blog Archive

About Me

My photo
Pembrokeshire, United Kingdom
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2