Contents:
This is a VRML engine, so many programs here do something non-trivial with VRML files. Not surprisingly, I needed at some point to extend what is allowed by VRML specifications, for various reasons. This page documents these extensions, so everyone can use them.
Note that some of these extensions may not be tolerated by other VRML viewers. However:
Many VRML 2.0 extensions may be preceeded by appropriate EXTERNPROTO statements, this will allow other VRML 2.0 implementations to at least gracefully omit them. Kambi VRML test suite uses this mechanism whenever possible, so that even things inside kambi_extensions/ should be partially handled by other VRML browsers.
Our extensions are identified by URN like "urn:vrmlengine.sourceforge.net:node:KambiTriangulation".
Our extensions' external prototypes may specify a fallback URL http://vrmlengine.sourceforge.net/fallback_prototypes.wrl for VRML 2.0. For X3D, analogous URL is http://vrmlengine.sourceforge.net/fallback_prototypes.x3dv. Such fallback URL will allow other VRML browsers to partially handle our extensions. For example, see EXTERNPROTO example for Text3D — browsers that don't handle Text3D node directly should use our fallback URL and render Text3D like normal 2D text node.
TODO: eventual goal is to make all extensions this way, so that they can be nicely omitted. Also, it would be nice to use VRML 1.0 similar feature, isA and fields, for the same purpose, but it's not implemented (and probably never will be, since VRML 1.0 is basically dead and VRML 2.0 / X3D externproto is so much better).
Some of VRML 1.0 extensions are borrowed from VRML 97 specification (e.g. attenuation field for lights), I just allow them also in VRML 1.0.
Some other extensions like compressing VRML files by gzip or multiple root nodes in VRML 1.0 are often implemented in other VRML viewers.
To understand specifications of these extensions you will need some basic knowledge of VRML. Here you can find official VRML 1.0 and 97 specifications if you want to educate yourself.
VRML fields and nodes are specified on this page in the convention somewhat similar to VRML 97 specification:
NodeName {
fieldKind FieldType fieldName default_value # short comment
...
}
Example VRML models that use these extensions may be found in Kambi VRML test suite — look inside vrml_1/kambi_extensions/, vrml_2/kambi_extensions/, x3d/kambi_extensions/, x3d/shaders/kambi_extensions/ subdirectories.
Instead of Appearance node, you can use KambiApperance node that adds some new fields useful for bump mapping:
KambiAppearance {
... all normal Appearance fields ...
exposedField SFNode normalMap NULL # only texture nodes (ImageTexture, MovieTexture, PixelTexture) allowed
exposedField SFNode heightMap NULL # only texture nodes (ImageTexture, MovieTexture, PixelTexture) allowed
exposedField SFFloat heightMapScale 0.01 # must be > 0, meaningful only if heightMap specified
}
|
|
|
|
Texture specified as normalMap describes normal vector values on each texel. Normal vector values are actually encoded as colors: normal vector (x, y, z) should be encoded as RGB((x+1)/2, (y+1)/2, (z+1)/2). You can use e.g. GIMP normalmap plugin to generate such normal maps. (Hint: Remember to check "invert y" when generating normal maps, in image editing programs image Y grows down but we want Y (as interpreted by normals) to grow up, just like texture T coordinate.)
This allows bump mapping to be used. If you set BumpMappingMaximum attribute (and pass light position for bump mapping), our VRML engine will automatically do appropriate bump mapping.
normalMap is enough to use normal bump mapping ("dot product" method, done by pure multitexturing or GLSL programs, depending on OpenGL capabilities). If you additionally specify some texture as heightMap then parallax mapping (steep parallax mapping with self-shadowing, if used OpenGL will support it) will be additionally used. heightMapScale allows you to tweak the perceived height of bumps for parallax mapping.
You can test it in
You can turn it on and see the effects in view3dscene. In view3dscene, for simplicity, bump mapping light position is always set to current camera position. Sample models with normal maps and height maps are inside Kambi VRML test suite, in directory vrml_2/kambi_extensions/bump_mapping/.
You can see this used in The Castle "The Fountain" level. Authors of new levels are encouraged to use bump mapping !
Programmers may also compile and run example program 3dmodels.gl/examples/bump_mapping/ in engine sources, this allows to really play with bump mapping settings and see how to use this in your own programs.
Note some limitations of bump mapping:
|
|
|
These extensions describe the shadows behavior when using the shadow volumes algorithm. They are interpreted by all programs from my engine (like "The Castle", view3dscene and VRML browser components) when rendering shadows using shadow volumes.
General notes about using shadows by shadow volumes:
Shadows are produced if and only if you have some light node in the scene with kambiShadows = kambiShadowsMain = TRUE. That's all that is required to turn shadows on. By default everything is considered a "shadow caster".
Test X3D model that uses dynamic shadows is available in Kambi VRML test suite, see file kambi_vrml_test_suite/x3d/kambi_extensions/shadows_dynamic.x3dv.
For shadow volumes to work perfectly, all parts of the model that are shadow casters should sum to a number of 2-manifold parts. It's allowed to not make them perfectly 2-manifold, but then in some cases, some artifacts are unavoidable — see VRML engine documentation, chapter "Shadows" for description. To be manifold, edge must have exactly two neighboring faces, so that ideally the whole shape is a correct closed volume. Also, faces must be oriented consistently (e.g. CCW outside). This requirement is often quite naturally satisfiable for natural objects, people etc., and consistent ordering allows you to use backface culling which is a good thing on it's own.
You can inspect whether your model is detected as a 2-manifold by view3dscene: see menu item Help -> Manifold Edges Information. To check which edges are actually detected as border you can use View -> Fill mode -> Silhouette and Border Edges, manifold silhouette edges are displayed yellow and border edges (you want to get rid of them) are blue.
You can also check manifold edges in Blender: you can easily detect why the mesh is not manifold by Select non-manifold command (in edit mode). Also, remember that faces must be ordered consistently CCW — I think that in some cases Recalculate normals outside may be needed to reorder them properly.
Shadow casters may be transparent (have material with transparency > 0), this is handled perfectly.
However, note that all opqaue shapes must be 2-manifold and separately all transparent shapes must be 2-manifold. For example, it's Ok to have some transparent box cast shadows over the model. But it's not Ok to have a shadow casting box composed for two separate VRML shapes: one shape defines one box face as transparent, the other shape defines the rest of box faces as opque.
(For programmers: reasoning may be found in TVRMLGLScene.RenderSilhouetteShadowVolume comments, see glDepthFunc(GL_NEVER) notes. For transparent triangles, light/dark caps must always be drawn, even in Z-pass approach.)
Remember to choose rendering optimization other than "scene as a whole" ("scene as a whole" bakes whole rendering call into a single display list, which means that even lights cannot be dynamically turned on/off). None of my programs uses "scene as a whole" by default (as you can guess, "scene as a whole" is only for special purposes when the scene is really absolutely static), so you should be safe here by default.
To all VRML light nodes, we add two fields:
XxxLight {
...
exposedField SFBool kambiShadows FALSE
exposedField SFBool kambiShadowsMain FALSE # meaningfull only when kambiShadows = TRUE
}
The idea is that shadows are actually projected from only one light source (with shadow volumes, number of light sources is limited, since more light sources mean more rendering passes; for now, I decided to use only one light). The scene lights are divided into three groups:
First of all, there's one and exactly one light that makes shadows. Which means that shadows are made where this light doesn't reach. This should usually be the dominant, most intensive light on the scene.
This is taken as the first light node with kambiShadowsMain and kambiShadows = TRUE. Usually you will set kambiShadowsMain to TRUE on only one light node.
There are other lights that don't determine where shadows are, but they are turned off where shadows are. This seems like a nonsense from "realistic" point of view — we turn off the lights, even though they may reach given scene point ? But, in practice, it's often needed to put many lights in this group. Otherwise, the scene could be so light, that shadows do not look "dark enough".
All lights with kambiShadows = TRUE are in this group. (As you see, the main light has to have kambiShadows = TRUE also, so the main light is always turned off where the shadow is).
These are lights with kambiShadows = FALSE (default).
Usually you have to experiment a little to make the shadows look good. This involves determining which light should be the main light (kambiShadowsMain = kambiShadows = TRUE), and which lights should be just turned off inside the shadow (only kambiShadows = TRUE). This system tries to be flexible, to allow you to make shadows look good — which usually means "dark, but not absolutely unrealistically black".
In "The Castle" you can experiment with this using Edit lights inside debug menu.
If no "main" light is found (kambiShadowsMain = kambiShadows = TRUE) then shadows are turned off on this model.
Trick: note that you can set the main light to have on = FALSE. This is the way to make "fake light" — this light will determine the shadows position (it will be treated as light source when calculating shadow placement), but will actually not make the scene lighter (be sure to set for some other lights kambiShadows = TRUE then). This is a useful trick when there is no comfortable main light on the scene, so you want to add it, but you don't want to make the scene actually brighter.
Instead of Appearance node, you can use KambiApperance node. Additional field shadowCaster says whether a shape does cast a shadow. By default all shapes cast a shadow, so typically you need to use this field only to explicitly disable shadows casting from given shape.
KambiAppearance {
... all normal Appearance fields, and KambiAppearance fields documented previously ...
exposedField SFBool shadowCaster TRUE
}
This is honoured by our shadow volumes implementation (that is, dynamic shadows in OpenGL) and also by our ray-tracers.
|
Our engine implements also another algorithm for shadows: the shadow maps. Shadow maps work completely orthogonal to shadow volumes, which means that you can freely mix both shadow approaches (volumes and maps) within a single scene.
The engine allows you to trivially generate (by GeneratedShadowMap node) and apply (by TextureCoordinateGenerator.mode = "PROJECTION") the shadow map.
An example VRML/X3D code for a light and a shadow receiver (everything in the scene will be considered a "shadow caster" for shadow maps):
DEF MySpot SpotLight {
location 0 0 10
direction 0 0 -1
# Additional fields for projective texturing (part of shadow mapping)
projectionNear 1
projectionFar 20
}
Shape {
appearance Appearance {
material Material { }
texture GeneratedShadowMap { light USE MySpot update "ALWAYS" }
}
geometry IndexedFaceSet {
texCoord TextureCoordinateGenerator {
mode "PROJECTION"
projectedLight USE MySpot
}
....
}
}
Note that the shadow texture will be applied in a very trivial way, just to generate intensity values (0 - in shadow, 1 - not in shadow). If you want to have some nice shading, you should use GLSL shader to sample the depth texture (like shadow2DProj(shadowMap, gl_TexCoord[0]).r) and do there any shading you want. Using shaders is generally the way to make shadow mapping both beautiful and in one pass (read: fast), and it's the way of the future anyway. You can start from a trivial fragment shader in our examples: shadow_map.fs.
Every light node gets new fields:
*Light {
...
[in,out] SFFloat projectionNear 1 # > 0
[in,out] SFFloat projectionFar 100 # anything > projectionNear
[in,out] SFVec3f up 0 1 0
}
"projectionNear", "projectionFar" specify the near, far values for projection used when generating the shadow map texture. You should always try to make projectionNear as large as possible and projectionFar as small as possible, this will make depth precision better (keeping projectionNear large is more important for this).
"up" is the up vector of the light node when capturing the shadow map. We know the direction for DirectionalLight and SpotLight, but for shadow mapping we also need to know the up vector. Internally, it will be adjusted to be orthogonal to the directional, so you actually don't have to specify it in many cases — only make sure that it's something non-parallel to the direction.
These properties are specified at the light node, because both texture generation and texture coord generation must know them, and use the same values (otherwise results will not be of much use).
DirectionalLight gets additional fields to specify orthogonal projection rectangle (projection XY sizes) and location for projection (although directional light is conceptually at infinity and doesn't have a location, but for making a texture projection we actually need to know the light's location):
DirectionalLight {
...
[in,out] SFVec4f projectionRectangle -10 10 -10 10 # # left, right, bottom, top; must be left < right and bottom < top
[in,out] SFVec3f projectionLocation 0 0 0 # affected by node's transformation
}
SpotLight gets additional field to specify perspective projection angle.
SpotLight {
...
[in,out] SFFloat projectionAngle 0
}
When projectionAngle is zero, it means to take projection angle from 2 * cutOffAngle. Whether this is Ok, depends on your needs. Note that projectionAngle is the vertical/horizontal field of view for rectangular texture, while cutOffAngle is the cone angle. This means that using cutOffAngle as projection angle would generally make the projected image smaller then perceived light cone. There's no way to choose projectionAngle and cutOffAngle to fit perfectly, since you can't perfectly fit a rectangular texture into a circle shape.
New TextureCoordinateGenerator.mode = "PROJECTION", and new TextureCoordinateGenerator field "projectedLight" (SFNode, inputOutput, default NULL, allowed any light node). This will generate texture coordinates for projective texturing: a texture coordinate (s, t, r, q) will be generated for a fragment that is seen by the light on (s/q, t/q) position, with r/q being the depth. In other words, texture coordinates generated will contain the actual 3D geometry positions, but expressed in light's frustum coordinate system.
This can be used for any kind of projective texturing (not only for shadow maps, but for any situation when light acts like a projector for some texture).
New node:
GeneratedShadowMap {
[in,out] SFNode metadata NULL # [X3DMetadataObject]
[in,out] SFString update "NONE" # ["NONE"|"NEXT_FRAME_ONLY"|"ALWAYS"]
[] SFInt32 size 128
[] SFNode light NULL # any light node
[in,out] SFFloat scale 1.1
[in,out] SFFloat bias 4.0
[] SFString compareMode "COMPARE_R_LEQUAL" # ["COMPARE_R_LEQUAL" | "COMPARE_R_GEQUAL" | "NONE"]
}
The meaning of "update" is like for the standard GeneratedCubeMapTexture: "NONE" is self-explanatory, "ALWAYS" means every frame (for the full dynamic scenes), "NEXT_FRAME_ONLY" says to update at the next frame (and afterwards change back to "NONE").
"size" is the size of the texture. This texture will be created with OpenGL internal format GL_DEPTH_COMPONENT (see ARB_depth_texture), so it's ideal for typical shadow map operations. For GLSL shader, you're best using it with sampler2DShadow.
"light" specifies the light node from which to generate the map. For now, only DirectionalLight and SpotLight are supported. Other nodes types, or NULL, will prevent the texture from generating. It's usually comfortable to "USE" here some existing light node, instead of defining new one.
"scale" and "bias" are used with glPolygonOffset to offset the scene rendered to the shadow map. This avoids ugly precision problems (related to float precision, but also to projecting shadow map texture on the screen with different resolution). In short, increase "bias" if you see strange noise instead of shadows (but don't increase too much, or you will see that shadows visibly move back).
Note that light node instanced inside GeneratedShadowMap.light or TextureCoordinateGenerator.projectedLight isn't considered a normal light, that is it doesn't shine anywhere. It should be defined anywhere in normal scene part to actually act like a normal light. Moreover, it should not be instanced many times in normal scene part, as then it's unspecified from which view we will generate shadow map.
"compareMode" allows to additionally do depth comparison on the texture. For texture coordinate (s, t, r, q), compare mode allows to compare r/q with texture2D(s/q, t/q). Combined with typical "PROJECTION" texture mapping, this is the moment when we actually decide which screen pixel is in shadow and which is not. Default value "COMPARE_R_LEQUAL" is the most useful value for standard shadow mapping, it generates 1 (true) when r/q <= texture2D(s/q, t/q), and 0 (false) otherwise. "NONE" means that comparison is not done, depth texture values are returned directly, this is useful for example for debug / demo purposes — you can view the texture as a normal grayscale (luminance) texture.
To every viewpoint node (this applies to all viewpoints usable in our engine, including all X3DViewpointNode descendants, like Viewpoint and OrthoViewpoint, and even to VRML 1.0 PerspectiveCamera and OrthographicCamera) we add output events that provide you with current camera matrix. One use for such matrices is to route them to your GLSL shaders (as uniform variables), and use inside the shaders to transform between world and camera space.
"cameraMatrix" transforms from world-space (global 3D space that we most often think within) to camera-space (aka eye-space; when thinking within this space, you know then that the camera position is at (0, 0, 0), looking along -Z, with up in +Y). It takes care of both the camera position and orientation, so it's 4x4 matrix. "cameraInverseMatrix" is simply the inverse of this matrix, so it transforms from camera-space back to world-space.
"cameraRotationMatrix" again transforms from world-space to camera-space, but now it only takes care of camera rotations, disregarding camera position. As such, it fits within a 3x3 matrix (9 floats), so it's smaller than full cameraMatrix (4x4, 16 floats). "cameraRotationInverseMatrix" is simply it's inverse. Ideal to transform directions between world- and camera-space in shaders.
XxxViewpoint {
...
[out] SFMatrix4f cameraMatrix
[out] SFMatrix4f cameraInverseMatrix
[out] SFMatrix3f cameraRotationMatrix
[out] SFMatrix3f cameraRotationInverseMatrix
}
|
TextureCoordinateGenerator.mode allows two additional generation modes:
We add new node:
Text3D {
exposedField MFString string []
exposedField SFNode fontStyle NULL
exposedField MFFloat length []
exposedField SFFloat maxExtent 0
exposedField SFFloat depth 0.1 # must be >= 0
exposedField SFBool solid TRUE
}
This renders the text, pretty much like Text node from VRML 97 (see VRML 97 specification about string, fontStyle, length, maxExtent fields). But the text is 3D: it's "pushed" by the amount depth into negative Z. The normal text is on Z = 0, the 3D text had front cap on Z = 0, back cap on Z = -Depth, and of course the extrusion (sides).
Also, it's natural to apply backface culling to such text, so we have a solid field. When true (default), then backface culling is done. This may provide much speedup, unless camera is able to enter "inside" the text geometry (in which case solid should be set to FALSE).
If depth is zero, then normal 2D text is rendered. However, backface culling may still be applied (if solid is true) — so this node also allows you to make 2D text that's supposed to be visible from only front side.
See Kambi VRML test suite, file vrml_2/kambi_extensions/text_depth.wrl for example use of this.
Compatibility:
EXTERNPROTO Text3D [
exposedField MFString string
exposedField SFNode fontStyle
exposedField MFFloat length
exposedField SFFloat maxExtent
exposedField SFFloat depth
exposedField SFBool solid
] [ "urn:vrmlengine.sourceforge.net:node:Text3D",
"http://vrmlengine.sourceforge.net/fallback_prototypes.wrl#Text3D" ]
This way other VRML browsers should be able to render Text3D node like normal 2D Text.
|
Alpha channel of your textures is fully supported, both a simple yes-no transparency (done by alpha_test in OpenGL) and full range transparency (done by blending in OpenGL, just like partially transparent materials). Internally, we have a simple and very nice algorithm that detects whether texture's alpha channel qualifies as simple yes-no or full range, see TImage.AlphaChannelType reference (default tolerance values used by VRML renderer are 5 and 0.01). There is also a special program in engine sources (see images/tools/detect_alpha_simple_yes_no.pasprogram file) if you want to use this algorithm yourself. You can also see the results for your textures if you run view3dscene with --debug-log option.
Sometimes you want to override results of this automatic detection. For example, maybe your texture has some pixels using full range alpha but you stil want to use simpler rendering by alpha_test.
So we add new field to all texture nodes (ImageTexture, MovieTexture, also Texture2 in VRML 1.0 and all future 3D texture nodes in X3D):
TextureNode {
... all normal TextureNode fields ...
field SFString alphaChannel "AUTO" # "AUTO", "SIMPLE_YES_NO" or "FULL_RANGE"
}
Value AUTO means that automatic detection is used, this is the default. Value SIMPLE_YES_NO means that alpha channel, if present, will always be treated like a simple yes/no channel (only fully opaque or fully transparent pixels). FULL_RANGE means that we will always consider alpha channel as full range, and always render it using blending.
|
| This movie shows how it looks animated. You can also get AVI version with much better quality (4.503 MB) |
For MovieTexture nodes, you can use an URL like image%d.png to load movie from a sequence of images. This will load all successive images, substituting counter in place of %d, starting from 1.
You can specify a number between % and d, like %4d, to pad counter with zeros. For example, normal %d.png results in names like 1.png, 2.png, ..., 9.png, 10.png... But %4d.png results in names like 0001.png, 0002.png, ..., 0009.png, 0010.png, ...
Such movie will always be considered to run at the speed of 25 frames per second.
A simple image filename (without %d pattern) is also accepted as a movie URL. This just loads a trivial movie, that consists of one frame and is always still...
Allowed image formats are just like everywhere in our engine — PNG, JPEG and many others, see glViewImage docs for the list.
Besides the fact that loading image sequence doesn't require ffmpeg installed, using image sequence has also one very important advantage over any other movie format: you can use images with alpha channel (e.g. in PNG format), and MovieTexture will be rendered with alpha channel appropriately. This is crucial if you want to have a video of smoke or flame in your game, since such textures usually require an alpha channel.
Samples of MovieTexture usage are inside Kambi VRML test suite, in directory vrml_2/movie_texture/.
New KambiInline node extends standard Inline node, allowing you to do something like search-and-replace automatically on inlined content.
KambiInline {
... all normal Inline fields ...
exposedField MFString replaceNames []
exposedField MFNode replaceNodes [] # any VRML node is valid on this list
}
replaceNames specifies the node names in inlined content to search. replaceNodes are the new nodes to replace with. replaceNames and replaceNodes fields should have the same length. By default, the lists are empty and so KambiInline works exactly like standard Inline node.
An example when this is extremely useful: imagine you have a VRML file generated by exporting from some 3D authoring tool. Imagine that this tool is not capable of producing some VRML content, so you write a couple of VRML nodes by hand, and inline the generated file. For example this is your generated file, generated.wrl:
#VRML V2.0 utf8
Shape {
geometry Box { size 1 2 3 }
appearance Appearance {
texture DEF Tex ImageTexture { url "test.png" }
}
}
and this is your file created by hand, final.wrl:
#VRML V2.0 utf8
# File written by hand, because your 3D authoring tool cannot generate
# NavigationInfo node.
NavigationInfo { headlight "FALSE" }
Inline { url "generated.wrl" }
The advantage of this system is that you can get back to working with your 3D authoring tool, export as many times as you want overriding generated.wrl, and your hand-crafted content stays nicely in final.wrl.
The problem of the above example: what happens if you want to always automatically replace some part inside generated.wrl? For example, assume that your 3D authoring tool cannot export with MovieTexture node, but you would like to use it instead of ImageTexture. Of course, you could just change generated.wrl in any text editor, but this gets very tiresome and dangerous if you plan to later regenerate generated.wrl from 3D authoring tool: you would have to remember to always replace ImageTexture to MovieTexture after exporting. Needless to say, it's easy to forget about such thing, and it gets very annoying when there are more replaces needed. Here's when KambiInline comes to help. Imagine that you use the same generated.wrl file, and as final.wrl you will use
#VRML V2.0 utf8
# File written by hand, because your 3D authoring tool cannot generate
# MovieTexture node.
KambiInline {
url "generated.wrl"
replaceNames "Tex"
replaceNodes MovieTexture { url "test.avi" }
}
Each time when loading final.wrl, our engine will automatically replace in the VRML graph node Tex with specified MovieTexture. Of course the "replacing" happens only in the memory, it's not written back to any file, your files are untouched. Effectively, the effect is like you would load a file
#VRML V2.0 utf8
Shape {
geometry Box { size 1 2 3 }
appearance Appearance {
texture MovieTexture { url "test.avi" }
}
}
By default, VRML/X3D time origin is at 00:00:00 GMT January 1, 1970 and SFTime reflects real-world time (taken from your OS). This is somewhat broken idea in my opinion, unsuitable for normal single-user games. So you can change this by using KambiNavigationInfo node:
KambiNavigationInfo {
... all normal NavigationInfo fields ...
field SFBool timeOriginAtLoad FALSE
}
The default value, FALSE, means the standard VRML behavior. When TRUE the time origin for this VRML scene is considered to be 0.0 when browser loads the file. For example this means that you can easily specify desired startTime values for time-dependent nodes (like MovieTexture or TimeSensor) to start playing at load time, or a determined number of seconds after loading of the scene.
A special Script protocol "compiled:" allows programmers to execute compiled-in code on normal Script events. "Compiled-in code" means simply that you write a piece of code in ObjectPascal and register it after creating the scene. This piece of code will be executed whenever appropriate script will receive an event (when eventIn of the Script is received, or when exposedField is changed by event, or when the script receives initialize or shutdown notifications).
This should be very handy for programmers that integrate our VRML engine in their own programs, and would like to have some programmed response to some VRML events. Using Script node allows you to easily connect programmed code to the VRML graph: you write the code in Pascal, and in VRML you route anything you want to your script.
For example consider this Script:
DEF S Script {
inputOnly SFTime touch_event
inputOnly SFBool some_other_event
inputOnly SFInt32 yet_another_event
url "compiled:
initialize=script_initialization
touch_event=touch_handler
some_other_event=some_other_handler
" }
DEF T TouchSensor { }
ROUTE T.touchTime TO S.touch_event
This means that handler named touch_handler will be executed when user will activate TouchSensor. As additional examples, I added handler named script_initialization to be executed on script initialization, and some_other_handler to execute when some_other_event is received. Note that nothing will happen when yet_another_event is received.
As you see, compiled: Script content simply maps VRML event names to Pascal compiled handler names. Each line maps event_name=handler_name. Lines without = character are understood to map handler of the same name, that is simple line event_name is equivalent to event_name=event_name.
To make this actually work, you have to define and register appropriate handlers in your Pascal code. Like this:
type
TMyObject = class
procedure ScriptInitialization(Value: TVRMLField; const Time: TVRMLTime);
procedure TouchHandler(Value: TVRMLField; const Time: TVRMLTime);
end;
procedure TMyObject.ScriptInitialization(Value: TVRMLField; const Time: TVRMLTime);
begin
{ ... do here whatever you want ...
Value parameter is nil for script initialize/shutdown handler.
}
end;
procedure TMyObject.TouchHandler(Value: TVRMLField; const Time: TVRMLTime);
begin
{ ... do here whatever you want ...
Value parameter here contains a value passed to Script.touch_event.
You can cast it to appropriate field type and get it's value,
like "(Value as TSFTime).Value".
(Although in case of this example, Value here will always come from
TouchSensor.touchTime, so it will contain the same thing
as our Time.Seconds parameter. But in general case, Value can be very useful to you.)
}
end;
{ ... and somewhere after creating TVRMLScene (or TVRMLGLScene) do this: }
Scene.RegisterCompiledScript('script_initialization', @MyObject.ScriptInitialization);
Scene.RegisterCompiledScript('touch_handler', @MyObject.TouchHandler);
For working example code in Pascal and VRML see example program kambi_vrml_game_engine/3dmodels.gl/examples/vrml_browser_script_compiled.pasprogram, use it to open kambi_vrml_test_suite/x3d/simple_script_tests.x3dv, and note that Pascal code reacts to clicks on TouchSensor.
We have a simple scripting language that can be used inside Script nodes. See KambiScript documentation (with examples).
|
|
|
X3DComposedGeometryNode {
... all normal X3DComposedGeometryNode fields ...
exposedField MFVec3f radianceTransfer []
}
The field radianceTransfer specifies per-vertex values for Precomputed Radiance Transfer. For each vertex, a vector of N triples is specified (this describes the radiance transfer of this vertex). We use Vec3f, since our transfer is for RGB (so we need 3 values instead of one). The number of items in radianceTransfer must be a multiple of the number of coord points.
Since this field is available in X3DComposedGeometryNode, PRT can be used with most of the VRML/X3D geometry, like IndexedFaceSet. Note that when using PRT, the color values (color, colorPerVertex fields) are ignored (TODO: in the future I may implement mixing). We also add this field to VRML 1.0 IndexedFaceSet, so with VRML 1.0 this works too.
For PRT to work, the object with radianceTransfer computed must keep this radianceTransfer always corresponding to current coords. This means that you either don't animate coordinates, or you animate coords together with radianceTransfer fields. TODO: make precompute_xxx work with animations, and make an example of this.
For more information, see kambi_vrml_game_engine/3dmodels.gl/examples/radiance_transfer/ demo in engine sources.
TODO: currently radianceTransfer is read but ignored by view3dscene and simple VRML browser components. This means that you have to write and compile some ObjectPascal code (see above radiance_transfer/ example) to actually use this in your games.
See X3D implementation status about programmable shaders.
Since we officially support X3D now, this is not really an extension, it's just normal X3D feature. You can use it in VRML 2.0 models too, as usual our engine allows you to mix VRML versions freely.
For example, this means that a couple of VRML 2.0/X3D nodes are available (and behave exactly like they should) also for VRML 1.0 authors:
Also VRML 1.0 things are available in VRML 2.0, e.g. OrthographicCamera (this is one thing not available in VRML 2.0 specification; although for X3D you should rather use standard OrthoViewpoint).
Also things like GLSL shaders (from X3D) are available in VRML 97.
You can also freely include VRML 1.0 files inside VRML 2.0, or X3D, or the other way around.
Fog {
...
exposedField SFBool volumetric FALSE
exposedField SFVec3f volumetricDirection 0 -1 0 # any non-zero vector
exposedField SFFloat volumetricVisibilityStart 0
exposedField SFNode alternative NULL # NULL or another Fog node
}
Meaning: when volumetric is FALSE (the default), every other volumetricXxx field is ignored and Fog behaves like defined in VRML 97 spec. If volumetric is TRUE, then the volumetric fog is used.
volumetricDirection is the direction of the volumetric fog, it must be any non-zero vector (it's length doesn't matter). Every vertex of the 3D scene is projected on volumetricDirection vector, and then the resulting distance (signed distance, i.e. along the direction of this vector) of this point is used to determine fog amount. For example: in the simple case when volumetricDirection is (0, 1, 0), then the Y coordinate of every vertex determines the amount of fog. In the default case when volumetricDirection is (0, -1, 0), then the negated Y coordinate of every vertex determines the amount of fog. I will call such calculated amount of fog the FogAmount.
Now FogAmount values between volumetricVisibilityStart and volumetricVisibilityStart + visibilityRange correspond to fog color being applied in appropriately 0 (none) and 1 (full) amount. fogType determines how values between are interpolated (in the simple LINEAR case they are interpolated linearly).
Note that volumetricVisibilityStart is transformed by the Fog node transformation scaling, just like visibilityRange in VRML spec.
Note that visibilityRange must stay >= 0, as required by VRML specification. This means that volumetricDirection always specifies the direction of the fog: the more into volumetricDirection, the more fog appears. For example, if your world is oriented such that the +Y is the "up", and ground is on Y = 0, and you want your fog to start from height Y = 20, you should set volumetricDirection to (0, -1, 0) (actually, that's the default) and set volumetricVisibilityStart to -20 (note -20 instead of 20; flipping volumetricDirection flips also the meaning of volumetricVisibilityStart).
Oh, and note that in our programs for now EXPONENTIAL fog (both volumetric and not) is actually approximated by OpenGL exponential fog. Equations for OpenGL exponential fog and VRML exponential fog are actually different and incompatible, so results will be a little different than they should be.
VRML test suite has test VRMLs for this (see vrml_1/kambi_extensions/fog_volumetric/ and vrml_2/kambi_extensions/fog_volumetric/ subdirectories). Also our games malfunction and The Castle use it.
One additional field not explained yet: alternative. This will be used if current graphic output (e.g. OpenGL implementation) for any reason doesn't allow volumetric fog (or at least doesn't allow it to be implemented efficiently). Currently, this means that GL_EXT_fog_coord extension is not supported. In such case we'll look at alternative field:
If alternative is NULL (the default), then no fog will be rendered.
Otherwise we'll try to use fog node recorded in alternative.
If fog node recorded in alternative is not suitable too (e.g. because it also uses volumetric fog) then we'll look at it's alternative field in turn... So in the usual case a fog node placed within the alternative will not use volumetric fog.
alternative will also be tried if the value specified in fogType field of Fog node is not recognized.
Material {
...
exposedField SFBool fogImmune FALSE
}
When fogImmune of given object's material is TRUE, then the fog effect (specified by Fog node) is not applied to the object. Object is "immune" to fog.
This should be used only in a very special cases, when the scene looks better with the material left without fog effect. For example, I used this in The Castle for a river surface material — it's a transparent material, and the whole level is covered with a volumetric fog. It just looks better when the river surface is not affected by the fog color.
Also, you can freely mix VRML versions when including. You're free to include VRML 1.0 file inside VRML 2.0 file, or X3D, or the other way around. Everything works.
This also works for jumping to scenes by clicking on an Anchor node — you can make an Anchor to any VRML version, or a 3DS, Collada etc. file.
|
New node:
KambiTriangulation {
exposedField SFInt32 quadricSlices -1 # {-1} + [3, infinity)
exposedField SFInt32 quadricStacks -1 # {-1} + [2, infinity)
exposedField SFInt32 rectDivisions -1 # [-1, infinity)
}
This node affects rendering of subsequent Sphere, Cylinder, Cone and Cube nodes. For VRML 1.0 you can delimit the effect of this node by using Separator node, just like with other VRML "state changing" nodes. For VRML 2.0 every grouping node (like Group) always delimits this, so it only affects nodes within it's parent grouping node (like many other VRML 2.0 nodes, e.g. DirectionalLight or sensors).
When rendering sphere, cylinder, cone or cube we will triangulate (divide the surfaces into triangles) with settings specified in last KambiTriangulation node. quadricSlices divides like pizza slices, quadricStacks divides like tower stacks, rectDivisions divides rectangular surfaces of a Cube. More precise description of this triangulation is given at description of --detail-... options in view3dscene documentation. Comments given there about so-called over-triangulating apply also here.
Special value -1 for each of these fields means that the program can use it's default value. In case of view3dscene and rayhunter they will use values specified by command-line options --detail-... (or just compiled-in values (see source code) if you didn't specify --detail-... options).
Note that this node gives only a hints to the renderer. Various algorithms and programs may realize triangulation differently, and then hints given by this node may be interpreted somewhat differently or just ignored. That said, this node is useful when you're designing some VRML models and you want to fine-tune the compromise between OpenGL rendering speed and quality of some objects. Generally, triangulate more if the object is large or you want to see light effects (like light spot) looking good. If the object is small you can triangulate less, to get better rendering time.
Test VRML file: see Kambi VRML test suite, file kambi_vrml_test_suite/vrml_2/kambi_extensions/kambi_triangulation.wrl.
E.g. you can call view3dscene like
view3dscene my_compressed_vrml_file.wrl.gz
and you can use WWWInline nodes that refer to gzip-compressed VRML
files, like
WWWInline { name "my_compressed_vrml_file.wrl.gz" }
Filenames ending with .wrl.gz or .wrz are assumed to be always compressed by gzip.
Files with normal extension .wrl but actually compressed by gzip are also handled OK. Currently, there's a small exception to this: when you give view3dscene VRML file on stdin, this file must be already uncompressed (so you may need to pipe your files through gunzip -c). TODO: this is intended to be fixed, although honestly it has rather low priority now.
A personal feeling about this feature from the author (Kambi): I honestly dislike the tendency to compress the files with gzip and then change the extension back to normal .wrl. It's handled by our engine, but only because so many people do it. I agree that it's often sensible to compress VRML files by gzip (especially since before X3D, there was no binary encoding for VRML files). But when you do it, it's also sensible to leave the extension as .wrl.gz, instead of forcing it back into .wrl, hiding the fact that contents are compressed by gzip. Reason: while many VRML browsers detect the fact that file is compressed by gzip, many other programs, that look only at file extension, like text editors, do not recognize that it's gzip data. So they treat .wrl file as a stream of unknown binary data. Programs that analyze only file contents, like Unix file, see that it's a gzip data, but then they don't report that it's VRML file (since this would require decompressing).
Also note that WWW servers, like Apache, when queried by modern WWW browser, can compress your VRML files on the fly. So, assuming that VRML browsers (that automatically fetch URLs) will be also intelligent, the compression is done magically over HTTP protocol, and you don't have to actually compress VRML files to save bandwidth.
Reasoning:
Also, VRML 2.0 spec says that the gravity upward vector should be taken as +Y vector transformed by whatever transformation is applied to Viewpoint node. This also causes similar problems, since e.g. to have gravity upward vector in +Z you have to apply rotation to your Viewpoint node.
So I decided to create new fields for PerspectiveCamera, OrthographicCamera and Viewpoint nodes to allow alternative way to specify an orientation:
PerspectiveCamera / OrthographicCamera / Viewpoint {
...
exposedField MFVec3f direction []
exposedField MFVec3f up []
exposedField SFVec3f gravityUp 0 1 0
}
If at least one vector in direction field is specified, then this is taken as camera look vector. Analogous, if at least one vector in up field is specified, then this is taken as camera up vector. This means that if you specify some vectors for direction and up then the value of orientation field is ignored. direction and up fields should have either none or exactly one element.
As usual, direction and up vectors can't be parallel and can't be zero. They don't have to be orthogonal — up vector will be always silently corrected to be orthogonal to direction. Lengths of these vectors are always ignored.
As for gravity: VRML 2.0 spec says to take standard +Y vector and transform it by whatever transformation was applied to Viewpoint node. So we modify this to say take gravityUp vector and transform it by whatever transformation was applied to Viewpoint node. Since the default value for gravityUp vector is just +Y, so things work 100% conforming to VRML spec if you don't specify gravityUp field.
In view3dscene "Print current camera node" command (key shortcut Ctrl+C) writes camera node in both versions — one that uses orientation field and transformations to get gravity upward vector, and one that uses direction and up and gravityUp fields.
Material {
...
exposedField MFFloat / SFFloat mirror 0.0 # [0.0; 1.0]
}
Currently this is respected only by classic ray-tracer in view3dscene and rayhunter. Well, it's also respected by path-tracer, although it's much better to use fields describing physical properties (Phong's BRDF) for Material node when using path-tracer. In the future mirror field may be somehow respected with normal OpenGL rendering in view3dscene and others.
0.0 means no mirror (i.e. normal surface), 1.0 means the perfect mirror (i.e. only reflected color matters). Values between 0.0 and 1.0 mean that surface's color is partially taken from reflected color, partially from surface's own material color.
Note that this field can be (ab)used to specify completely unrealistic materials. That's because it's not correlated in any way with shininess and specularColor fields. In the Real World the shininess of material is obviously closely correlated with the ability to reflect environment (after all, almost all shiny materials are also mirrors, unless they have some weird curvature; both shininess and mirroring work by reflecting light rays). However, in classic ray-tracer these things are calculated in different places and differently affect the resulting look (shininess and specularColor calculate local effect of the lights, and mirror calculates how to mix with the reflected color). So the actual "shiny" or "matte" property of material is affected by shininess and specularColor fields as well as by mirror field.
The default values of this node are compatible with VRML specification, that explicitly states that NavigationInfo.headlight should have intensity = 1, color = (1 1 1), ambientIntensity = 0.0, and direction = (0 0 -1).
KambiHeadLight {
exposedField SFFloat ambientIntensity 0 # [0.0, 1.0]
exposedField SFVec3f attenuation 1 0 0 # [0, infinity)
exposedField SFColor color 1 1 1 # [0, 1]
exposedField SFFloat intensity 1 # [0, 1]
exposedField SFBool spot FALSE
exposedField SFFloat spotDropOffRate 0
exposedField SFFloat spotCutOffAngle 0.785398
}
The meaning of these field should be self-explanatory: ambientIntensity, attenuation, color and intensity are the same as for PointLight or DirectionalLight in VRML 2.0. If spot is TRUE then the light makes a spot, meaning of spotDropOffRate and spotCutOffAngle is the same as in VRML 1.0 (I didn't use beamWidth from VRML 2.0 spec because it translates badly to OpenGL).
Material {
...
exposedField MFColor reflSpecular [] # specular reflectance
exposedField MFColor reflDiffuse [] # diffuse reflectance
exposedField MFColor transSpecular [] # specular transmittance
exposedField MFColor transDiffuse [] # diffuse transmittance
exposedField MFFloat (SFFloat in VRML 1.0) reflSpecularExp 1000000 # specular reflectance exponent
exposedField MFFloat (SFFloat in VRML 1.0) transSpecularExp 1000000 # specular transmittance exponent
}
Short informal description how these properties work (for precise description see Phong's BRDF equations or source code of my programs):
For VRML 1.0, all these fields have multi- type (like other fields of Material node) to allow you to specify many material kinds at once. For VRML >= 2.0 (includes X3D) only the four non-exponent fields are of multi- type, this is only to allow you to specify zero values there and trigger auto-calculation (see below). Otherwise, you shouldn't place more than one value there for VRML >= 2.0.
Two *SpecularExp fields have default values equal to 1 000 000 = 1 million = practically infinity (bear in mind that they are exponents for cosinus). Other four fields have very special default values. Formally, they are equal to zero-length arrays. If they are left as being zero-length arrays, they will be calculated as follows :
This way you don't have to use any of described here 6 fields. You can use only standard VRML fields (and maybe mirror field) and path tracer will use sensible values derived from other Material fields. If you will specify all 6 fields described here, then path tracer will completely ignore most other Material colors (normal diffuseColor, specularColor etc. fields will be ignored by path tracer then; only emissiveColor will be used, to indicate light sources).
You can use kambi_mgf2inv program to convert MGF files to VRML 1.0 with these six additional Material fields. So you can easily test my ray-tracer using your MGF files.
These fields are used only by path tracer in rayhunter and view3dscene.
|
Like most 3D engines, Kambi VRML game engine uses a smart tree structure to handle collision detection in arbitrary 3D worlds. The structure used in our engine is the octree, with a couple of special twists to handle dynamic scenes. See documentation chapter "octrees" for more explanation.
There are some limits that determine how fast the octree is constructed, how much memory does it use, and how fast can it answer collision queries. While our programs have sensible and tested defaults hard-coded, it may be useful (or just interesting for programmers) to test other limits — this is what this extension is for.
In all honesty, I (Michalis) do not expect this extension to be commonly used... It allows you to tweak an important, but internal, part of the engine. For most normal people, this extension will probably look like an uncomprehensible black magic. And that's Ok, as the internal defaults used in our engine really suit (almost?) all practical uses.
If the above paragraph didn't scare you, and you want to know more about octrees in our engine: besides documentation chapter "octrees" you can also take a look at the (source code and docs) of the TVRMLScene.Spatial property and units VRMLTriangle, VRMLTriangleOctree and VRMLShapeOctree.
A new node:
KambiOctreeProperties {
field SFInt32 maxDepth -1 # must be >= -1
field SFInt32 leafCapacity -1 # must be >= -1
}
Limit -1 means to use the default value hard-coded in the program. Other values force the generation of octree with given limit. For educational purposes, you can make an experiment and try maxDepth = 0: this forces a one-leaf tree, effectively making octree searching work like a normal linear searching. You should see a dramatic loss of game speed on non-trivial models then.
To affect the scene octrees you can place KambiOctreeProperties node inside KambiNavigationInfo node. For per-shape octrees, we add new fields to Shape node:
KambiNavigationInfo {
...
field SFNode octreeRendering NULL # only KambiOctreeProperties node
field SFNode octreeDynamicCollisions NULL # only KambiOctreeProperties node
field SFNode octreeVisibleTriangles NULL # only KambiOctreeProperties node
field SFNode octreeCollidableTriangles NULL # only KambiOctreeProperties node
}
X3DShapeNode (e.g. Shape) {
...
field SFNode octreeTriangles NULL # only KambiOctreeProperties node
}
See the API documentation for classes TVRMLScene and TVRMLShape for precise description about what each octree is. In normal simulation of dynamic 3D scenes, we use only octreeRendering, octreeDynamicCollisions and Shape.octreeTriangles octrees. Ray-tracers usually use octreeVisibleTriangles.
We will use scene octree properties from the first bound NavigationInfo node (see VRML/X3D specifications about the rules for bindable nodes). If this node is not KambiNavigationInfo, or appropriate octreeXxx field is NULL, or appropriate field within KambiOctreeProperties is -1, then the default hard-coded limit will be used.
Currently, it's not perfectly specified what happens to octree limits when you bind other [Kambi]NavigationInfo nodes during the game. With current implementation, this will cause the limits to change, but they will be actually applied only when the octree will be rebuild — which may happen never, or only at some radical rebuild of VRML graph by other events. So if you have multiple [Kambi]NavigationInfo nodes in your world, I advice to specify in all of them exactly the same octreeXxx fields values.
We handle some Avalon / instant-reality extensions. See instant-reality and in particular the specifications of Avalon extensions.
Please note that I implemented this all looking at Avalon specifications, which are quite terse. (I didn't actually run instant-reality program (closed-source, not installable under current Debian testing).) Please report any incompatibilities.
|
We add new field to KambiAppearance node: blendMode (SFNode, NULL by default, inputOutput). It's allowed to put there BlendMode node to specify blending mode done for partially-transparent objects. BlendMode node is not X3D standard, but it's specified by Avalon: see BlendNode specification.
From Avalon spec, our engine supports a subset of fields: srcFactor, destFactor, color, colorTransparency. Note that some values require newer OpenGL versions, we will eventually fallback on browser-specific blending modes (you can set them explicitly in view3dscene).
For example:
appearance KambiAppearance {
material Material {
transparency 0.5
}
blendMode BlendMode {
srcFactor "src_alpha" # actually this srcFactor is the default
destFactor "one"
}
}
Example above sets blending to an often-desired equation where the order of rendering doesn't matter. It's very useful for models with complex 3D partially-transparent objects, otherwise traditional approach (src_alpha and one_minus_src_alpha) may cause rendering artifacts.
MatrixTransform: supported matrix field, and the standard X3DGroupingNode fields.
This is analogous to Transform node, but specifies explicit 4x4 matrix. Note that VRML 1.0 also had MatrixTransform node (we also handle it), although specified a little differently. Later VRML 97 and X3D removed the MatrixTransform node from official specification — this extension fills the gap.
Note that this node was removed from specifications for a good reason. Our engine can invert your matrix internally (this is needed for some things, like bump mapping), but still it's difficult to extract particular features (like scaling factor) from such matrix. Currently our engine extracts scaling factors by very naive method. (Although this is planned to be fixed using unmatrix.c algorithm.) The bottom line is: You are well adviced to try to express all transformations using stardard Transform node.
This node may be useful when you really have no choice (for example, when converting from Collada files that have transformation written as explicit 4x4 matrix, it's natural to convert it to VRML MatrixTransform).
Logger: supported level, logFile, enabled fields and write inputOnly event.
|
An extremely useful debugger when playing with VRML / X3D routes and events. The idea is simple: whatever is sent to write input event is logged on the console. write event has special type (Avalon calls this XFAny) that allows to receive any VRML field type.
Other properties allow to control logging better. When enabled is false, nothing is logged. level controls the amount of logged info:
logFile, when non-empty, specifies the filename to write log information to. (When logFile is empty, it's all simply dumped on standard output, i.e. usually console.) As a security measure (you really do not want to allow an author of X3D file to overwrite arbitrary files without asking user), in my implementation only the basename of the logFile matters, the file is always saved into current directory. Moreover, filename is like view3dscene_logger_XXX_%d.log, where "view3dscene" is the name of the program, "XXX" is the name specified in logFile, and "%d" is just next free number. This way logger output file is predictable, and should never overwrite your data.
These security measures were added by my implementation — Avalon spec simply says that logFile is the name of the file, I don't know how they handled security problems with logFile.
|
A teapot. Useful non-trivial shape for testing various display modes, shaders and such.
Compatibility with Avalon Teapot: we support size and solid fields from Avalon. Fields texCoord and manifold are our own (Kambi engine) extensions.
Teapot : X3DGeometryNode {
[in,out] SFNode metadata NULL # [X3DMetadataObject]
[] SFVec3f size 3 3 3
[] SFBool solid TRUE
[] SFBool manifold FALSE
[in,out] SFNode texCoord NULL # [TextureCoordinateGenerator]
}
The "size" field allows you to scale the teapot, much like the standard Box node. The default size (3, 3, 3) means that the longest size of teapot bounding box is 3.0 (all other sizes are actually slightly smaller). Changing size scales the teapot (assuming that size = 3 means "default size").
The "texCoord" field may contain a TextureCoordinateGenerator node specifying how texture coordinates are generated. Very useful to quickly test various texture coordinate generators (e.g. for cube env mapping) on teapot. When texCoord is not present but texture coordinates are required (because appearance specifies a texture), we will generate default texture coords (using the same alrgoithm as for IndexedFaceSet).
The "solid" field has standard meaning: if true (default), it's assumed that teapot will never be seen from the inside (and backface culling is used to speed up rendering).
The "manifold" field allows you to force teapot geometry to be correctly closed (2-manifold, where each edge has exactly 2 neighboring faces). This is useful if you want to use shadow volumes to cast shadow of this teapot.
For the sake of VRML / X3D standards, I do not really advice using this node... VRML developers should spend their time better than to implement such nodes of little practical use :), and it's possible to make the same thing with a PROTO. But it's useful for testing purposes.
|
Texture rendered from a specified viewpoint in the 3D scene. This can be used for a wide range of graphic effects, the most straighforward use is to make something like a "security camera" or a "portal", through which a player can peek what happens at the other place in 3D world.
This is mostly compatible with Avalon RenderedTexture specification. We do not support all Avalon fields, but the basic fields and usage remain the same.
RenderedTexture : X3DTextureNode {
[in,out] SFNode metadata NULL # [X3DMetadataObject]
[in,out] MFInt32 dimensions 128 128 4 1 1
[in,out] SFString update "NONE" # ["NONE"|"NEXT_FRAME_ONLY"|"ALWAYS"]
[in,out] SFNode viewpoint NULL # [X3DViewpointNode] (VRML 1.0 camera nodes also allowed)
[] SFNode textureProperties NULL # [TextureProperties]
[] SFBool repeatS TRUE
[] SFBool repeatT TRUE
[] SFBool repeatR TRUE
[in,out] MFBool depthMap []
}
First two numbers in "dimensions" field specify the width and the height of the texture. (Our current implementation ignores the rest of dimensions field. Also, in current implementation, you should treat dimensions field as initializeOnly, i.e. do not change it's value after world is loaded.)
"update" is the standard field for automatically generated textures (works the same as for GeneratedCubeMapTexture or GeneratedShadowMap). It says when to actually generate the texture: "NONE" means never, "ALWAYS" means every frame (for fully dynamic scenes), "NEXT_FRAME_ONLY" says to update at the next frame (and afterwards change back to "NONE").
"viewpoint" allows you to explicitly specify viewpoint node from which to render to texture. Default NULL value means to render from the current camera (this is equivalent to specifying viewpoint node that is currently bound). Yes, you can easily see recursive texture using this, just look at the textured object. It's quite fun :) (It's not a problem for rendering speed — we always render texture only once in a frame.) You can of course specify other viewpoint node, to make rendering from there.
"textureProperties" is the standard field of all texture nodes. You can place there a TextureProperties node to specify magnification, minification filters (note that mipmaps, if required, will always be correctly automatically updated for RenderedTexture), anisotropy and such.
"repeatS", "repeatT", "repeatR" are also standard for texture nodes, specify whether texture repeats or clamps. For RenderedTexture, you may often want to set them to FALSE. "repeatR" is for 3D textures, useless for now.
"depthMap", if it is TRUE, then the generated texture will contain the depth buffer of the image (instead of the color buffer as usual). (Our current implementation only looks at the first item of MFBool field depthMap. Also, in current implementation, you should treat depthMap as initializeOnly, i.e. do not change it's value after world is loaded.)
Avalon Plane node. You should instead use Rectangle2D node from X3D 3.2 when possible, this is implemented only for compatibility.
Our current implementation doesn't support anything more than size and solid fields. So it's really equivalent to Rectangle2D inside our engine, the only difference being that Plane.solid is TRUE by default (for Rectangle2D spec says it's FALSE by default).
Moreover, all lights have ambientIntensity field, also defined exactly like in VRML 97. However, when reading VRML 1.0 files, we treat default value of ambientIntensity as -1 (while VRML 97 specification gives 0). And when rendering, we treat lights with ambientIntensity < 0 specially: we treat them like ambientIntensity = intensity. This way:
These things allow me to handle many Inventor 1.0 files. They also allow me to handle many broken VRML 1.0 files that sometimes falsely claim that they are VRML 1.0 while in fact they use some Inventor-specific features.
For completely unrecognized nodes, our engine can always omit them (even without any VRML >= 2.0 (protos) or VRML 1.0 ("fields", "isA") extensibility features), so most Inventor files can be at least partially handled and displayed.
WWWInline {
...
exposedField SFBool separate TRUE
}
To explain this field, let's create an example. Assume you have file 1.wrl with following contents:
#VRML V1.0 ascii
Material { diffuseColor 1 0 0 }
And a file 2.wrl with following contents:
#VRML V1.0 ascii
Group {
WWWInline { name "1.wrl" }
Cube { }
}
Question: what material is used by the cube ? The red material (defined in 1.wrl) or the default material ? In other words, do the state changes inside 1.wrl "leak outside" of WWWInline node ?
The answer (stated by VRML specification, and followed by our programs when separate is TRUE (the default)) is that the cube uses the default material. Not the red material. In other words, state changes do not "leak" outside. This is definitely a sensible behavior. This is safer for the author of VRML files (you're not so "vulnerable" to changes done in included files). And it allows to delay loading of inlined file until it's really needed (e.g. is potentially visible). Effectively, this means that WWWInline behaves a little like a Separator node. File 2.wrl is equivalent to
#VRML V1.0 ascii
Group {
Separator {
Material { diffuseColor 1 0 0 }
}
Cube { }
}
On the other hand, when you set field separate to FALSE, the cube will be red. Every state change done inside inlined file will affect the things defined after WWWInline node. Effectively, this means that WWWInline behaves a little like a Group node. Two files below are equivalent:
#VRML V1.0 ascii
Group {
WWWInline { name "1.wrl" separare FALSE }
Cube { }
}
#VRML V1.0 ascii
Group {
Group {
Material { diffuseColor 1 0 0 }
}
Cube { }
}
Generally, setting field separate to FALSE is a little dangerous (because you have to be careful what you include), but it also allows you to do various tricks.
Test VRML file: see VRML test suite, file vrml_1/kambi_extensions/inline_not_separate.wrl.