Saturday, April 24, 2010

Label, Scale9, FontManager, and Resource

Yes, this is my third post today, but it's Saturday and I'm cranking through code now that I've gotten over the mouse-picking hurdle!

I have created a new component "Label" that is moderately self-explanatory, but represents a single-line of text. With this Resource comes to fruition as a simple lookup call "Resource(resourceName)" that will find a resource in the defined resource paths (defaults to look in "resource" path). Additionally I have created a FontManager that simplifies the management of fonts in the system. I have plans in the long-run to create a bitmap-font generator from AWT fonts, but for now getting a font is as easy as "FontManager("Arial")" presuming there's an AngelCode font definition in the Resource lookup path by the name of "Arial.fnt".

See the following test of Label:

package org.sgine.ui

import scala.io.Source

import org.sgine.input.event.MousePressEvent

import org.sgine.render.Renderer
import org.sgine.render.font.FontManager
import org.sgine.render.scene.RenderableScene

import org.sgine.scene.GeneralNodeContainer

object TestLabel {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test RenderScene")
  
  val scene = new GeneralNodeContainer()
  val component = new Label()
  component.location.z := -500.0
  component.font := FontManager("Franklin")
  component.text := "Hello World!"
  scene += component
  
  r.renderable := RenderableScene(scene)
 }
}

Even more exciting is Scale-9 (a.k.a nine-slice) support that opens up more advanced skinning functionality and in the short-run allows creation of pretty buttons; bet you can guess what's up next. ;)

Anyway, here's a quick test using the new Scale9 component:

package org.sgine.ui

import org.sgine.core.Resource

import org.sgine.render.Renderer
import org.sgine.render.scene.RenderableScene

import org.sgine.scene.GeneralNodeContainer

object TestScale9 {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test Scale-9")
  
  val scene = new GeneralNodeContainer()
  
  val component = new Scale9()
  component(Resource("scale9/windows/button/hover.png"), 3.0, 3.0, 4.0, 5.0)
  component.width := 200.0
  component.height := 50.0
  component.location.z := -500.0
  scene += component
  
  r.renderable := RenderableScene(scene)
 }
}

Here's a screenshot of the application:



That's using a pretty Windows 7 skinned button in a hover state.

Component Mouse Events

Now that I've got mouse-picking all figured out things are moving swiftly forward. I have created a new "bounding" package in sgine to support hit testing for mouse events and collision detection in the future. One of the awesome things about Scala's mix-ins is that I can modularize functionality down to a few traits that make up specific functionality to allow users of the engine to take on as much or as little of the system as they want. To this end I've created two classes: MatrixPropertyContainer (name to change to MatrixObject) and BoundingObject. MatrixPropertyContainer represents an immutable property "matrix" that returns a mutable Matrix4 and BoundingObject similarly contains an immutable property "bounding" that returns a Bounding instance.

In RenderableScene I listen for events on Mouse, walk through the NodeView for my scene matching Nodes that have both of these traits and then do a hit-test with them. The result is shown in the test below:

package org.sgine.ui

import org.sgine.core.Resource

import org.sgine.event.EventHandler

import org.sgine.input.event.MouseEvent

import org.sgine.render.Renderer
import org.sgine.render.scene.RenderableScene

import org.sgine.scene.GeneralNodeContainer

object TestMousePicking {
 def main(args: Array[String]): Unit = {
  // Create the Renderer
  val r = Renderer.createFrame(1024, 768, "Test Mouse Picking")
  
  // Create a mutable scene
  val scene = new GeneralNodeContainer()
  
  // Create an image to show the puppies
  val component = new Image()
  component.location.x := -200.0
  component.location.z := -500.0
  component.scale.x := 1.5
  component.rotation.y := Math.Pi / -4.0
  component.source := Resource("resource/puppies.jpg")   // 700x366
  scene += component
  
  // Add our scene to the renderer
  r.renderable := RenderableScene(scene, false)
  
  // Add a listener to listen to all mouse events to the picture
  component.listeners += EventHandler(mouseEvent)
 }
 
 // The method that is invoked when a mouse event occurs on the picture
 private def mouseEvent(evt: MouseEvent) = {
  println("MouseEvent: " + evt)
 }
}

Though adding a listener to mouse events is a one-line call the underlying functionality remains modular and optional to rendering functionality in the application giving the benefits of simple functionality in classes that make use of it, and trimmed down explicit functionality in those classes that do not. The mouse event system dispatches "press", "release", "move", and "wheel" events. I have on my todo to incorporate "over" and "out" events as well.

Mouse Picking

The reason for the delay since the last post has been the fact that I've been bogged down with mouse-picking for a while. Getting the math correct to properly handle ray-casting to 3D object space in a fast and efficient way took a while to wrap my head around, but finally, with quite a bit of help from my math guru of a wife I have a nice and efficient "translateLocal" method in Renderer:

