Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (487)
Games in Android Showcase (112)
games submitted by our members
Games in WIP (553)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Is JOODE mature enough for a simple torque demo?  (Read 3801 times)
0 Members and 1 Guest are viewing this topic.
Offline lsvan

Senior Newbie





« Posted 2006-05-02 12:34:24 »

Hi,

I'm writing a set of physics applets for a Finnish highschool physics schoolbook. I was planning to use JOODE, but after checking out the latest version in JOODE CVS repository (the one at SourceForge) I run into several problems. I wonder if the current revision is broken, as the most tests didn't show anything moving (even though the stepper was operative).

So anyway, my torque applet would be rather simple: A seesaw with two movable mass elements on top of that, supported by a cylinder of which place can be moved as well.  Actually, the support can be anything as long as it can be moved. So, the system would be following:

Four mass bodies:
- red box
- blue box
- seesaw
- support
- dumb object (for the joint system between seesaw and support)

Connected by
- hinge (seesaw, dumb object)
- slider (dumb object, support)
- slider (support, null) (for fixing the support to some axis)
- slider (red box, seesaw)
- slider (blue box, seesaw)

I think I was able to construct such system with JOODE, but I didn't get the hinge rotate at all. After writing some bits of test code I noticed a few other problems, e.g. setting facc (or any property related to forces, acceleration or velocity) didn't do anything to a body. So I think there's probably something wrong with the current build available in CVS.

If the problems are minor, I'm willing to contribute to bug fixing, testing or whatsoever. I think this project is cool and deserves support.  Wink

Best regards,
Lauri Svan
Offline hdietrich

Junior Member




Harald Dietrich


« Reply #1 - Posted 2006-05-03 10:23:28 »

Hi Isvan,

I guess we should get your sample running, even if the JOODE is still under development  Wink

I was planning to use JOODE, but after checking out the latest version in JOODE CVS repository (the one at SourceForge) I run into several problems. I wonder if the current revision is broken, as the most tests didn't show anything moving (even though the stepper was operative).

First you should get the tests running, since they are known to be working. Try editing the class TestingWorld in the package net.java.dev.joode.test. There is a method step(), which gets called for every iteration. In the current version in CVS this methods contains a line world.step(0.01f). This tells the engine to do a fixed stepping of 0.01seconds per step. On slow machines this value could be too low and result in very slow movement. Try changing this to world.step(1/fps.getFPS()), which should result in real time behaviour. Now the tests should show at least some movement.

Next is the world.step() method itself. It is known to have some bugs, so some of the test will result in strange behaviour. There is another function world.quickStep(), which shows an appropriate behaviour for all tests. This function is less accurate for small scale systems than world.step(), but works fine for most applications.

Let me know, if you get appropriate results from the tests now.
Offline lsvan

Senior Newbie





« Reply #2 - Posted 2006-05-03 14:25:38 »

Hi, hdietrich!

Thanks for such a quick reply. I modified the test code as you proposed and it seems to work fine. Threfore, I suggest there's something wrong with my code. I think it's better to paste some of my code, even though it doesn't run as such. You'll probably get a grasp of what I'm trying to to. Actually, If you ever need pure java test classes (without any bindings), I'd be happy to contribute my code bits (2d scenegraph with picking, dragging etc.)

