Scala + LWJGL + AWT = Resizable OpenGL Window

September 28, 2011

Following up on my previous post, I played around a bit more with Scala and LWJGL. What bothered me is that the standard examples for LWJGL and Scala had no resizable windows. But this is no more the case! It took me a bit to figure out how to translate various bloated Java examples to Scala but I am happy with the result.

Here is the code (btw.: does someone know how to make WP-Syntax a bit nicer looking?):

/**
 * Example program to get a resizable window using LWJGL + AWT in Scala.
 *
 * This code is public domain. Feel free to use it for anything you like.
 */
package org.fysx.lwjgltools
 
import java.awt._
import java.awt.event.{ComponentEvent, ComponentAdapter, WindowAdapter, WindowEvent}
import java.util.concurrent.atomic.{AtomicReference, AtomicBoolean}
 
import org.lwjgl._
import opengl.{Display,GL11,DisplayMode}
import GL11._
import input._
import math._
import org.lwjgl.opengl.AWTGLCanvas
 
class LWJGLFrame(text: String) extends Frame(text) {
  // tells us whether the screen was resized
  var resized = new AtomicBoolean()
  // the canvas on which the OpenGL context will be drawn
  var canvas = new Canvas()
  // this will store the new size after a resize
  var newCanvasSize = new AtomicReference[Dimension]
  // will be true if we received a window close request
  var closeRequested = false
  // used when resizing
  var newDim = new Dimension()
  // used for some animation
  var c = 0.0f
  var delta_c = 0.01f
 
  // added constructor in case we did not specify a String to LWJGLFrame()
  def this() {
    this("")
  }
 
  // add listener to the resize event
  canvas.addComponentListener (new ComponentAdapter() {
    override def componentResized(e: ComponentEvent) = {
//      println ("new size")
      newCanvasSize.set(canvas.getSize())
    }
  })
 
  // add the canvas our LWJGLFrame
  add(canvas, BorderLayout.CENTER)
 
  // add listener to the window close event
  addWindowListener(new WindowAdapter() {
    override def windowClosing (e: WindowEvent) = {
//      println ("close requested")
      closeRequested = true
    }
  })
 
  // adjust OpenGL context for change of the window size
  def resize() {
    newDim = newCanvasSize.getAndSet(null)
 
    if ( newDim != null ) {
//      println ("updating viewport")
      GL11.glViewport(0, 0, newDim.width, newDim.height)
    }
  }
 
  // perform the actual drawing
  def render() {
    c = c + delta_c
    if (c > 1.0f || c < 0.0f) delta_c = delta_c * -1.f
 
    GL11.glClearColor (0, 0, 0, 0)
    GL11.glClear(GL11.GL_COLOR_BUFFER_BIT)
 
    glColor3f (1.0f, 1.0f, 1.0f)
    glBegin(GL_TRIANGLES)
    glColor3f (0.25f, 1-c, 0.25f)
    glVertex3f (-0.5f, -0.75f, 0.0f)
    glColor3f (0.5f, 0.25f, 1.0f-c)
    glVertex3f (0.5f, -0.75f, 0.0f)
    glColor3f (c, 0.5f, 0.25f)
    glVertex3f (0.f, 0.75f, 0.0f)
    glEnd
  }
 
  // handle keyboard and other events
  def logic() {
    import Keyboard._
 
    if (isKeyDown(KEY_I)) println ("Key I was pressed!")
  }
 
  // starts the OpenGL loop
  def run() {
//    println ("executing run()")
    try {
      // setup of the Display (connect it to the canvas)
      Display.setParent(canvas)
      try {
        Display.create()
      } catch {
        case e: LWJGLException => {
          e.printStackTrace()
          println (e.getMessage())
        }
      }
 
      Display.setVSyncEnabled(true)
 
      // the actual main loop
      while (!Display.isCloseRequested() && !closeRequested) {
        resize()
        Display.update()
 
        logic()
 
        render()
 
        Display.sync(60)
      }
 
      // we're done so we have to destroy the Display
      Display.destroy()
    }
  }
}
 
object Main {
  // actual entry point of the program
  def main(args: Array[String]) {
    var lwjglframe = new LWJGLFrame ("Scala + LWJGL + AWT = resizable window")
 
    lwjglframe.setMinimumSize(new Dimension(320, 240))
    lwjglframe.setSize(new Dimension(640, 480))
    lwjglframe.setVisible(true)
 
    lwjglframe.run()
 
    lwjglframe.dispose()
  }
}

Tags

Scala + LWJGL = Running Program

September 22, 2011

scala + lwjglI thought I would try to get the Lightweight Java Game Library (LWJGL) running in Scala as both are really hip nowadays. Turns out that there is not much available on teh internets that works. Most of them use the “Simple Build Tool” (sbt) which I did not find simple at all. Also they have a new version 0.10.x which is incompatible to the Scala+LWJGL examples which use sbt version 0.7.x. Yay!

