feat: Handle additional syntax (#12)

* Converts all Lighting nodes into the output
  * Translates Title Info and SceneInfo Info into a WorldInfo node
  * Translates Viewer Info into a NavigationInfo node
  * Captures the creaseAngle parameter
  * Also, this commit documents the API (such as it is)
  Resolves #9.
  Resolves #10.

Reviewed-on: #12
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
Glen Whitney 2023-09-11 07:15:17 +00:00 committed by Glen Whitney
parent 48925d05d9
commit 6752ab1348
3 changed files with 85 additions and 13 deletions

View File

@ -33,3 +33,26 @@ or from the command line via node
```shell ```shell
npx vrml1to97 < old.wrl > shinynew.wrl npx vrml1to97 < old.wrl > shinynew.wrl
``` ```
## API
Currently this package exports just two functions:
* convert(vrml1: string): string
The main function, takes in VRML 1 syntax (note that it does not
actually check that its input is VRML 1, so its behavior is undefined
if given anything but valid VRML 1 syntax). Returns VRML 97 syntax for
the same scene, as nearly as it can translate. Note that not all of
VRML 1 is recognized, and some of the translations may be somewhat
approximate.
* tree97(vrml1: string): Tree
Takes the same input as convert, but rathre than returning a string, it
returns a very rough syntax tree for the converted VRML97 scene. The
returned Tree data structure is an object whose keys are property names
and whose values are lists of either strings or sub-Trees. Note however
that this syntax tree does not represent a complete parse; since VRML 1
and VRML97 are close, whole blocks of syntax are left as strings rather
than broken into node names and property values. If there is a need to
generate XML syntax output, for example, the code would need to be
modified to break the tree down further to be ready for conversion to XML.

View File

@ -1,6 +1,6 @@
{ {
name: 'vrml1to97', name: 'vrml1to97',
version: '0.1.3', version: '0.2.0',
description: 'JavaScript converter from VRML 1 to VRML97', description: 'JavaScript converter from VRML 1 to VRML97',
scripts: { scripts: {
test: 'echo "Error: no test specified" && exit 1', test: 'echo "Error: no test specified" && exit 1',

View File

@ -74,6 +74,18 @@ function findNumbersAtTopLevel(
function addChild(child: string | Tree, tree: Tree): void function addChild(child: string | Tree, tree: Tree): void
(tree.children ??= []).push child (tree.children ??= []).push child
type WorldInfoNode = {WorldInfo: (string|Tree)[]} | undefined
function addWorldParameter(name: string, value: string, tree: Tree): void
children := tree.children ??= []
world .= (children.find (x) =>
if x <? 'object'
'WorldInfo' in x
else false) as WorldInfoNode
unless world
world = WorldInfo: []
children.push world
world.WorldInfo.push(` ${name}`, ` ${value}`)
function parse(stream: Lexer, tree: Tree = {}): Tree function parse(stream: Lexer, tree: Tree = {}): Tree
held .= filtered stream // for lookahead held .= filtered stream // for lookahead
@ -93,7 +105,9 @@ function parse(stream: Lexer, tree: Tree = {}): Tree
${renderList newKids} ] }\n`, tree ${renderList newKids} ] }\n`, tree
'ShapeHints' 'ShapeHints'
subTree := parse stream subTree := parse stream
if 'vertexOrdering' in subTree then tree.ccw = ['1.0'] if 'vertexOrdering' in subTree then tree.ccw = ['true']
if 'creaseAngle' in subTree
tree.creaseAngle = subTree.creaseAngle
'Coordinate3' 'Coordinate3'
tree.Coordinate = toksUntilClose stream tree.Coordinate = toksUntilClose stream
'Normal' 'Normal'
@ -124,7 +138,7 @@ function parse(stream: Lexer, tree: Tree = {}): Tree
dims := radius: '' dims := radius: ''
findNumbersAtTopLevel stream, dims findNumbersAtTopLevel stream, dims
addShape 'Sphere', [`radius ${dims.radius}`], tree addShape 'Sphere', [`radius ${dims.radius}`], tree
/IndexedLineSet|PointSet/ // ignored /IndexedLineSet|PointSet/ // ignored, but why? They are in VRML97
findNumbersAtTopLevel stream, {} findNumbersAtTopLevel stream, {}
'IndexedFaceSet' 'IndexedFaceSet'
contents := translatedToksUntilClose stream contents := translatedToksUntilClose stream
@ -138,27 +152,62 @@ function parse(stream: Lexer, tree: Tree = {}): Tree
if 'TextureCoordinate' in tree if 'TextureCoordinate' in tree
params.push "texCoord TextureCoordinate {\n", params.push "texCoord TextureCoordinate {\n",
...tree.TextureCoordinate, " }\n" ...tree.TextureCoordinate, " }\n"
if 'creaseAngle' in tree
params.push `creaseAngle ${tree.creaseAngle[0]}`
if 'ccw' in tree
params.push `ccw true`
params.push ...contents params.push ...contents
addShape 'IndexedFaceSet', params, tree addShape 'IndexedFaceSet', params, tree
/Light$/
contents := toksUntilClose stream
addChild {[held.value]: contents}, tree
else else
parse stream // discard the subgroup parse stream // discard the subgroup
held = filtered stream held = filtered stream
{ht: 'word', hv: 'vertexOrdering', nt: 'word', nv: 'COUNTERCLOCKWISE'} {ht: 'word', hv: 'vertexOrdering', nt: 'word', nv: 'COUNTERCLOCKWISE'}
tree.vertexOrdering = ['ccw'] tree.vertexOrdering = ['ccw']
held = filtered stream held = filtered stream
{ht: 'word', hv: 'BackgroundColor', nt: 'word', nv: 'Info'} {ht: 'word', hv: 'creaseAngle', nt: 'number'}
tree.creaseAngle = [ next.value ]
held = filtered stream
{ht: 'word', nt: 'word', nv: 'Info'}
opener := filtered stream opener := filtered stream
if opener and opener.type === 'obrace' if opener and opener.type === 'obrace'
contents := toksUntilClose stream contents := toksUntilClose stream
// Find the first string token and assume it is the color switch held.value
stok := contents.find .type[0] === 'string' 'BackgroundColor'
colorString := stok?.value[0] // Find the first string token and assume it is the color
if colorString and typeof colorString === 'string' stok := contents.find .type[0] === 'string'
color := colorString.replace /^"|"$/g, '' colorString := stok?.value[0]
addChild `Background { if colorString and typeof colorString === 'string'
groundColor [ ${color} ] color := colorString.replace /^"|"$/g, ''
skyColor [ ${color} ] addChild `Background {
}\n`, tree groundColor [ ${color} ]
skyColor [ ${color} ]
}\n`, tree
'Title'
// Find the first string token and assume it is the title
stok := contents.find .type[0] === 'string'
titleString := stok?.value[0]
if titleString and typeof titleString === 'string'
addWorldParameter 'title', titleString, tree
'SceneInfo'
// Filter all the strings and add them as info
info := contents
|> .filter .type[0] === 'string'
|> .map .value[0]
|> .join ' '
addWorldParameter 'info', `[ ${info} ]`, tree
'Viewer'
// Filter all of the strings, and translate them into
// current viewer names
tr: Record<string, string> := {'"examiner"': '"EXAMINE"'}
viewers := contents
|> .filter .type[0] === 'string'
|> .map .value[0] as string
|> .map((x) => x in tr ? tr[x] : x.toUpperCase())
|> .join ' '
addChild `NavigationInfo { type [ ${viewers} ] }\n`, tree
held = filtered stream held = filtered stream
else held = opener else held = opener
else else