def translateLocal(x: Double, y: Double, m: Matrix4, store: MutableVector3) = {
  synchronized {
   storeRay.origin.set(0.0, 0.0, 0.0)
   storeRay.direction.set(x, y, -nearDistance)
   
   storeMatrix.set(m)
   storeMatrix.invert()
   storeRay.transform(storeMatrix)
   storeRay.translateLocal(store)
   
   store
  }
 }

Today I'm working on integration of this within the Component architecture to support determination of hits within bounding regions. This does not create any additional garbage and I can run this on my machine about a million times in 700ms. Ideally I'd like the performance to be better than that, but since this does not absolutely have to be done within the rendering thread and with optimizations to limit the checks in the first place the performance should be fine.

Upgrading to LWJGL 2.4.2 from 2.2.2 gave me a framerate improvement of about 40% in my examples which is nice. :)

Next steps are to finish bounding support for BoundingQuad (what all my 2D UI components will use) and then start working on Scale-9 images, Label, TextInput, and multi-line text support with selection ability along with focus management.

Sunday, April 11, 2010

ImageCube

It's been a few days since my last post, but I've been endeavoring to make strides toward fully 3D UI concepts and it hasn't been altogether easy to do. It is relatively straight-forward to create a 2D UI and it's even relatively easy to create a 3D game, but developing a powerful and easy-to-use engine that provides both full 3D and advanced user-interface development functionality is a more complicated road to take.

The following video shows my ImageCube leveraging 3D, adjustable properties, easings, and my new effects framework. This is all still very early development, but as you can see the "effects" essentially make up a work-flow of actions to take in sequence. At the moment I've got:
  • CompositeEffect - executes multiple effects in sequence
  • PauseEffect - creates a delay
  • PropertyChangeEffect - will apply a value to a property upon invocation, primarily designed to work with AdjustableProperties
  • PropertySetEffect - immediately applies a value to the property ignoring any existing adjusters



Here's the code:
package org.sgine.ui

import org.sgine.core.Resource

import org.sgine.easing.Elastic
import org.sgine.easing.Linear

import org.sgine.effect.CompositeEffect
import org.sgine.effect.PropertyChangeEffect
import org.sgine.effect.PropertySetEffect

import org.sgine.math.mutable.MatrixPropertyContainer

import org.sgine.property.adjust.EasingNumericAdjuster

import org.sgine.render.Renderer
import org.sgine.render.scene.RenderableScene

import org.sgine.scene.GeneralNodeContainer

object TestCube {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test Cube", 4, 8, 4, 4)
  r.verticalSync := false
  
  val scene = new GeneralNodeContainer()
  
  val cube = new ImageCube()
  cube.location.z := -1500.0
  cube.rotation.x.adjuster = new EasingNumericAdjuster(Linear.easeIn, 2.0)
  cube.rotation.y.adjuster = new EasingNumericAdjuster(Linear.easeIn, 4.0)
  cube.rotation.z.adjuster = new EasingNumericAdjuster(Linear.easeIn, 6.0)
  cube.location.x.adjuster = new EasingNumericAdjuster(Elastic.easeInOut, 4.0)
  cube(Resource("resource/sgine_256.png"), 256.0, 256.0)
  scene += cube
  
  r.renderable := RenderableScene(scene, showFPS = true)
  
  // Rotate the cube perpetually on the x-axis
  val rx1 = new PropertyChangeEffect(cube.rotation.x, Math.Pi * 2.0)
  val rx2 = new PropertySetEffect(cube.rotation.x, 0.0)
  val rotateX = new CompositeEffect(rx1, rx2)
  rotateX.repeat = -1
  
  // Rotate the cube perpetually on the y-axis
  val ry1 = new PropertyChangeEffect(cube.rotation.y, Math.Pi * 2.0)
  val ry2 = new PropertySetEffect(cube.rotation.y, 0.0)
  val rotateY = new CompositeEffect(ry1, ry2)
  rotateY.repeat = -1
  
  // Rotate the cube perpetually on the z-axis
  val rz1 = new PropertyChangeEffect(cube.rotation.z, Math.Pi * 2.0)
  val rz2 = new PropertySetEffect(cube.rotation.z, 0.0)
  val rotateZ = new CompositeEffect(rz1, rz2)
  rotateZ.repeat = -1
  
  // Move the cube back and forth perpetually on the x-axis
  val me1 = new PropertyChangeEffect(cube.location.x, -600.0)
  val me2 = new PropertyChangeEffect(cube.location.x, 600.0)
  val move = new CompositeEffect(me1, me2)
  move.repeat = -1
  
  // Start effects
  rotateX.start()
  rotateY.start()
  rotateZ.start()
  move.start()
 }
}

Wednesday, April 7, 2010

eSCALAtion Blog Interview

I was asked to give a brief interview with the Scala blog "eSCALAtion" regarding sgine. Here is the link: http://dgronau.wordpress.com/2010/04/05/interview-with-matt-hicks/

