Thursday, December 06, 2007

3D Engine Design

During the past weeks, I haven't been able to come up with something new and/or meaningful. But for those interested in building a 3D engine, I came across the Lightfeather 3D engine. And the guys over there describe (in a high level manner - functional point of view) the design of their engine. So an experienced graphics developer might use this information to design a "copy" or get inspired by the Lightfeather engine.

Following is the "table of contents" of the Lightfeather 3D engine design:
  1. Introduction
  2. Geometry
  3. Scene
  4. Portals
  5. Rendering

Wednesday, November 28, 2007

Talk like an animal, walk like an animal ...

Well, after some time has passed, it seems that Microsoft's Terrarium is at least surfacing again. I have played around with Terrarium while doing some AI in the university. Official links are not working but googling a bit has popped a Microsoft site about some Canadian Terrarium contest. Look here http://msdn.microsoft.com/canada/terrarium/ in the bottom of the page you may even download some video made at the contest but it is not working.

The new download link on windowsclient.net is this one https://windowsclient.net/downloads/folders/starterkits/entry1269.aspx

Browsing through MSDN has surfaced that Tom Anderson did a VB.NET port of Terrarium from .NET framework version 1.x to 2.0. You will find the respective MSDN post here
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=373738&SiteID=1
Unfortunately, he has stopped working on the project and there is no visible link to me where one can grab the .NET 2.0 port of Terrarium.


Maybe there is some time left to play around a bit ...

Saturday, October 06, 2007

Newbie on the CCR Road

This post is about the CCR part of MRS, in particular I am going to do some simple examples about ports and a simple arbiter the Receiver.

CCR components use ports to communicate with each other. Just like a human being uses speech, verbal speech, or mimicry to communicate, so do CCR components use ports to communicate with each other. Ports are used to

  • Send a message to a particular receiver.
  • Receive a message from a particular sender.

In terms of CCR port, we have to define how the message, looks like that our port is going to receive. For this, we have to define the message's underlying data type or more precisely we have to create a port, which receives a message with some particular data type.

Port<string> stringPort = new Port<string>();

This creates a new port, which is able to send and receive message of type string. Now, that we have defined our port, how are do we send a message? Nothing simpler than that, the port offers an instance method Post. This method accepts a formal parameter item of type string. So the canonical "Hello World" example looks like this

stringPort.Post("Hello World");

This passes the item of type string with value "Hello World" to our stringPort, where it waits to be taken from the port for whatever one wants to do with it. To illustrate that the sent item is really "sitting" on the stringPort, we could simply do the following:

if (inputPort.ItemCount > 0)

{

string item = inputPort.Test() as string;


if (!string.IsNullOrEmpty(item))

{

Console.WriteLine("Item: " + item);

}

}

This shows two things. The first (1) being, that you may query the number of items waiting on some port by the ports field ItemCount and the second (2) being that you may test the port for the presence of items. If there is one ore more items on the port, then Test will return this item as an object and remove it from the queue of available items. Fortunately, the removal of an item is an atomic process, so that we won't going to race condition hell.

At times you may want to be able to send and receive more than just one item on some port. Therefore, the CCR offers a so called PortSet, which is just that a set of Ports. For example, to create a PortSet accepting a string and DateTime message, we would do the following:

PortSet<string, DateTime> messageDateTimePort = new
PortSet<string, DateTime>();

To avoid passing ports around in some program and taking individual items of some port, the CCR offers arbiters. Arbiters coordinate incoming messages passed between ports. In order to not manually call the Test method on the stringPort, we are going to declare a so called Receiver, which will be active whenever an item is waiting on a port matching the Receiver's signature. For example,

Receiver<string> stringReceiver = new
Receiver<string>(

true,

stringPort,

null,

new
IterativeTask<string>(MessageHandler));

this creates a Receiver, which accepts string items waiting on stringPort. The first parameter with value true sets persistence to true for this receiver. This means that the Receiver continues to receive string items even after it has received one string item. The null value is passed to the constraints formal parameter and the last parameter wires up a message handler, which actually takes care of string items incoming from stringPort. The MessageHandler function is nothing special (of course, you are free to add your own logic to it) and looks like this

private static
IEnumerator<ITask> MessageHandler(string message)

{

Console.WriteLine("Received Message: " + message);

yield break;

}

