[x3d-public] Prototype Expander, Question on design of instance expansions

Don Brutzman brutzman at nps.edu
Sat Jul 2 14:09:55 PDT 2016


Thank you for bringing up this really important issue John.  I think you have uncovered an excellent implementation challenge.

On 7/1/2016 1:36 PM, John Carlson wrote:
>
> I have an -material object in JSON which accepts a ProtoInstance. The ProtoBody has -children, the first of which is Material.  There are more nodes including Script nodes.  The question is, how do I create prototype expanded JSON?

Specifically, since the first node of a ProtoBody is what gets rendered, and the following top-level sibling nodes are active but non-rendered, how do they get handled when expanding the prototype?

X3D Tooltips: ProtoBody
http://www.web3d.org/x3d/content/X3dTooltips.html#ProtoBody
===========================================================
ProtoBody collects ProtoDeclare body nodes.
Warning: only the first top-level node and its children are rendered, subsequent nodes (such as Scripts and ROUTEs) are active but are not drawn.
===========================================================

4.4.4.3 PROTO definition semantics
http://www.web3d.org/documents/specifications/19775-1/V3.3/Part01/concepts.html#PROTOdefinitionsemantics
===========================================================
A prototype definition consists of one or more nodes, nested PROTO statements, and ROUTE statements. The first node type determines how instantiations of the prototype can be used in an X3D file. An instantiation is created by filling in the parameters of the prototype declaration and inserting copies of the first node (and its scene graph) wherever the prototype instantiation occurs.

EXAMPLE  If the first node in the prototype definition is a Material node, instantiations of the prototype can be used wherever a Material node can be used. Any other nodes and accompanying scene graphs are not part of the transformation hierarchy, but may be referenced by ROUTE statements or Script nodes in the prototype definition.
===========================================================

Important to note:  this challenge is not about the X3D JSON encoding per se.  We are able to produce a 1::1 correspondence between .json and .x3d .x3dv etc.

This implementation challenge is about how to implement the Prototype Expander so that ProtoInstance nodes might get replaced with cookie-cutter instantiations of a prototype declaration template.

This question was likely answered in similar but slightly different ways by each X3D player that has implemented Prototypes.  So there should be some good example implementations visible in Xj3D, freewrl, view3dscene, Cobweb ("Cobweb supports custom shaders, clip planes, reflection mapping, script nodes, prototyping capabilities"), etc.

Given their excellent implementation of prototypes in InstantReality, and their design of X3DOM, it would be a good thing if Fraunhofer might release their IR prototype implementation as an open-source fragment to facilitate these kinds of efforts.

>Included are the original JSON and the JSON with the current expansion.  I can’t just take the first node, because then the script would be left off.  Ideas?  What most faithfully represents the original?  What about other cases (I have more)?

I saved your example conversions as MaterialModulatorPrototypeInstance.json and MaterialModulatorPrototypeExpanded.json, respectively.  Re-attached.

> It would be really nice if JSON schema handled arrays for -material, -geometry and -appearance at least, so we can add the non-rendered nodes without invalidating the file.
>
> How would VRML handle this?

For the moment I think that we are mostly in implementation space, not language design.  Let's first work through with what we have already.  After that, gap analysis can tell us if additional specification support is needed.

So at present, a better question for integrating prototype capabilities in in X3DOM might be "How can a prototype expander be used as a preprocessor to generate internal X3DOM javascript source emulating the scene graph?"  That is certainly an appropriate goal for the x3dom-developer community (cc:ed).

Meanwhile, let's press ahead separately with also trying to produce a general-case solution with your expander.  Since the solution must work for any of the X3D encodings, likely no encoding-specific workarounds are needed.

First-pass design follows.  All review & improvement welcome:

1. Goal: convert valid X3D with ProtoInstance nodes to corresponding valid X3D without ProtoInstance nodes, substituting nodes found in ProtoDeclare (possibly via ExternProtoDeclare).

2. Requirements:
a. Legal scene graph results which can pass all X3D validity tests.
b. Maintain all ROUTE and IS/connect pathways for events correctly.  May require some renaming.
c. Repeatable examples (MaterialModulator is a good place to start).

