Saturday, May 22, 2010

ResolutionNode

I'm back from a week long cruise to the Caribbean without cell phones, computers, or internet access. It was a great break from technology, but now I'm back, refreshed, and ready to crank out more functionality into Sgine.

What I have today is called ResolutionNode. If you look back at all the blogged examples below you'll notice all the 'z' translation stuff on the components. The reason for that is because Sgine (and OpenGL generally) is configured to have 'z' at 0.0 have a distance of -1.0 to 1.0 both vertically and horizontally. Further, anything between 0.0 and -1.0 (negative to back further into the screen) is clipped for mathematical precision reasons. So by pushing the the components to something like -1000.0 for 'z' causes it to appear much smaller because its depth is so far back into the projection.

This is fine for games and general 3D applications, but can be a pain in the neck when you want to have a normal graphics-based UI and you're expecting a specific resolution (e.g. 1024x768) to design graphics and layout for. To this end I have created a trait called ResolutionNode. It gives the ability to specify a resolution (e.g. 1024x768) and modifies the world matrix for that Node to properly display that resolution to fit to the screen. Not only does this give you the benefit of managing the resolution for the screen, but it also keeps you from having to worry about the *actual* resolution as this will scale-to-fit so even if you are running 1920x1200 resolution if you specify 1024x768 internally you'll end up with everything being stretched to fit.

This is actually part of a larger UI design concept I've been developing that I'll go into more on as the functionality is finalized in Sgine, but for now I give you my example.

The following code takes a 1024x768 image and a 640x480 image and displays them overlayed on top of each other. The cool thing here though is that you can see that using a ResolutionNode container for each I am able to see both of them at the exact same size.

package org.sgine.ui

import org.sgine.core.Color
import org.sgine.core.Resource

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

import org.sgine.scene.GeneralNodeContainer
import org.sgine.scene.ext.ResolutionNode

object TestResolution {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test Resolution")
  r.verticalSync := false
  
  val scene = new GeneralNodeContainer()
  
  val r1024 = new GeneralNodeContainer() with ResolutionNode
  r1024.setResolution(1024.0, 768.0)
  scene += r1024
  
  val c1024 = new Image()
  c1024.source := Resource("1024.jpg")
  c1024.alpha := 0.5
  r1024 += c1024
  
  val r640 = new GeneralNodeContainer() with ResolutionNode
  r640.setResolution(640.0, 480.0)
  scene += r640
  
  val c640 = new Image()
  c640.source := Resource("640.jpg")
  c640.alpha := 0.5
  r640 += c640
  
  r.renderable := RenderableScene(scene)
 }
}

The two images I'm using are:



and:



The resulting screenshot is as follows:

Sunday, May 2, 2010

ColorNode and Render Sorting

I followed the same approach for ColorNode as I did for MatrixNode and now have the ability to define hierarchical color and alpha in Sgine. With this addition I started playing with TestCube to see how well it worked translucent. Unfortunately because I was not doing any sorting on the render list I end up with some undesirable effects:



With a simple modification to sort based on depth I now get this:



I'm doing the sort per render, which I'm sure is incredibly inefficient, but my framerate dropped from 3000fps to 2900fps in this example. I need to spend some time and make it only sort upon change, which in the test cube scenario would probably not make a lot of difference in performance since it's swapping positions every few frames, but will increase the efficiency in less frequently changing scenes.

Saturday, May 1, 2010

MatrixNode Awesomeness!

There were several things I was hoping to get accomplished today that didn't get done because I got sidetracked on another task that ended up taking significantly longer than I had anticipated. My initial implementation of Node / Component support for Matrix4 was spotty and I wasn't very happy with it, so I took the time today to refactor it and make it into a much more powerful and simplified architecture.

For those of you that have actually used the scene support in Sgine will be aware, the scenegraph architecture is extremely abstract and is designed to avoid implications towards a specific use and allow multiple uses within the same scenegraph. For example, a NodeContainer could possibly contain physics information, sound/music information, visual rendering information, etc. while each part being completely separate and independent from the other. The level at which I'm able to accomplish this is almost entirely due to the awesomeness that is Scala, but to that end abstractions often make it difficult to architect advanced uses without making system extremely complicated. For example, if I want to have my root NodeContainer define matrix information to translate 'z' to -200.0, meaning that all children will receive that it can be problematic to support if nodes between don't also provide matrix support. However, that is the difficulty I solved today. :)

I've created a simple trait "MatrixNode" that contains a "localMatrix" and a "worldMatrix". The "localMatrix" can be manipulated to represent the local translation, rotation, scale, etc. and upon change will invalidate the "worldMatrix" causing it to get updated to the parent's "worldMatrix" (walking up the scenegraph as necessary) multiplied against the "localMatrix".

See the following example:
package org.sgine.ui

import org.sgine.core.Color
import org.sgine.core.Resource

import org.sgine.easing.Linear

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

import org.sgine.scene.GeneralNodeContainer
import org.sgine.scene.MatrixNode

object TestScene {
 def main(args: Array[String]): Unit = {
  val r = Renderer.createFrame(1024, 768, "Test RenderScene")
  
  val scene = new GeneralNodeContainer()
  
  val container1 = new GeneralNodeContainer() with MatrixNode
  container1.localMatrix().translate(0.0, 0.0, -200.0)
  scene += container1
  
  val container2 = new GeneralNodeContainer() with MatrixNode
  container2.localMatrix().translate(0.0, 0.0, -500.0)
  container1 += container2
  
  val container3 = new GeneralNodeContainer()
  container2 += container3
  
  val component = new Image()
  component.source := Resource("puppies.jpg")
  component.color := Color(1.0, 1.0, 1.0, 0.5)
  component.location.z := 100.0
  container3 += component
  
  r.renderable := RenderableScene(scene)
 }
}

This is a pretty simple example. As you can see there are four containers (scene, container1, container2, and container3) and two of them mix-in "MatrixNode" in order to specify a translation (container1 and container2). Finally, there is a Image "component" that defines its own location.z. As a result "component" will render at a "z" translation of -600.0 (-200.0 + -500.0 + 100.0). Notice that although "container3" is between "container2" and "component" it is still applied. This allows extremely powerful control of the contents of the scenegraph while maintaining a very simple abstract feel.

Hopefully in the coming weeks you'll see much more functionality like this making its way into the system. In fact, I believe I'm going to have to write something along these lines for Color in order to properly mix colors hierarchically for OpenGL.

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()
 }
}
Scala Engine for high-performance interactive applications.