Hier eine kleine Erläuterung zur Verarbeitung von VRML für die gängigen Open-Source 3D-Engines wie Away3D, Papervision oder Sandy3D. Um genauer zu sein, der Verarbeitung von VRML2 (.WRL), welches von jeder 3D-Software exportiert werden kann. Leider exportieren einige 3D-Programme VRML Geometry-Nodes lediglich als IndexedFaceSets, einer langen Liste von Knotenpunkten, die für die hier vorgestellte Methode der Verarbeitung ungeeignet ist. Cinema4D erzeugt z.B diese Art von VRML-Code, es sit daher empfohlen z.B. auf 3DMax zurückzugreifen, wenn möglich.
Der Ablauf des Parsings ist keine Raketentechnik; da VRML ein Textformat ist, geht es eigentlich nur um den Einsatz von String-Operationen innerhalb einer Array-Filter Schleife. Hier ein kurzer Blick auf typische VRML-Syntax:
#VRML V2.0 utf8
# Produced by 3D Studio MAX VRML97 exporter, Version 9, Revision 1
# MAX File: level.max, Date: Sat Sep 6 8:00:00 2008
DEF myTestBox Transform {
translation 317 100 -57
children [
Transform {
translation 0 11.5 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.03137 0.2392 0.5412
}
}
geometry Box { size 20 23 20 }
}
] }
]
}
Im obigen Beispiel kann man leicht die nötigen Eckdaten ersehen, die man herausfischen muß, um sie in Actionscript3 wiederum in eine 3D-Szene zu übersetzen. Hier der nötige Ansatz dazu:
private var container:TransformGroup;
/*for Away3D change TransformGroup to ObjectContainer3D*/
private var vrmlString:String;
/*return-type TransformGroup is for Sandy3D,
for Away3D choose ObjectContainer3D instead*/
private function parseVRML( $vrmlString:String ) : TransformGroup
{
this.vrmlString = $vrmlString;
var nodesInclCamera:Array = vrmlString.split( ‘DEF ‘ );
/* remove VRML-header */
nodesInclCamera.shift();
/*exclude camera-node*/
var nodesExclCamera:Array = nodesInclCamera.filter( filterCamera );
container = new TransformGroup( ‘container’ );
/*Away3D: container = new ObjectContainer3D();*/
/*iterate over all geometry-nodes*/
nodesExclCamera.forEach( processNode );
return container;
};
private function filterCamera( element:*, index:int, arr:Array ):Boolean
{
return ( element.match( ‘Camera’ ) == null );
};
private function processNode( element:*, index:int, arr:Array ) : void
{
var helperArray:Array = element.split( ‘geometry ‘ );
var primitive:String = helperArray[ 1 ].substr( 0, helperArray[ 1 ].indexOf( ‘ ‘ ) );
var properties:Object = new Object();
properties.name = element.substr(0, element.indexOf( ‘ ‘ );
/*parse size*/
var sizeSub:String = element.substr( element.indexOf( ‘size’ )+5, element.length);
var sizeSplit:Array = sizeSub.split( ‘ ‘ );
properties.width = sizeSplit[ 0 ];
properties.height = sizeSplit[ 1 ];
properties.depth = sizeSplit[ 2 ];
/*parse position*/
var transSub:String = element.substr( element.indexOf( ‘translation’ )+12,
element.indexOf( ‘ children’ ) );
var transSplit:Array = transSub.split( ‘ ‘ );
properties.x = transSplit[ 0 ];
properties.y = transSplit[ 1 ];
properties.z = transSplit[ 2 ];
/*parse rotation*/
/*properties are called rotateX, rotateY and rotateZ in Sandy
Away3d: change to rotationX, rotationY and rotationZ*/
if ( element.match( ‘rotation’ ) != null)
{
var rotSub:String = element.substr( element.indexOf( ‘rotation’ )+9,
element.indexOf( ‘ children’ ) );
var rotSplit:Array = rotSub.split( ‘ ‘ );
properties.rotateX= Number( rotSplit[ 0 ] ) * Number( rotSplit[ 3 ] ) * 100;
properties.rotateY= Number( rotSplit[ 1 ] ) * Number( rotSplit[ 3 ] ) * 100;
properties.rotateZ= Number( rotSplit[ 2 ] ) * Number( rotSplit[ 3 ] ) * 100;
}
container.addChild( this[ ‘_create’ + primitive ]( properties ) );
};
/*return-type for Away3D is Cube instead*/
private function _createBox( properties: Object ) : Box
{
var shape:Box = new Box();
/*Away3D: var shape:Cube = new Cube();*/
for( var p:String in properties ) shape[ p ] = properties[ p ];
return shape;
};
Der erste Filter-Befehl trennt die Kamera von den restlichen Nodes. Man kann diesen Eintrag löschen, wie in unserem Beispiel, oder an dieser Stelle sogar seine 3D-Kamera dynamisch erzeugen.
var nodesExclCamera:Array = nodesInclCamera.filter( filterCamera );
private function filterCamera( element:*, index:int, arr:Array ):Boolean
{
return ( element.match( ‘Camera’ ) == null );
};
Die zweite Schleife durchläuft alle Geomtry-Nodes; hier kann man den Code zum Erzeugen der 3D-Szene einfügen—dazu wird jeweils eine dynmische Funktion aufgerufen anhand der Art von 3D-Objekt. Das funktioniert natürlich nur mit den 3D-Objekten, die auch von der gewählten 3D-Engine unterstützt werden. Außerdem sollte man im Hinterkopf behalten, daß die verschiedenen Körper unterschiedliche Klassennamen in den 3D-Engines haben. So wird ein rechteckiger Körper in Away3D “Cube” genannt, und in Sandy3D “Box”. Generell entsprechen die Bezeichnungen in Sandy3D denen von VRML (gute Arbeit, Jungs):
container.addChild( this[ ‘_create’ + primitive ]( properties ) );
Der Funktionsaufruf übergibt alle nötigen Eigenschaften zum Erzeugen des 3D-Primitve als Objekt. Die Funktion gibt den fertigen 3D-Körper zurück.
/*return-type for Away3D is Cube instead*/
private function _createBox( properties: Object ) : Box
{
var shape:Box = new Box();
/*Away3D: var shape:Cube = new Cube();*/
for( var p:String in properties ) shape[ p ] = properties[ p ];
return shape;
};
Das ist der Ansatz zum Parsen von VRML—ich werde hier nicht weiter in die Details gehen, z.B. dem Setzen von Texturen, welche in VRML in Appearance- und Material-Nodes abgelegt werden. Das Verarbeiten dieser Daten läßt sich ähnlich dem oben vorgestellten Beispiel handhaben. Ich denke, daß sollte als Anstoß reichen.
makc: 12.09.2008, 07:26 AM
sandy parsers used to have problems with .split(‘ ‘) kind of code because you never know how much whitespace will be inserted between stuff. so we now do regexp split instead wherever we notice it - .split(/\s+/)