3. Approach:
a. Substitute the first node (along with contained scene subgraph) of Protobody as replacement for ProtoInstance.
b. Handle follow-on nodes by also inserting them, in a scene-valid manner.
b.1. When the first node in the ProtoBody is an X3DChildNode, any follow-on nodes can be wrapped in a Switch (with whichChoice="-1") to ensure they are non-rendering.
b.2. When first node in the ProtoBody is not an X3DChildNode, any follow-on nodes are still wrapped in a Switch which follows at the closest subsequent point in the scene graph that can accept children nodes.
c. External routing: field names within ROUTEs are modified, and DEF names adjusted if needed, and IS/connect statements are removed, to preserve event-passing semantics.
d. External routing: field names within ROUTEs are modified, and DEF names adjusted if needed, to preserve event-passing semantics.
e. Naming convention needed for steps c and d, which are quite similar.  Needs to be able to handle more than one copy of a ProtoInstance getting expanded.
f. Assign initial values to field definitions where appropriate.
g.Nice to have: possibility that an expanded ProtoInstance block of nodes might be reconverted later into the original ProtoInstance, perhaps simply by containing the original as a comment.

Looking at your MaterialModulatorPrototypeExpanded.json above, it appears that your approach accomplishes most of these steps already.  Only significant difference would be step b.2, handling incompatible follow-on nodes within a Shape.

Next attachment is original scene with ProtoDeclare and ProtoInstance: MaterialModulator.x3d

Attached please find an attempt MaterialModulatorExpanded that follows a quite similar pattern to yours.  Only intended difference is to put the hiding Switch following the Shape node - thus it includes restructuring for step b.2.  This expansion works in BS Contact, Instant Reality, FreeWrl, Octaga, Cobweb.

There is still a bit more trickiness to sort out here with the renaming, but it looks like a general pattern is possible.

Am also optimistic that we might be able to create an XSLT stylesheet that accomplishes the identical task of converting ProtoInstance nodes into expanded nodes.

... so, seems pretty promising.  A few more rocks and shoals to navigate but appears that we can get there from here.  What do you think?