To make things complete, a small sample illustrates the previous things. The example creates a simple string port, which is used by a secondary thread to to send the current time to. This message is received by a Receiver arbiter and printed out to the console. The program terminates if the user presses any key. In this case we stop the secondary thread and simple exit the program.

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;


using Microsoft.Ccr.Core;


namespace CCRTest5

{

class Program

{

private static bool running = false;


static void Main(string[] args)

{

Console.WriteLine("CCRTest5 - send to a message port and use arbiter to receive");


Port<string> stringPort = new
Port<string>();

Console.WriteLine("Created Port<string>");


Thread messageThread = new Thread(new
ParameterizedThreadStart(AddItemFunc));


Receiver<string> stringReceiver = new
Receiver<string>(

true,

stringPort,

null,

new
IterativeTask<string>(MessageHandler));


Console.WriteLine("Created Receiver");


Console.WriteLine("Starting Arbiter");

Arbiter.Activate(new
DispatcherQueue(), stringReceiver);


Console.WriteLine("Starting AddItem Thread");

messageThread.Start(stringPort);


Console.WriteLine("Press key to exit");

Console.ReadLine();


//abort

running = false;

Thread.Sleep(500);


if (messageThread.ThreadState == ThreadState.Running)

{

try

{

messageThread.Abort();

}

catch { }

}

}


private static
IEnumerator<ITask> MessageHandler(string message)

{

Console.WriteLine("Received Message: " + message);

yield break;

}


private static void AddItemFunc(object port)

{

running = true;

Port<string> messagePort = port as
Port<string>;


while(running)

{

messagePort.Post(DateTime.Now.ToString());

Thread.Sleep(500);

}

}

}

}

More on arbiters and CCR in the next post.

Thursday, September 27, 2007

A simple GreatestCommonDivisor service in C# - Part2

What if we wanted to consume our previously built GCD Service by a web browser, such as Internet Explorer? ... Nothing easier than that. In fact, with minimal effort we can implement this functionality.
Firstly, we have to (1) declare that our service supports the HTTPQuery operation, secondly we have to (2) implement a handler for this HTTPQuery request, and thirdly we may (3) test the service with a browser.

1.
Just add the HTTPQuery port to the GCD service's port set, which is defined in GCDTypes.cs (assuming that your service is named GCD).

[ServicePort()]
public class GCDOperations : PortSet<..., HttpQuery, ...>
{}

So now that we have declared that our service is capable of understanding a HttpQuery action, we need to implement a routine, which handles such HttpQuery requests - a HTTPQueryGCDHandler.

2. Add the following lines to the GCD.cs