Monday, April 5, 2010

Beginnings of Components

Last week I made a new post daily outlining the work of the day, showing a screenshot of the progress, and posting a code sample. This past weekend I didn't post anything, but not because I wasn't working hard, but because primarily what I was working on had no real visual presence. A quick look at the change log for Mercurial will show significant increase in code being checked in: see!

Last week I showed how nicely both images and text can be displayed to the screen with relatively little amounts of code, but that code is not very practical from a modularity and simplicity perspective. The goal was never for developers to rely specifically on that functionality but to work as a core to build upon within sgine to provide more powerful abstractions for developers to use in order to develop with simplicity and logically.

This past weekend I spent quite a bit of time working on what I consider to be the fulfillment of that objective (or at least the beginning of) through the use of what I call Components. Components in sgine are sort of a hybrid between what would classically be a Node/Spatial in a 3D scenegraph and a Component / Widget in a standard GUI like Swing. There will be much more coming soon on this topic, but for now I'll leave you with a very short snippet to portray the same as my original example did of the puppies but using the Image component in sgine:

package org.sgine.ui

import org.sgine.core.Resource

import org.sgine.easing.Elastic

import org.sgine.property.adjust.EasingNumericAdjuster

import org.sgine.render.Renderer
import org.sgine.render.scene.RenderableScene

import org.sgine.scene.GeneralNodeContainer

object TestImage {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test RenderScene")
  
  val scene = new GeneralNodeContainer()
  val component = new Image()
  component.location.z := -1000.0
  component.source := Resource("resource/puppies.jpg")
  scene += component
  
  r.renderable := RenderableScene(scene)
 }
}

Friday, April 2, 2010

Font Testing

I think I'm just about done with the first pass on bitmap font work. I still need to provide convenience methods for determining size of blocks of text and paragraph support as well as creating a system to dynamically generate bitmapped fonts via AWT at runtime, but I think I've finally knocked out all the major bugs. Here is a prettier example using the AngelCode fonts I've been working on:



Finally starting to look like something useful and less like a crappy hobby project, but still a long way to go. Anyway, here's the source:

package org.sgine.render

import org.sgine.math.mutable.Matrix4

import javax.imageio.ImageIO

import org.lwjgl.opengl.GL11._

import scala.io.Source

object TestFonts {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test Fonts")

  val franklinFont = AngelCodeFont(Source.fromURL(getClass.getClassLoader.getResource("resource/Franklin.fnt")), getClass.getClassLoader.getResource("resource/Franklin.png"))
  val arialFont = AngelCodeFont(Source.fromURL(getClass.getClassLoader.getResource("resource/Arial.fnt")), getClass.getClassLoader.getResource("resource/Arial.png"))
  val lcdFont = AngelCodeFont(Source.fromURL(getClass.getClassLoader.getResource("resource/lcd.fnt")), getClass.getClassLoader.getResource("resource/lcd.png"))
  
  val m1 = Matrix4().translate(x = -400.0, z = -1000.0)
  val m2 = Matrix4().translate(x = -100.0, y = -50.0, z = -1000.0)
  val fps = FPS(1.0, lcdFont)
  
  val a = new Array[() => Unit](5)
  a(0) = MatrixState(m1)
  a(1) = () => franklinFont.drawString("Franklin Gothic Heavy with a blue gradient.", true)
  a(2) = MatrixState(m2)
  a(3) = () => arialFont.drawString("Standard Arial", false)
  a(4) = fps
  r.renderable := RenderList(a)
 }
}

AngelCode Fonts - Kerning

I already did most of the legwork earlier this week, but hadn't gotten around to turning on support for kerning, but that's all changed now:



Pretty easy to see the benefits of kerning here. However, this example also shows I seem to have a little problem with my low-hanging characters like 'g' and 'p'. Hopefully that will be fixed in the next update.

For now, here's the source:

package org.sgine.render

import org.sgine.math.mutable.Matrix4

import javax.imageio.ImageIO

import org.lwjgl.opengl.GL11._

import scala.io.Source

object TestFonts {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test Fonts")
  
  val m1 = Matrix4().translate(x = -200.0, z = -1000.0)
  val m2 = Matrix4().translate(x = -200.0, y = -25.0, z = -1000.0)
  val fps = FPS(1.0)
  
  val font = AngelCodeFont(Source.fromURL(getClass.getClassLoader.getResource("resource/Arial.fnt")), getClass.getClassLoader.getResource("resource/Arial.png"))
  
  val a = new Array[() => Unit](5)
  a(0) = MatrixState(m1)
  a(1) = () => font.drawString("VAST Kerning Example Enabled", true)
  a(2) = MatrixState(m2)
  a(3) = () => font.drawString("VAST Kerning Example Disabled", false)
  a(4) = fps
  r.renderable := RenderList(a)
 }
}

A couple more days of effort and we can hopefully dig into some much more interesting features. :)
Scala Engine for high-performance interactive applications.