all the best, Don
-- 
Don Brutzman  Naval Postgraduate School, Code USW/Br       brutzman at nps.edu
Watkins 270,  MOVES Institute, Monterey CA 93943-5000 USA   +1.831.656.2149
X3D graphics, virtual worlds, navy robotics http://faculty.nps.edu/brutzman
-------------- next part --------------
{ "X3D": {
    "encoding":"UTF-8",
    "@profile":"Immersive",
    "@version":"3.3",
    "@xsd:noNamespaceSchemaLocation":"http://www.web3d.org/specifications/x3d-3.3.xsd",
    "JSON schema":"http://www.web3d.org/specifications/x3d-3.3-JSONSchema.json",
    "head": {
        "meta": [
          {
            "@name":"title",
            "@content":"MaterialModulator.x3d"
          },
          {
            "@name":"description",
            "@content":"Mimic a Material node and modulate fields as an animation effect"
          },
          {
            "@name":"hint",
            "@content":"try changing the modulation script so that it goes from [0 ... 1] and then [1 ... 0] alternating, rather than abruptly shifting from 1 immediately back to 0."
          },
          {
            "@name":"creator",
            "@content":"Don Brutzman"
          },
          {
            "@name":"created",
            "@content":"10 March 2008"
          },
          {
            "@name":"modified",
            "@content":"28 November 2014"
          },
          {
            "@name":"subject",
            "@content":"X3D prototype requiring Script inputOutput fields"
          },
          {
            "@name":"Image",
            "@content":"MaterialModulator.png"
          },
          {
            "@name":"identifier",
            "@content":"http://X3dGraphics.com/examples/X3dForWebAuthors/Chapter14-Prototypes/MaterialModulator.x3d"
          },
          {
            "@name":"generator",
            "@content":"X3D-Edit 3.3, https://savage.nps.edu/X3D-Edit"
          },
          {
            "@name":"license",
            "@content":"../license.html"
          },
          {
            "@name":"translated",
            "@content":"27 March 2016"
          },
          {
            "@name":"generator",
            "@content":"X3dToJson.xslt, http://www.web3d.org/x3d/stylesheets/X3dToJson.html"
          },
          {
            "@name":"warning",
            "@content":"An experimental version of X3D JSON encoding is used for this scene.  Status online at http://www.web3d.org/wiki/index.php/X3D_JSON_Encoding"
          }
        ]
    },
    "Scene": {
        "-children":[
          { "ProtoDeclare":
            {
              "@name":"MaterialModulator",
              "@appinfo":"mimic a Material node and modulate fields as an animation effect",
              "ProtoInterface": {
                  "field": [
                    {
                      "@name":"enabled",
                      "@accessType":"inputOutput",
                      "@type":"SFBool",
                      "@value":true
                    },
                    {
                      "@name":"diffuseColor",
                      "@accessType":"inputOutput",
                      "@type":"SFColor",
                      "@value":[0.8,0.8,0.8]
                    },
                    {
                      "@name":"emissiveColor",
                      "@accessType":"inputOutput",
                      "@type":"SFColor",
                      "@value":[0,0,0]
                    },
                    {
                      "@name":"specularColor",
                      "@accessType":"inputOutput",
                      "@type":"SFColor",
                      "@value":[0,0,0]
                    },
                    {
                      "@name":"transparency",
                      "@accessType":"inputOutput",
                      "@type":"SFFloat",
                      "@value":0.0
                    },
                    {
                      "@name":"shininess",
                      "@accessType":"inputOutput",
                      "@type":"SFFloat",
                      "@value":0.2
                    },
                    {
                      "@name":"ambientIntensity",
                      "@accessType":"inputOutput",
                      "@type":"SFFloat",
                      "@value":0.2
                    }
                  ]
              },
              "ProtoBody": {
                  "-children":[
                    { "Material":
                      {
                        "@DEF":"MaterialNode",
                        "IS": {
                            "connect": [
                              {
                                "@nodeField":"diffuseColor",
                                "@protoField":"diffuseColor"
                              },
                              {
                                "@nodeField":"emissiveColor",
                                "@protoField":"emissiveColor"
                              },
                              {
                                "@nodeField":"specularColor",
                                "@protoField":"specularColor"
                              },
                              {
                                "@nodeField":"transparency",
                                "@protoField":"transparency"
                              },
                              {
                                "@nodeField":"shininess",
                                "@protoField":"shininess"
                              },
                              {
                                "@nodeField":"ambientIntensity",
                                "@protoField":"ambientIntensity"
                              }
                            ]
                        }
                      }
                    },
                    { "#comment":"Only first node (the node type) is renderable, others are along for the ride"
                    },
                    { "Script":
                      {
                        "@DEF":"MaterialModulatorScript",
                        "field": [
                          {
                            "@name":"enabled",
                            "@accessType":"inputOutput",
                            "@type":"SFBool"
                          },
                          {
                            "@name":"diffuseColor",
                            "@accessType":"inputOutput",
                            "@type":"SFColor"
                          },
                          {
                            "@name":"newColor",
                            "@accessType":"outputOnly",
                            "@type":"SFColor"
                          },
                          {
                            "@name":"clockTrigger",
                            "@accessType":"inputOnly",
                            "@type":"SFTime"
                          }
                        ],
                        "IS": {
                            "connect": [
                              {
                                "@nodeField":"enabled",
                                "@protoField":"enabled"
                              },
                              {
                                "@nodeField":"diffuseColor",
                                "@protoField":"diffuseColor"
                              }
                            ]
                        },
                        "#sourceText":[
"ecmascript:",
"function initialize ()",
"{",
"    newColor = diffuseColor; // start with correct color",
"}",
"",
"function clockTrigger (timeValue)",
"{",
"    if (!enabled) return;",
"    red   = newColor.r;",
"    green = newColor.g;",
"    blue  = newColor.b;",
"    ",
"    // note different modulation rates for each color component, % is modulus operator",
"    newColor = new SFColor ((red + 0.02) % 1, (green + 0.03) % 1, (blue + 0.04) % 1);   ",
"    Browser.print ('diffuseColor=(' + red +',' + green + ',' + blue + ') newColor=' + newColor.toString() + '\\');",
"}"
]
                      }
                    },
                    { "#comment":"Clock tickles Script to wake up and compute a new value"
                    },
                    { "ROUTE":
                      {
                        "@fromField":"newColor",
                        "@fromNode":"MaterialModulatorScript",
                        "@toField":"diffuseColor",
                        "@toNode":"MaterialNode"
                      }
                    },
                    { "TimeSensor":
                      {
                        "@DEF":"ModulationClock",
                        "@cycleInterval":0.1,
                        "@loop":true
                      }
                    },
                    { "ROUTE":
                      {
                        "@fromField":"cycleTime",
                        "@fromNode":"ModulationClock",
                        "@toField":"clockTrigger",
                        "@toNode":"MaterialModulatorScript"
                      }
                    }
                  ]
              }
            }
          },
          { "#comment":"Rendered geometry follows prototype declaration"
          },
          { "Shape":
            {
              "-geometry":
                { "Sphere":
                  {
                  }
                },
              "-appearance":
                { "Appearance":
                  {
                    "-material":
                      { "ProtoInstance":
                        {
                          "@name":"MaterialModulator",
                          "fieldValue": [
                            {
                              "@name":"enabled",
                              "@value":true
                            },
                            {
                              "@name":"diffuseColor",
                              "@value":[0.5,0.1,0.1]
                            }
                          ]
                        }
                      }
                  }
                }
            }
          }
        ]
    }
  }
}
-------------- next part --------------
{
  "X3D": {
    "encoding": "UTF-8",
    "@profile": "Immersive",
    "@version": "3.3",
    "@xsd:noNamespaceSchemaLocation": "http://www.web3d.org/specifications/x3d-3.3.xsd",
    "JSON schema": "http://www.web3d.org/specifications/x3d-3.3-JSONSchema.json",
    "head": {
      "meta": [
        {
          "@name": "title",
          "@content": "MaterialModulator.x3d"
        },
        {
          "@name": "description",
          "@content": "Mimic a Material node and modulate fields as an animation effect"
        },
        {
          "@name": "hint",
          "@content": "try changing the modulation script so that it goes from [0 ... 1] and then [1 ... 0] alternating, rather than abruptly shifting from 1 immediately back to 0."
        },
        {
          "@name": "creator",
          "@content": "Don Brutzman"
        },
        {
          "@name": "created",
          "@content": "10 March 2008"
        },
        {
          "@name": "modified",
          "@content": "28 November 2014"
        },
        {
          "@name": "subject",
          "@content": "X3D prototype requiring Script inputOutput fields"
        },
        {
          "@name": "Image",
          "@content": "MaterialModulator.png"
        },
        {
          "@name": "identifier",
          "@content": "http://X3dGraphics.com/examples/X3dForWebAuthors/Chapter14-Prototypes/MaterialModulator.x3d"
        },
        {
          "@name": "generator",
          "@content": "X3D-Edit 3.3, https://savage.nps.edu/X3D-Edit"
        },
        {
          "@name": "license",
          "@content": "../license.html"
        },
        {
          "@name": "translated",
          "@content": "27 March 2016"
        },
        {
          "@name": "generator",
          "@content": "X3dToJson.xslt, http://www.web3d.org/x3d/stylesheets/X3dToJson.html"
        },
        {
          "@name": "warning",
          "@content": "An experimental version of X3D JSON encoding is used for this scene.  Status online at http://www.web3d.org/wiki/index.php/X3D_JSON_Encoding"
        }
      ]
    },
    "Scene": {
      "-children": [
        {},
        {
          "#comment": "Rendered geometry follows prototype declaration"
        },
        {
          "Shape": {
            "-geometry": {
              "Sphere": {}
            },
            "-appearance": {
              "Appearance": {
                "-material": [
                  {
                    "Material": {
                      "@DEF": "DECLMaterialModulator_INSTANCE_MaterialNode",
                      "@diffuseColor": [
                        0.5,
                        0.1,
                        0.1
                      ],
                      "@emissiveColor": [
                        0,
                        0,
                        0
                      ],
                      "@specularColor": [
                        0,
                        0,
                        0
                      ],
                      "@transparency": 0,
                      "@shininess": 0.2,
                      "@ambientIntensity": 0.2
                    }
                  },
                  {
                    "#comment": "Only first node (the node type) is renderable, others are along for the ride"
                  },
                  {
                    "Script": {
                      "@DEF": "DECLMaterialModulator_INSTANCE_MaterialModulatorScript",
                      "field": [
                        {
                          "@name": "enabled",
                          "@accessType": "inputOutput",
                          "@type": "SFBool",
                          "@value": true
                        },
                        {
                          "@name": "diffuseColor",
                          "@accessType": "inputOutput",
                          "@type": "SFColor",
                          "@value": [
                            0.5,
                            0.1,
                            0.1
                          ]
                        },
                        {
                          "@name": "newColor",
                          "@accessType": "outputOnly",
                          "@type": "SFColor"
                        },
                        {
                          "@name": "clockTrigger",
                          "@accessType": "inputOnly",
                          "@type": "SFTime"
                        }
                      ],
                      "@value": [
                        0.8,
                        0.8,
                        0.8
                      ],
                      "#sourceText": [
                        "ecmascript:",
                        "function initialize ()",
                        "{",
                        "    newColor = diffuseColor; // start with correct color",
                        "}",
                        "",
                        "function clockTrigger (timeValue)",
                        "{",
                        "    if (!enabled) return;",
                        "    red   = newColor.r;",
                        "    green = newColor.g;",
                        "    blue  = newColor.b;",
                        "    ",
                        "    // note different modulation rates for each color component, % is modulus operator",
                        "    newColor = new SFColor ((red + 0.02) % 1, (green + 0.03) % 1, (blue + 0.04) % 1);   ",
                        "    Browser.print ('diffuseColor=(' + red +',' + green + ',' + blue + ') newColor=' + newColor.toString() + '\\');",
                        "}"
                      ]
                    }
                  },
                  {
                    "#comment": "Clock tickles Script to wake up and compute a new value"
                  },
                  {
                    "ROUTE": {
                      "@fromNode": "DECLMaterialModulator_INSTANCE_MaterialModulatorScript",
                      "@fromField": "newColor",
                      "@toNode": "DECLMaterialModulator_INSTANCE_MaterialNode",
                      "@toField": "diffuseColor"
                    }
                  },
                  {
                    "TimeSensor": {
                      "@DEF": "DECLMaterialModulator_INSTANCE_ModulationClock",
                      "@cycleInterval": 0.1,
                      "@loop": true
                    }
                  },
                  {
                    "ROUTE": {
                      "@fromNode": "DECLMaterialModulator_INSTANCE_ModulationClock",
                      "@fromField": "cycleTime",
                      "@toNode": "DECLMaterialModulator_INSTANCE_MaterialModulatorScript",
                      "@toField": "clockTrigger"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: MaterialModulator.x3d
Type: model/x3d+xml
Size: 4610 bytes
Desc: not available
URL: <http://web3d.org/pipermail/x3d-public_web3d.org/attachments/20160702/f6a816c9/attachment-0002.x3d>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: MaterialModulatorPrototypeExpanded.x3d
Type: model/x3d+xml
Size: 3075 bytes
Desc: not available
URL: <http://web3d.org/pipermail/x3d-public_web3d.org/attachments/20160702/f6a816c9/attachment-0003.x3d>
-------------- next part --------------
{ "X3D": {
    "encoding":"UTF-8",
    "@profile":"Immersive",
    "@version":"3.3",
    "@xsd:noNamespaceSchemaLocation":"http://www.web3d.org/specifications/x3d-3.3.xsd",
    "JSON schema":"http://www.web3d.org/specifications/x3d-3.3-JSONSchema.json",
    "head": {
        "meta": [
          {
            "@name":"title",
            "@content":"MaterialModulatorPrototypeExpanded.x3d"
          },
          {
            "@name":"description",
            "@content":"Mimic a Material node and modulate fields as an animation effect, providing example for expansion of a MaterialModulator ProtoInstance as regular nodes"
          },
          {
            "@name":"creator",
            "@content":"Don Brutzman"
          },
          {
            "@name":"created",
            "@content":"2 July 2016"
          },
          {
            "@name":"modified",
            "@content":"2 July 2016"
          },
          {
            "@name":"subject",
            "@content":"X3D prototype expansion requiring Script inputOutput fields"
          },
          {
            "@name":"Image",
            "@content":"MaterialModulator.png"
          },
          {
            "@name":"identifier",
            "@content":"http://X3dGraphics.com/examples/X3dForWebAuthors/Chapter14-Prototypes/MaterialModulatorPrototypeExpanded.x3d"
          },
          {
            "@name":"generator",
            "@content":"X3D-Edit 3.3, https://savage.nps.edu/X3D-Edit"
          },
          {
            "@name":"license",
            "@content":"../license.html"
          },
          {
            "@name":"translated",
            "@content":"02 July 2016"
          },
          {
            "@name":"generator",
            "@content":"X3dToJson.xslt, http://www.web3d.org/x3d/stylesheets/X3dToJson.html"
          },
          {
            "@name":"warning",
            "@content":"An experimental version of X3D JSON encoding is used for this scene.  Status online at http://www.web3d.org/wiki/index.php/X3D_JSON_Encoding"
          }
        ]
    },
    "Scene": {
        "-children":[
          { "Shape":
            {
              "-geometry":
                { "Sphere":
                  {
                  }
                },
              "-appearance":
                { "Appearance":
                  {
                    "-material":
                      { "Material":
                        {
                          "@DEF":"MaterialModulatorNodeExpanded1",
                          "-children":[
                            { "#comment":"no children nodes found, no follow-on nodes appear here"
                            }
                          ]
                        }
                      }
                  }
                }
            }
          },
          { "Switch":
            {
              "@DEF":"HideAdditionalPrototypeBodyNodesExpanded1",
              "@whichChoice":-1,
              "-children":[
                { "#comment":"Only first node (the node type) of the prototype is renderable, others follow here when Switch is first allowed"
                },
                { "Script":
                  {
                    "@DEF":"MaterialModulatorScriptExpanded1",
                    "field": [
                      {
                        "@name":"enabled",
                        "@accessType":"inputOutput",
                        "@type":"SFBool",
                        "@value":true
                      },
                      {
                        "@name":"diffuseColor",
                        "@accessType":"inputOutput",
                        "@type":"SFColor",
                        "@value":[0.8,0.8,0.8]
                      },
                      {
                        "@name":"newColor",
                        "@accessType":"outputOnly",
                        "@type":"SFColor"
                      },
                      {
                        "@name":"clockTrigger",
                        "@accessType":"inputOnly",
                        "@type":"SFTime"
                      }
                    ],
                    "#sourceText":[
"ecmascript:",
"function initialize ()",
"{",
"    newColor = diffuseColor; // start with correct color",
"}",
"",
"function clockTrigger (timeValue)",
"{",
"    if (!enabled) return;",
"    red   = newColor.r;",
"    green = newColor.g;",
"    blue  = newColor.b;",
"    ",
"    // note different modulation rates for each color component, % is modulus operator",
"    newColor = new SFColor ((red + 0.02) % 1, (green + 0.03) % 1, (blue + 0.04) % 1);   ",
"    Browser.print ('diffuseColor=(' + red +',' + green + ',' + blue + ') newColor=' + newColor.toString() + '\\');",
"}"
]
                  }
                },
                { "#comment":"Clock tickles Script to wake up and compute a new value"
                },
                { "ROUTE":
                  {
                    "@fromField":"newColor",
                    "@fromNode":"MaterialModulatorScriptExpanded1",
                    "@toField":"diffuseColor",
                    "@toNode":"MaterialModulatorNodeExpanded1"
                  }
                },
                { "TimeSensor":
                  {
                    "@DEF":"ModulationClockExpanded1",
                    "@cycleInterval":0.1,
                    "@loop":true
                  }
                },
                { "ROUTE":
                  {
                    "@fromField":"cycleTime",
                    "@fromNode":"ModulationClockExpanded1",
                    "@toField":"clockTrigger",
                    "@toNode":"MaterialModulatorScriptExpanded1"
                  }
                }
              ]
            }
          }
        ]
    }
  }
}
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://web3d.org/pipermail/x3d-public_web3d.org/attachments/20160702/f6a816c9/attachment-0001.html>


More information about the x3d-public mailing list