[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public virtual IEnumerator HTTPQueryGCDHandler(HttpQuery httpQuery) {
if (!string.IsNullOrEmpty(httpQuery.Body.Query["Action"]) &&
httpQuery.Body.Query["Action"] == "GCD")
{
string _M = httpQuery.Body.Query["M"];
string _N = httpQuery.Body.Query["N"];

int _intM = 1;
int _intN = 1;

if (!Int32.TryParse(_M, out _intM))
{
this.LogError("Failed to parse M");
httpQuery.ResponsePort.Post(

new HttpResponseType(new GcdtestState(-1)));
yield break;
}

if (!Int32.TryParse(_N, out _intN))
{
this.LogError("Failed to parse N");
httpQuery.ResponsePort.Post(

new HttpResponseType(new GcdtestState(-1)));
yield break;
}

int _GDC = this.ComputeGCD(_intM, _intN);
this.LogError("GDC = " + _GDC);

httpQuery.ResponsePort.Post(

new HttpResponseType(new GcdtestState(_GCD)));
}
else
{
this.LogError("Action is not GDC");
httpQuery.ResponsePort.Post(

new HttpResponseType(new GcdtestState(-1)));
}

yield break;
}

Besides computing the greatest common divisor and returning it as a http response, this handler does not do too much. It analyses whether the passed Action equals GCD and if so it tries to take the (hopefully) supplied parameters M and N. The service uses M and N to compute their GCD and if successful, passes the result back. If an error occurs, this service returns -1. The handler does not update the service's internal GCD state because of the following recommendation from the MRS help :

"As both GET and QUERY requests are defined by DSSP as not having any
side-effects, GET and QUERY handlers should be marked as
ServiceHandlerBehavior.Concurrent using the ServiceHandler attribute so that the
infrastructure knows that these handlers can run concurrently. "


3.With the handler in place, we may test our service. So we execute it by opening a browser and typing in the following URL

http://localhost:50000/GCD?Action=GCD&M=15&N=3

and this will return the correct result of 3. Note, that depending on you configuration the port and service name might be different. In fact, in the previous tutorial the service's name was GCDService, so you might want to take this into consideratio.

Although this was only a minor sample, I am trying to do something more meaningful with DSS and I hope to provide another small tutorial soon. So read you soon.

Christian

Saturday, September 15, 2007

A simple GreatestCommonDivisor service in C# - Part 1

I was finally able to get the GCD service up and running. Essentially it does nothing different than my previously posted GCD service, which is implemented in VPL. So to recall, this service computes the greatest common divisor of two positive integer numbers (inputx, inputy) and outputs the result as greatestcommondivisor.

Based on this description of what the service does, the remainder of this post will guide you through the process of how I built such a rather simple DSS service using the MRS object model and C# as implementation language (as mentioned you could also choose VB.NET, C++).

This kind of tutorial is divided into distinct steps beginning at creating a new service project to the final result being the service we want.


  1. Create new DSS Service
    The first thing one has to do in order to set up a basic GreatestCommonDivisor service is to create a new DSS service.We will do so by creating a new Visual Studio 2005 Project. More specifically it is a "Simple DSS Service (1.5)" project (it is assumed that you have MRS 1.5 installed). Select the mentioned project type and name it as you please (e.g., "GCDService") (see here for more detail). This will create a file for the service (e.g., GCDService.cs) a manifest file (e.g., *.manifest.xml) and a file for the types used by the service (e.g., GCDServiceTypes.cs).

  2. Define what is the objective of the service
    The service should do nothing special, just compute the greatest common divisor of two non-negative integer numbers m and n.This can be easily accomplished via euclids algorithm. In a previous post about python, I gave a recoursive implementation for this algorith, which is why I won't do it here again. So let us assume we have built a function, which implements euclids algorithm. The function's signature might look like this

    public static int ComputeGreatestCommonDivisor(int m, int n)
    {/*function's implementation not shown*/}


  3. Define the input message
    A DSS Service accepts input messages of some form coming from some input port, handles these messages and sends some output message as a response to its ouput port. For example, to get information about the state of a DSS service, this service has to handle a Get request, i.e. a "Get" message sent to the input port of this service. The DSS service receives the "Get" message does something and might respond to this message by sending "Hello World" as answer to its output port.
    Similarly, our "GreatestCommonDivisor" service might get a request to compute the greatest common divisor (GCD) of two non-negative integer numbers n and m. Therefore, such a request might incorporate the n, and m sent to our service and the expected response might be the GCD of m and n. Consequently, we have to define such a request or how such a GCD message must look like to be accepted as GCD message by our service and to be handled appropriately. The following lines of code, which we add to the types source code file (e.g., GCDServiceTypes.cs), define such a GCD request

    [DataContract(Name="ComputeGreatestCommonDivisor")]
    public class ComputeGCDRequest
    {
    private int _inputX;

    [DataMember(IsRequired = true, Name = "InputX")]
    public int InputX
    {
    get { return (this._inputX); }
    set { this._inputX = value; }
    }

    private int _inputY;

    [DataMember(IsRequired = true, Name = "InputY")]
    public int InputY
    {
    get { return (this._inputY); }
    set { this._inputY = value; }
    }

    public ComputeGCDRequest()
    {
    this.InputX = 1;
    this.InputY = 1;
    }
    public ComputeGCDRequest(int x, int y)
    {
    this._inputX = x;
    this._inputY = y;
    }
    }

    The ComputeGCDRequest defines how a request to our service has to look like. It defines m and n (InputX, InputY). Each request has to be marked with the DataContractAttribute attribute, so DSS can wire things up for us. Fields we want to incorporate in our request have to be marked with DataMemberAttribute attribute (those fields are part of the message serialization process when the ComputeGCDRequest message is serialized over some channel). These two attributes take some optional arguments such as "Name" and "IsRequired".
  4. Define a message handler for the input message
    The sheer presence of such a GCD request message type won't bring us far. We have to do define a GCD request (I), and add this GCD request to list of requests understood by our service (II). Afterwards we have to set up a mechanism that whenever a GCD request is issued to our service, takes the message, which abstracts this request and acts accordingly (III). So defining a GCD request serive, involves defining what the incoming GCD request message body looks like and defining what the result of processing a GCD request is, may it be success or failure. The following lines of source code, added to the GCDServiceTypes.cs, define a ComputeGCD request type ...

    public class ComputeGCD :
    Query<ComputeGCDRequest, PortSet<GCDState, Fault>>{

    public ComputeGCD(int m, int n) :
    base(new ComputeGCDRequest(m, n))
    {
    this.Body.InputX = m;
    this.Body.InputY = n;
    }

    public ComputeGCD() :
    base()
    { }

    }

    The ComputeGCD request type, subclasses the Query<> type, and defines the incoming message to be our previously declared ComputeGCDRequest message and the result will be a GCDState on success and the pre-defined Fault on failure. A Portset is a collection of ports (a set of ports) and a port is just an abstraction that allows services to communicate with each other. So to signal that our GCDService actually understands the ComputeGCD type, we have to add this type to the GCDService service's operations port set, which is defined in the GCDServiceTypes.cs file.

    [ServicePort()]
    public class GCDServiceOperations :
    PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, ComputeGCD>
    {}

    Now that our GCDService declares being able to understand GCD requests, we have to make him do so. More specifically we have to set up a message handler that jumps in whenever a GCDRequest is sent to our GCDService. The following lines of code, added to the GCDService.cs will be in charge of this.

    [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
    public virtual IEnumerator ComputeGCDHandler(ComputeGCD computeGCD)
    {
    int M = computeGCD.Body.InputX;
    int N = computeGCD.Body.InputY;

    int gcd = ComputeGreatestCommonDivisor(M, N);

    this._state.GCD = gcd;

    LogInfo("=========\r\nGcdTest\r\n=========");
    LogInfo(string.Format("M = {0}\r\nN = {1}\r\nGCD= {2}",
    computeGCD.Body.InputX,
    computeGCD.Body.InputY,
    this._state.GCD));

    computeGCD.ResponsePort.Post(new GCDState(gcd));

    yield break;
    }

    The ComputeGCDHandler handler takes the ComputeGCD request as input and unwraps the necessary InputX and InputY fields from message's body. It then uses these two numbers as input to our ComputeGreatestCommonDivisor method, which computes the greatest common divisor and stores it in the gcd parameter. This gcd parameter is stored into the state of the service, which is of type GCDState. This GCDState encapsulates the state of the service, which is the greatest common divisor computed by the service. Actually the state is not really necessary, because we don't do much with it. At last, the message handler outputs the result of the operation to the output port for success, which was of type GCDState. The LogInfo calls are merely for debugging. Since this service alters its internal state, we have to make sure that this happens in an exclusive fashion, i.e. while updating the state no access to the state is granted. This is what the

    [ServiceHandler(ServiceHandlerBehavior.Exclusive)]

    Attribute declaration is for. It ensures that no race condition can occur by exclusively granting access to the internals our service.
  5. Define the GCDState type
    What is left, is to define what the state of our GCDService looks like. As mentioned, our service doesn't need to have a state but I have to decided to give it one (mainly for learning purposes). The GCDState type is define in the GCDServiceTypes.cd and can be modified like this

    [DataContract()]
    public class GCDState
    {
    private int _GCD;

    [DataMember(IsRequired=true, Name = "GreatestCommonDivisor")]
    public int GCD
    {
    get { return (this._GCD); }
    set { this._GCD = value; }
    }

    public GCDState()
    {
    this._GCD = 1;
    }

    public GCDState(int gcd)
    {
    this._GCD = gcd;
    }

    }

  6. Let's test
    Now that we have set things up. We are ready to build the service and test it. Therefore compile everything. Open the MRS Visual Programming Language to build a new test service using our GCDService. I have done that and the test service can be seen here (my GCDService is named "GreatestCommonDivisor").


I hope you enjoyed building this service as much as I did and I hope to have something ready for you asap.

Christian

Thursday, September 13, 2007

Online Lectures

Hi folks,

I found this link about online learning in video form on another blog and thought I could repost it here

http://videolectures.net/

so thanks goes out to

http://smart-machines.blogspot.com/

Both links are available in the sidebar.

Other online lectures


  1. http://www.researchchannel.org/prog/displayinst.aspx?fID=880

Christian

Saturday, September 08, 2007

Microsoft Robotics Studio - First Sight

Microsoft Robotics Studio (MRS)

I have recently come accross this great piece of new technology, offered for free by big MS [1]. MRS allows to program robots and other external devices via a visual programming language (VPL) or your prefered .NET programming language, such as VB.NET or C#. MRS main abstraction is a service.
Assuming you use VPL, a service is graphically represented as a box. Services exist in their own space and can be combined via message passing. In fact, services communicate via a SOAP-like protocol. This communication act is graphically represented by wires between services. Where such a wire connects the output of one service to the input of another. It is up to the developer of a service to decide what a service does, i.e. what kind kind of messages a service understands and what output a service produces. Therefore one can develop rather simple services such as a NoOP service, which does nothing, to a more complex service that for example takes a picture from a webcam. The great news is that MS ships MRS with a bunch of pre-built services, such as a Text-to-Speech service. This service takes some input message and renders it as its audio representive.
Since I have been able to play around with MRS, I wanted to share my first project, which calculates the greatest common divisor of two positive integer number m and n. Currently it is assumed that m is greater than n. The main routine that computes the gcd of m and n can be seen below here. As in yesterdays post, we make use of the fact that gcd (m, n) = gcd (n, r) where n = m % n and % is defined as the modulus of m and n. At the moment there is no swapping of m and n, in case n is greater than m (gcd (m,n) = gcd(n,m)). A possible example setting can be seen here. This example simply takes 10 and 5 as input and produces the expected output 5. I hope that this simple service will be one of many to come on my road to do cool stuff with MRS.

[1] Microsoft Robotics Studio: http://msdn2.microsoft.com/de-de/robotics/bb625969.aspx

Friday, September 07, 2007

Working the Snake - Python

I finally found my way to (Iron) Python[1][2] - well I did some Python in the beginning of my studies but lost focus after some time. The experience was quite impressive, since development time is greatly reduced by such a dynamic language. I managed to get some small programs running in less then 1 hour and of course I want to share them with you - precious audience. Since these are my first scripts, I assume that they are not quite correct/stable/... - they are simple and not very error-safe (no checking of arguments ) ...

The first is some simple factorial program, which tries to find the greatest common divisor (gcd)of two numbers by employing the fact that gcd (x, y) = gcd (y, r) where r = x mod y. Since I do not know how a scope for loops can be created (scoping a while/for loop) I did the routine recoursively. The second function builds on the first and is allows to ask whether some number x is a prime number by using the simple strategy of finding a gcd within the range of 1..root(x)


import System
def gcd(x,y):
  if y == 1 : return 1
  elif y == 0: return y
  elif x == y : return y
  elif x return gdc(y,x)
  n = x % y
  if n == 0: return y
  return gcd(y, n)

def IsPrime(x):
  if x == 1 : return 1
  elif x == 2 : return 1
  elif x == 3 : return 1
  tempsqrt = System.Math.Sqrt(x)
  tempsqrt = System.Convert.ToInt32(tempsqrt)
  for i in range(tempsqrt) :
  j = i + 1
  divisor = gcd(x,j)
  if divisor != 1 : return 0
  return 1


The second program is about the all time recursion favorite - the fibonaci numbers ...

the Fibonaci (Fib ) numbers are defined as follows

Fib(0) = 0
Fib(1) = 1
Fib(n) = Fib(n-2) + Fib(n-1)

example

n 0 1 2 3 4 5 6
Fib (n) 0 1 1 2 3 5 8
and here is the program - putting python suggar just around the exemplified formulae

import System
def Fibonaci(n):
  if n <= 0: return 0
  if n == 1: return 1
  return Fibonaci(n-2) + Fibonaci(n-1)

References:

[1] IronPython - www.codeplex.com/IronPython
[2] Python - www.python.org

Thursday, March 22, 2007

Well not much happened today. I was able to implement a triangle filling routine and read a bit on .NET remoting. I thought about how to implement a Z-Buffer for my small system and thought about homogenous clipping as well.

The problems I have right now are:
  1. I want to implement basic triangle 2D screen-space clipping. Which itself is not a big deal but I do not want to implement a polygon filling scan-line algorithm. A polygon may be the outcome of a triangle clipped against a rectangular clip window. So what is there to do ? Either clip a triangle which produces a polygon and implement a "simple" scanline algorithm to fill that polygon. The other option is of course to split the triangle into a series of triangles. Either way has its intricacies.
  2. As mentioned in yesterday's I want to implement homogenous clipping for triangles. Which makes things even more complicated then a simple 2D clipper. The reason is that not only a triangle can be split into a series of triangles during the clipping process but also extra state is necessary. Remember, we do not only clip the triangle and generate new triangles that form the clipped triangle but we have to generate appropriate values for associated color, texture and normal data.
  3. I want the filling of the z-buffer and the filling of the framebuffer to be separate. Although expensive, I want to maintain this separation for flexibility.
With these "basic" things I should be busy for some days :D ...

For all the people who want to do some graphics stuff I recommend the following books. At least they served my understanding quite well.
  1. Practical Linear Algebra: A Geometry Toolbox
  2. Computer Graphics: Principles and Practise
  3. Real-time Rendering
  4. Math for 3D Game Programming & Computer Graphics
a web reference on the topic
  1. 3D Graphic Engine Essentials
  2. Software Rendering School
Happy reading

Wednesday, March 21, 2007

In reference to yesterday's post I continued a bit and implemented the following
  1. Line drawing via DDA and Bresenham
  2. Basic Cohen-Southerland Clipping in 2D
These routines may not be the fastet but this actually not the point since the code is written in managed C# and makes no use of any optimizations. It's sole purpose is learning. The next things that need to be done are
  • drawing of triangles (including basic fill)
  • a Z-Buffer (at least I have already set up the basics)
  • clipping in homogenous clip space
  • simple scene-graph
If anyone is interested in some sources - I can either post them here or put them online on my website. I also thought about making the whole engine a bit more flexible. So I thought I could
divide all the pipeline elements into pipeline stages. Each stage has access to some renderstate and does only execute on some defined input. As a consequence one would get something similar to vertex and fragment programs and a non-fixed function pipeline. But of course this is only some thinking and I should better get things set up before reasoning about making it most flexible.

Tuesday, March 20, 2007

I have been quite busy these days writing my own small 3d engine. I did some small stuff with OpenGL in the past and to understand at least a bit better what is going on behind the scenes, I decided to do my own small renderer. I know, why write another software renderer if there is so much available. But at least for me I can say, that what I implement I do understand a little better.

Of course, it takes some time to get the basics running. Until know it does only little (only perspective projection is covered), such as the transformation from an object's local space to actual screen space. But many things are left to do such as homogenous clipping, 2D clipping, painting ...

The things I want to implement as a next step are
  • rasterization (lines via bresenham, basic filling - flat shading)
  • homogenous clipping
  • 2d clipping
  • a primitive scene graph
Since basic rasterization often produces flicker, I looked for a basic method to do double-buffering with windows forms and .NET and these are some of my findings:

  1. Enable double buffering for a control (e.g., Form) via setting a control's property DoubleBuffered = true
  2. Set a control's style via calling Control.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
This gives you basic double-buffering in an instant. But sometimes one wants to have a bit more control over the actual double-buffering process and therefore, the .NET framework provides several new classes in the System.Drawing namespace, such as:
  • BufferedGraphics
  • BufferedGraphicsContext
  • BufferedGraphicsManager
You can obtain an AppDomain's main BufferedGraphicsContext by using the Current property of the BufferedGraphicsManager or you may create your own BufferedGraphicsContext. A BufferedGraphicsContext can be used to create a BufferedGraphics object (the Off-screen buffer you want to use for drawing) and associate this object to an output device (e.g. display).
Use a BufferedGraphicsContext.Allocate method to create a BufferedGraphics object and to render the contents of such a BufferedGraphics object to an output device use the BufferedGraphics.Render method, which allows you to set the desired output device or use the one supplied to the BufferedGraphics.Allocate method. Drawing to such an off-screen buffer is easily done via a BufferedGraphics.Graphics property.

Of course, if this is not what you want you may still use a Bitmap object to do all off-screen drawing and draw that bitmap to the display.

Sunday, March 11, 2007

Back again :D

A long time ago ... in this galaxy :D ...

Well, it has been a while since I wrote the last post. But since I was finally able to conclude my studies this thursday, the 8th of march, I hope to be around more often. I will probably start my first work on april the 2nd. My new employer will be Avanade Deutschland GmbH (german office of Avanade Inc.). And I am quite happy about that since I expect a great learning experience - well but let's see ...

Since I am not too busy these days I will try to have a look onto several things such as SQL Server 2005 and maybe I get my hands dirty with Phoenix again.

Best regards

Christian