So a few explanations of the code below:
- I'm binding JOODE to my 2d scenegraph, where "Model" actually refers to geometry of an object
- JOODEPhysicsAnimator is run in the normal "update world, handle paint" simulation loop (yes, it's not yet thread-safe, and this probably will cause some problems as UI events are handled in a separate thread)
- I'm trying to make red box, black box movable in the x axis direction of seesaw, support movable in the x axis of world (fixed in y axis of the world)
- I haven't implemented any collision callbacks, I suppose I don't even need them if all elements are connected by some joints
- When I get this working, I probably need to add some geometry for ground to limit the rotation of seesaw
- Later on, I probably isolate all bits of JOODE to my JOODEPhysicsAnimator class

Here are some open questions:
- When attaching a geometry to body, is the mass, by default equally divided throughout the body (constant density)?
- ... Um... there's got to be more open thingies, I'll return them to later on after writing some new bits of test code...

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
      // Setup physics
     JOODEPhysicsAnimator physics = new JOODEPhysicsAnimator();
      physics.setController(controller);
      bluePhysics = physics.createJOODEPhysics(blue, 1);
      redPhysics = physics.createJOODEPhysics(red, 1);
      seesawPhysics = physics.createJOODEPhysics(seesaw, 1);
      supportPhysics = physics.createJOODEPhysics(support, 1);
      physics.addJOODEPhysics(redPhysics);
      physics.addJOODEPhysics(bluePhysics);
      physics.addJOODEPhysics(seesawPhysics);
      physics.addJOODEPhysics(supportPhysics);
     
      // Attach support to the floor, make it slideable  
     World world = physics.getWorld();
      JointSlider js = world.createSlider(null);
      js.attach(supportPhysics.getBody(), null);
      js.dJointSetSliderAxis(1, 0, 0);
     
      // Attach seesaw to support, make it a hinge to allow rotation
     // Make hinge slideable by attaching a slider on top of that
     JointHinge jh = world.createHinge(null);
      Body tempBody = world.createBody();
      Vector2D v = seesaw.getTranslation();
      tempBody.pos.set((float) v.getX(), (float) v.getY(), 0f);
     
      jh.attach(supportPhysics.getBody(), tempBody);
      jh.dJointSetHingeAnchor((float) v.getX(), (float) v.getY(), 0f);
      jh.dJointSetHingeAxis(0, 0, 1);
     
      JointSlider jhs = world.createSlider(null);
      jhs.attach(tempBody, seesawPhysics.getBody());
      jhs.dJointSetSliderAxis(1, 0, 0);


And for reference, here's the functions of my physics class that get called

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
207  
208  
209  
210  
211  
212  
213  
214  
215  
216  
217  
218  
219  
220  
221  
222  
223  
224  
225  
226  
227  
228  
229  
230  
231  
232  
233  
234  
235  
236  
237  
238  
239  
240  
241  
242  
243  
244  
245  
246  
247  
248  
249  
250  
251  
252  
253  
254  
255  
256  
import java.util.ArrayList;
import fi.tammi.math.Physics;
import fi.tammi.math.Vector2D;
import fi.tammi.util.Model;
import java.awt.geom.Rectangle2D;
import net.java.dev.joode.world.*;
import net.java.dev.joode.body.*;
import net.java.dev.joode.util.Vector3;
import net.java.dev.joode.geom.Box;
import net.java.dev.joode.geom.Geom;
import net.java.dev.joode.space.NearCallback;
import net.java.dev.joode.space.SimpleSpace;
import net.java.dev.joode.space.Space;
import net.java.dev.joode.util.Quaternion;


public class JOODEPhysicsAnimator implements AnimationListener {

   private ArrayList joodeObjects;
   private AbstractAnimationController controller;

   private double updated;  
   private Vector2D temp;
   
   // Joode objects
  private World world;
   private Space space;
   
   private Vector3 tempVect;

   public JOODEPhysicsAnimator() {
      joodeObjects = new ArrayList();
      updated = 0;
      temp = new Vector2D();
      tempVect = new Vector3();
     
      world = new World();
      world.gravity.set(0, 9.81f, 0);
      space = new SimpleSpace(null);
   }
   
   public JOODEPhysics createJOODEPhysics(Model m, double mass) {
      JOODEPhysics p = new JOODEPhysics();
      p.setModel(m);
      p.setMass(mass);
     
      return p;
   }

   public void addJOODEPhysics(JOODEPhysics p) {
      joodeObjects.add(p);
   }

   public void removeJOODEPhysics(JOODEPhysics p) {
      joodeObjects.remove(p);
   }

   public void animationFinished() {
   }

   public void animationStarted() {
      updated = 0;
   }

   /**
    * Update physics every frame
    */

   public void frameChanged(double now) {

      double timeDiff = now - updated;
      updated = now;
     
      // Update geometry  positions & translations
     for (int i = 0; i < joodeObjects.size(); i++) {        
         // Update geometry positions based on physics
        updatePhysics((JOODEPhysics) joodeObjects.get(i));
      }
     
      // Update physics
     //space.collide(null, null);
     // TODO Get rid of this kludge - instead,
     // put JOODE to handle zero difference
     world.step((float) timeDiff + (0.0001f));
     
      // Update positions for all the models
     for (int i = 0; i < joodeObjects.size(); i++) {        
         // Update geometry positions based on physics
        updateGeometryPosition((JOODEPhysics) joodeObjects.get(i));
      }
   }

   private void updateGeometryPosition(JOODEPhysics p) {
      Model m = p.getModel();
      Body b = p.getBody();
     
      // Fetch the translation & rotation from geometry
     temp.set((double) b.pos.getX(),(double) b.pos.getY());
      m.setTranslation(temp);      
      double rot = toAxisAngle(b.q, tempVect);
      m.setRotation(rot);
   }
   
   private void updatePhysics(JOODEPhysics p) {
      Model m = p.getModel();
      Body b = p.getBody();
     
      Vector2D xlat = m.getTranslation();
      double angle = m.getRotation();
     
      // Fetch the translation & rotation from geometry
     b.pos.set((float) xlat.getX(),(float) xlat.getY(), 0f);
      tempVect.set(0f, 0f, 1f);
      toQuaternion(tempVect, angle, b.q);  
   }

   public AbstractAnimationController getController() {
      return controller;
   }

   public void setController(AbstractAnimationController controller) {
      if (controller == this.controller)
         return;

      if (this.controller != null) {
         this.controller.removeAnimationListener(this);
      }
      if (controller != null) {
         controller.addAnimationListener(this);
      }

      this.controller = controller;
   }
   
   /**
    * Conversion from quaternion to axis angle, see the link below for formulas.
    *
    *  http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
    *
    * @param q Source quaternion
    * @param dst Destination axis
    * @return Destination angle
    */

   public double toAxisAngle(Quaternion q, Vector3 dst) {        
      double angle = 2 * Math.acos(q.m[0]);
      dst.setX((float) (q.m[1] / Math.sqrt(1-q.m[3]*q.m[0])));
      dst.setY((float) (q.m[2] / Math.sqrt(1-q.m[3]*q.m[0])));
      dst.setZ((float) (q.m[3] / Math.sqrt(1-q.m[3]*q.m[0])));
     
      return angle;
   }
   
   /**
    * Conversion from axis angle to quaternion, see the link below for formulas.
    *
    *  http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
    *
    * @param v Source vector axis
    * @param angle Source angle
    * @param q Destination quaternion
    * @return The quaternion transformed (q)
    */
     
   public Quaternion toQuaternion(Vector3 v, double angle, Quaternion q) {
      q.m[1] = v.getX() * (float) Math.sin(angle/2);
      q.m[2] = v.getY() * (float) Math.sin(angle/2);
      q.m[3] = v.getZ() * (float) Math.sin(angle/2);
      q.m[0] = (float) Math.cos(angle/2);
     
      return q;
   }  
   
   public class JOODEPhysics implements Physics {
     
      public static final double CONSTANT_DEPTH = 1.0;
     
      private Body body;
      private Vector2D velocityCache;
      private Vector2D accelerationCache;
      private double massCache;
      private Model model;
      private Geom geometry;      
     
      public JOODEPhysics() {
         body = world.createBody();
         velocityCache = new Vector2D();
         accelerationCache = new Vector2D();
         massCache = 0;
      }

      public Vector2D getVelocity() {
         velocityCache.set(body.avel.getX(), body.avel.getY());
         return velocityCache;
      }
     
      public void setMass(double mass) {
         massCache = mass;
         body.mass.adjust((float) mass);
      }

      public double getMass() {
         return massCache;
      }

      public Vector2D getAcceleration() {
         accelerationCache.set(body.facc.getX(), body.facc.getY());
         return accelerationCache;
      }
     
      public void setAcceleration() {
     
      }

      public Model getModel() {
         return model;
      }
     
      public void setModel(Model model) {
         this.model = model;
         
         // TODO Handle setting this to null
       
         // Update physics parameters
        // For now, update geometry as with bounding box
        Rectangle2D rect = model.getBoundingBox();
         float width = (float) rect.getWidth();
         float height = (float) rect.getHeight();
         float depth = (float) CONSTANT_DEPTH;
         geometry = new Box(space, width, height, depth);
         geometry.setBody(body);
         
         // Fetch the translation & rotation from model
        Vector2D xlat = model.getTranslation();
         double rot = model.getRotation();
         geometry.pos.set((float) xlat.getX(), (float) xlat.getY(), 0);        
         body.pos.set((float) xlat.getX(), (float) xlat.getY(), 0);
         // Set to pointing upwards from screen (z = 1, others 0)
        tempVect.set(0,0,1);
         toQuaternion(tempVect, rot, body.q);
      }      

      public Body getBody() {
         return body;
      }

      public Geom getGeometry() {
         return geometry;
      }
   }

   public Space getSpace() {
      return space;
   }

   public World getWorld() {
      return world;
   }  
}
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline hdietrich

Junior Member




Harald Dietrich


« Reply #3 - Posted 2006-05-03 15:44:57 »

I just had a short look at your code and detected a bug. Your method toAxisAngle is wrong. Correct code would be:

1  
2  
3  
4  
5  
6  
7  
8  
public double toAxisAngle(Quaternion q, Vector3 dst) {
 double angle = 2 * Math.acos(q.m[0]);
 dst.setX((float) (q.m[1] / Math.sqrt(1-q.m[0]*q.m[0])));
 dst.setY((float) (q.m[2] / Math.sqrt(1-q.m[0]*q.m[0])));
 dst.setZ((float) (q.m[3] / Math.sqrt(1-q.m[0]*q.m[0])));

 return angle;
}


Next thing I realized is that you do not supply a correct mass distribution momentOfIneratia (in accordance to your question). You have to add something like:

1  
body.mass.createBox(density, lengthX, lengthY, lengthZ)


This will set create correct momentOfInertia value. You could add this in your setModel() method.

Could you explain to me whats the current behaviour of your code. Maybe I can see where your problems are if I understand what's wrong with the behaviour.
Offline lsvan

Senior Newbie





« Reply #4 - Posted 2006-05-03 16:50:33 »

Hi,

Thanks for the bugfixes. Even though they didn't help me yet, they'll sure be handy later on.

Currently I'm trying a very simple thing - to get the seesaw rotate as a result of moving the support under it. (e.g. if the support is placed to the other end of seesaw, the seesaw should start rotating around the support. Later on, I should make the support movable and place two movable mass cubes over the seesaw.

I just got the problem solved - it was due to setting hinge anchor improperly. I'll keep you updated if I run into any problems.

Thanks!
Offline hdietrich

Junior Member




Harald Dietrich


« Reply #5 - Posted 2006-05-04 07:21:05 »

Isvan, one more thing you should be aware of is that if you are changing the position or rotation of a body or geometry the whole physics might result in a wrong behaviour. This is because joints might get in a invalid state because of the changes position. It would be better to apply forces and a torque to the body, which makes the body move in the desired direction.

Btw. if you apply forces or torque to a body the forces will be only used for the next step of the iteration. After each step the external forces get reseted. If you want to apply a permanent force you have to add the forces before every update of add a motor to a joint.
Offline darkprophet

Senior Member




Go Go Gadget Arms


« Reply #6 - Posted 2006-05-04 11:20:23 »

You could reverse integrate (i.e. differentiate) the acceleration, velocity and positional formulae to get the force required for a deltaP for that amount of time. But use that with caution as the force might be HUGE resulting in undesired effects.

Also, that force, although its been reset, will cause the next frame and so on to propegate further. So unless your not worried about that, then differentiating the formulae will do good.

DP

Friends don't let friends make MMORPGs.

Blog | Volatile-Engine
Offline lsvan

Senior Newbie





« Reply #7 - Posted 2006-05-05 11:10:44 »

Hi,

If I understood correctly, translating bodies may break the system, so I should apply forces to move the objects. Is it safe to reset velocities in each step? I'd like to maintain a drag'n'drop type feeling where the user could '"freeze the simulation", pick and move the object, and restart the animation with changes applied.

Currently I'm having the following problem in setting proper forces: My drag'n'drop events are executed in different thread than the physics stepper, so the object movement (and therefore velocity and acceleration) cannot be directly calculated from the time between mouse events. I guess that could be overcome by just maintaining information on target position (for each moved object) and calculating the required force on each step until the end position is reached. Right?
Offline arne

Senior Member




money is the worst drug- we should not let it rule


« Reply #8 - Posted 2006-05-05 14:17:57 »

setting the velocity is better than changing the position directly, but think, what'll happen in case of a collision? JOODE changes in such case simply the acceleration to get the required effect. So setting the velocity directly might result in strange effects.

:: JOODE :: Xith3d :: OdeJava ::
Offline lsvan

Senior Newbie





« Reply #9 - Posted 2006-05-10 06:50:07 »

I think my reverse integration code works fine now, and I'm able to move the objects. My problem currently is that I'm able to constraint object movement with different joints (e.g. a hinge for rotation only, slider for movement in one axis etc.), but I haven't found a way to turn them on/off when needed. I'm aiming to be able to drag objects at any time, and when dragging is not active, attach the objects to each other when drag is not active.

What is your proposal, should I just dynamically create two different joint groups and switch between them? I guess this means dynamically creating and destroying the joint groups, or is there a possibility of disabling them?

- Lauri
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline hdietrich

Junior Member




Harald Dietrich


« Reply #10 - Posted 2006-05-10 09:49:05 »

The hinge and the slider joints have a construct JointLimitMotor. This can be used for constraining the joint (limit) and apply a momentum to the joint (motor). Maybe this will be the right place to look for.
Offline arne

Senior Member




money is the worst drug- we should not let it rule


« Reply #11 - Posted 2006-05-10 15:26:27 »

BTW when your testcase works fine, I think it would be a great thing to add to the samples! Smiley

:: JOODE :: Xith3d :: OdeJava ::
Offline lsvan

Senior Newbie





« Reply #12 - Posted 2006-05-11 05:01:12 »

Umm... Am Iooking at wrong place, if don't find any JointLimitMotors in CVS? Looking the source code of Hinge and Slider, the both are currently unpowered (the motors & limits related code is commented out). Do you know if they can be safely uncommented, or should I actually rewrite the missing bits?
Offline hdietrich

Junior Member




Harald Dietrich


« Reply #13 - Posted 2006-05-11 07:46:49 »

Are your sure you have the latest code? The current JointHinge and JointSlider code contains a line

1  
public JointLimitMotor limot = new JointLimitMotor();


and the class JointLimitMotor must be present in the joint package as well.
Offline lsvan

Senior Newbie





« Reply #14 - Posted 2006-05-11 10:56:03 »

No, I don't and I assume neither does JOODE CVS Repository: http://cvs.sourceforge.net/viewcvs.py/joode/joode/src/net/java/dev/joode/joint/JointHinge.java?only_with_tag=HEAD&view=markup looks pretty old to me. Which repository are you using, and have you checked in the latest changes?  Huh

If not, I would really appreciate checking them in, as they'd be super useful for me. Smiley

And for Arne, I'm glad to add my stuff to test cases when they're working. As there's plenty of stuff, I could probably add them as a separate packages to contrib direction (when it  becomes available).
Offline hdietrich

Junior Member




Harald Dietrich


« Reply #15 - Posted 2006-05-11 13:33:37 »

Oh, pew, looks like the anonymous cvs for the project is out of sync with the developer cvs. Is there a difference? Anybody an idea why it is ike this?

BTW: I cannot access cvs with my username and password at the moment. Sourceforge seems to have some problems.

Isvan: Are you really working with code almost 3 month old? I just wonder if there is anything working.
Offline arne

Senior Member




money is the worst drug- we should not let it rule


« Reply #16 - Posted 2006-05-11 14:45:30 »

mmh the webpage cvs also shows an old version Sad

maybe we should send a bug-report to sourceforge?

:: JOODE :: Xith3d :: OdeJava ::
Offline hdietrich

Junior Member




Harald Dietrich


« Reply #17 - Posted 2006-05-11 15:50:20 »

Isvan, maybe you will be positively surprised by what happend. I can confirm that this code has been check in already some time ago.

Currently Sourceforge has some problems with the CVS repository anyway.

From http://sourceforge.net/docs/A04/:
(  2006-05-10 04:43:14 - Project CVS Service )   As of 2006-05-09 the developer CVS server had a disk-failure. As the new CVS infrastructure is in its final phases of rollout, we'll be deploying it, in place of the current infrastructure, by end of week. We'll be sending out an email to project administrators with further details later in the day, regarding how to access the new CVS servers and the changes that occurred with the new infrastructure.
Offline lsvan

Senior Newbie





« Reply #18 - Posted 2006-05-13 10:01:00 »

Hmm SourceForge is really odd in its way of updating stuff from developer cvs to the public one. Right now the web cvs browser seems to be operative, but a normal cvs access giver the old files...
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

TehJavaDev (15 views)
2014-08-29 01:26:30

CopyableCougar4 (25 views)
2014-08-23 02:31:30

atombrot (38 views)
2014-08-19 16:29:53

Tekkerue (33 views)
2014-08-16 13:45:27

Tekkerue (32 views)
2014-08-16 13:22:17

Tekkerue (20 views)
2014-08-16 13:20:21

Tekkerue (29 views)
2014-08-16 13:12:11

Rayexar (66 views)
2014-08-11 09:49:23

BurntPizza (42 views)
2014-08-10 04:09:32

BurntPizza (34 views)
2014-08-08 09:01:56
List of Learning Resources
by Longor1996
2014-08-16 17:40:00

List of Learning Resources
by SilverTiger
2014-08-06 02:33:27

Resources for WIP games
by CogWheelz
2014-08-01 23:20:17

Resources for WIP games
by CogWheelz
2014-08-01 23:19:50

List of Learning Resources
by SilverTiger
2014-07-31 23:29:50

List of Learning Resources
by SilverTiger
2014-07-31 23:26:06

List of Learning Resources
by SilverTiger
2014-07-31 18:54:12

HotSpot Options
by dleskov
2014-07-08 08:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!