Anyhow, I used the Scala code from http://lwjgl.org/forum/index.php?topic=3416.0 and finally managed to get it run without any “simple” tools or other dependencies. Here is how:

  1. Download the current LWJGL release (as of writing: 2.7.1). This should give you a zip file with a few jars, dlls and so files.
  2. Download and install Scala from http://www.scala-lang.org/ (as of writing: 2.9.1)
  3. Create the folders “lwjglproject/lib” and “lwjglproject/src”
  4. Extract all .jars to “lwjglproject/lib”. When using Windows do so with the dlls, on Linux put the .so files there and Mac users probably don’t have to do anything (that’s what I’m always being told)
  5. Create a source file “lwjglproject/src/lwjgl.scala” with contents from the example (also available further down)
  6. Compile the code with
    scalac -classpath ./lib/lwjgl.jar src/lwjgl.scala

    this should  give you some .class files

  7. Run the example by executing
    scala -classpath lib/lwjgl.jar -Djava.library.path=./lib Main
  8. Enjoy

Oh, and if you want to run the program directly with java to have to specify the path to scala-library.jar and add the (current) “lwjglproject” path to the classpath:

java -classpath .:lib/lwjgl.jar:/lib/scala-library.jar -Djava.library.path=./lib Main

Here is the code for the example:

import org.lwjgl._
import opengl.{Display,GL11,DisplayMode}
import GL11._
import input._
import math._
 
object Main{
	val GAME_TITLE = "My Game"
	val FRAMERATE = 60
	val width = 640
	val height = 480
 
	val player = new Player(0,0,0);
 
	var finished = false
	var angle = 0.0f
	var rotation = 0.0f
 
	def main(args:Array[String]){
		var fullscreen = false
		for(arg <- args){
			arg match{
				case "-fullscreen" =>
					fullscreen = true
			}
		}
 
		init(fullscreen)
		run
	}
 
	def init(fullscreen:Boolean){
 
		println("init Display")
		Display.setTitle(GAME_TITLE)
		Display.setFullscreen(fullscreen)
		Display.setVSyncEnabled(true)
		Display.setDisplayMode(new DisplayMode(width,height))
		Display.create
 
		println("init gl")
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_LIGHTING)
		glEnable(GL_LIGHT0)
		adjustcam
	}
 
	def adjustcam(){
		val v = Display.getDisplayMode.getWidth.toFloat/Display.getDisplayMode.getHeight.toFloat
		printf("v:%f",v)
		glMatrixMode(GL_PROJECTION)
		glLoadIdentity
		glFrustum(-v,v,-1,1,1,100)
		glMatrixMode(GL_MODELVIEW)
	}
 
	def cleanup(){
		Display.destroy
	}
 
	def run(){
		while(!finished){
			Display.update
 
			logic
			render
 
			Display.sync(FRAMERATE)
		}
	}
 
	def logic(){
	    // in scala we can locally import all methods from Keyboard.
	    import Keyboard._
 
		if(isKeyDown(KEY_ESCAPE))
			finished = true
		if(Display.isCloseRequested)
			finished = true
 
		// rx and rx store our keyboard input as direction  
		var ry = 0
		var rx = 0
 
		// keys are IKJL for up down left right
 
		if(isKeyDown(KEY_I))
			ry += 1
		if(isKeyDown(KEY_K))
			ry -= 1
		if(isKeyDown(KEY_J))
			rx -= 1
		if(isKeyDown(KEY_L))
			rx += 1
 
		// this makes the direction relative to the camera position
		// it is a simple rotation matrix you may know from linear algebra
		val ax = rx*cos(-rotation.toRadians)-ry*sin(-rotation.toRadians)
		val ay = rx*sin(-rotation.toRadians)+ry*cos(-rotation.toRadians)
 
		player.x += 0.1f*ax.toFloat
		player.y += 0.1f*ay.toFloat
 
		// this rotates our camera around the center
		angle += 2.0f % 360
		rotation += 0.2f
	}
 
	def renderGrid(size : Int){
	    // this creates the nice looking background.
		glDisable(GL_LIGHTING)
		glBegin(GL_LINES)
		for(i <- -size to size){
			glVertex2i(i,-size)
			glVertex2i(i, size)
			glVertex2i(-size,i)
			glVertex2i( size,i)
		}
		glEnd
		glEnable(GL_LIGHTING)
	}
 
	def render(){
		glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity
 
		glTranslatef(0,0,-20)
		glRotatef(-70,1,0,0)
		glRotatef(rotation,0,0,1)
 
		glPushMatrix
		player.applyPos
		//rotate the player just for fun
		glRotatef(angle, 0, 0, 1.0f)
		player.draw
		glPopMatrix
 
		//without background, motion is not visible
		// a green grid is nice and retro
		glColor3f(0,1,0)
		renderGrid(10000)
	}
}
 
class Player(nx:Float,ny:Float,nz:Float){
	var x:Float = nx
	var y:Float = ny
	var z:Float = nz
 
	def applyPos {
	    glTranslatef(x,y,z)
	}
 
	def draw = {
		glColor3f(1, 0, 0)
		glBegin(GL_TRIANGLE_FAN)
		glNormal3d( 0, 0, 1);	glVertex3d(0,0,0.5)
		glNormal3d(-1,-1, 1);	glVertex2d(-0.5, -0.5)
		glNormal3d( 1,-1, 1);	glVertex2d(0.5, -0.5)
		glNormal3d( 1, 1, 1);	glVertex2d(0.5, 0.5)
		glNormal3d(-1, 1, 1);	glVertex2d(-0.5, 0.5)
		glNormal3d(-1,-1, 1);	glVertex2d(-0.5, -0.5)
		glEnd
	}
}

Tags