feat: Set faces one or two-sided, conservatively

Set VRML97 translation of IndexedFaceSets to specify `solid false`
  unless the faces were explicitly indicated to have CLOCKWISE or
  COUNTERCLOCKWISE vertex ordering. This is conservative, but otherwise
  shapes may be "see-through" in undesirable ways, if the original
  VRML 1 vertices are not ordered consistently with the VRML97 default.

  Also fixes a typo/bug in picking up numerical values of parameters,
  and ensures that fields in the output VRML97 are always separated by
  at least a space, comma, or parenthesis.
This commit is contained in:
Glen Whitney 2024-02-12 13:10:49 -08:00
parent bb0ef18903
commit 92be417845
4 changed files with 58 additions and 37 deletions

View File

@ -62,3 +62,15 @@ Currently this package exports just two functions:
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.
## Conversion notes
One sort of geometry common to VRML 1 and VRML97 is the IndexedFaceSet. These
often used to render solids. Indeed, the default in VRML97 is to assume they
do represent a solid, with the normals pointing outward. Unusual visual effects
ensue if the normals are not properly directed (basically, you see through the
"front" of the solid and see the "backs" of the faces on the opposite side).
As a result, unless the input VRML 1 explicitly includes an explicit
vertexOrdering of CLOCKWISE or COUNTERCLOCKWISE, the default solid treatment
will be turned off in the VRML97 output, meaning that all faces will be
rendered opaque in both directions.

View File

@ -1,6 +1,6 @@
{
name: 'vrml1to97',
version: '0.2.2',
version: '0.2.3',
description: 'JavaScript converter from VRML 1 to VRML97',
scripts: {
test: 'echo "Error: no test specified" && exit 1',
@ -40,11 +40,11 @@
},
type: 'module',
devDependencies: {
'@danielx/civet': '^0.6.38',
'@types/moo': '^0.5.6',
'@danielx/civet': '^0.6.71',
'@types/moo': '^0.5.9',
'http-server': '^14.1.1',
json5: '^2.2.3',
typescript: '^5.2.2',
typescript: '^5.3.3',
},
dependencies: {
moo: '^0.5.2',

View File

@ -11,11 +11,11 @@ dependencies:
devDependencies:
'@danielx/civet':
specifier: ^0.6.38
version: 0.6.38(typescript@5.2.2)
specifier: ^0.6.71
version: 0.6.71(typescript@5.3.3)
'@types/moo':
specifier: ^0.5.6
version: 0.5.6
specifier: ^0.5.9
version: 0.5.9
http-server:
specifier: ^14.1.1
version: 14.1.1
@ -23,8 +23,8 @@ devDependencies:
specifier: ^2.2.3
version: 2.2.3
typescript:
specifier: ^5.2.2
version: 5.2.2
specifier: ^5.3.3
version: 5.3.3
packages:
@ -35,8 +35,8 @@ packages:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@danielx/civet@0.6.38(typescript@5.2.2):
resolution: {integrity: sha512-R63YGIfvV4DQianNPUfMfBX60ozlv5htnRXI1wK3Pg6+d4NZ2V3RifFRH0NkmXXoFkRcULzJ+9BzVeI2/yX+yA==}
/@danielx/civet@0.6.71(typescript@5.3.3):
resolution: {integrity: sha512-piOoHtJARe6YqRiXN02Ryb+nLU9JwU8TQLknvPwmlaNm6krdKN+X9dM+C9D4LRoAnAySC2wVshR0wf7YDIUV1Q==}
engines: {node: '>=19 || ^18.6.0 || ^16.17.0'}
hasBin: true
peerDependencies:
@ -44,8 +44,8 @@ packages:
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@typescript/vfs': 1.5.0
typescript: 5.2.2
unplugin: 1.4.0
typescript: 5.3.3
unplugin: 1.7.1
transitivePeerDependencies:
- supports-color
dev: true
@ -66,8 +66,8 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@types/moo@0.5.6:
resolution: {integrity: sha512-Q60hZhulhl2Ox4LjbJvhH+HzsKrwzLPjEB8dZw0fK1MH2HyOLe6LDou68yTfsWasxGv7DPZe5VNM5vgpzOa2nw==}
/@types/moo@0.5.9:
resolution: {integrity: sha512-ZsFVecFi66jGQ6L41TonEaBhsIVeVftTz6iQKWTctzacHhzYHWvv9S0IyAJi4BhN7vb9qCQ3+kpStP2vbZqmDg==}
dev: true
/@typescript/vfs@1.5.0:
@ -78,8 +78,8 @@ packages:
- supports-color
dev: true
/acorn@8.10.0:
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
/acorn@8.11.3:
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
@ -139,8 +139,8 @@ packages:
supports-color: 7.2.0
dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
/chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
@ -462,8 +462,8 @@ packages:
is-number: 7.0.0
dev: true
/typescript@5.2.2:
resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
/typescript@5.3.3:
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
@ -475,13 +475,13 @@ packages:
qs: 6.11.2
dev: true
/unplugin@1.4.0:
resolution: {integrity: sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==}
/unplugin@1.7.1:
resolution: {integrity: sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==}
dependencies:
acorn: 8.10.0
chokidar: 3.5.3
acorn: 8.11.3
chokidar: 3.6.0
webpack-sources: 3.2.3
webpack-virtual-modules: 0.5.0
webpack-virtual-modules: 0.6.1
dev: true
/url-join@4.0.1:
@ -493,8 +493,8 @@ packages:
engines: {node: '>=10.13.0'}
dev: true
/webpack-virtual-modules@0.5.0:
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
/webpack-virtual-modules@0.6.1:
resolution: {integrity: sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==}
dev: true
/whatwg-encoding@2.0.0:

View File

@ -67,7 +67,7 @@ function findNumbersAtTopLevel(
{type: 'cbrace'} depth -= 1
{type: 'word'} if depth === 1 and tok.value in fields
selector = tok.value
{tye: 'number'} if selecting then fields[selecting] = tok.value
{type: 'number'} if selecting then fields[selecting] = tok.value
{type: 'obrace'} depth += 1
selecting = selector
@ -105,7 +105,9 @@ function parse(stream: Lexer, tree: Tree = {}): Tree
${renderList newKids} ] }\n`, tree
'ShapeHints'
subTree := parse stream
if 'vertexOrdering' in subTree then tree.ccw = ['true']
if 'vertexOrdering' in subTree
tree.ccw = [
subTree.vertexOrdering[0] is 'ccw' ? 'true' : 'false']
if 'creaseAngle' in subTree
tree.creaseAngle = subTree.creaseAngle
'Coordinate3'
@ -153,8 +155,9 @@ function parse(stream: Lexer, tree: Tree = {}): Tree
...tree.TextureCoordinate, " }\n"
if isFaces and 'creaseAngle' in tree
params.push `creaseAngle ${tree.creaseAngle[0]}`
if isFaces and 'ccw' in tree
params.push `ccw true`
if isFaces
if 'ccw' in tree then params.push `ccw ${tree.ccw[0]}`
else params.push 'solid false'
params.push ...contents
addShape held.value, params, tree
/Light$/
@ -166,6 +169,9 @@ function parse(stream: Lexer, tree: Tree = {}): Tree
{ht: 'word', hv: 'vertexOrdering', nt: 'word', nv: 'COUNTERCLOCKWISE'}
tree.vertexOrdering = ['ccw']
held = filtered stream
{ht: 'word', hv: 'vertexOrdering', nt: 'word', nv: 'CLOCKWISE'}
tree.vertexOrdering = ['cw']
held = filtered stream
{ht: 'word', hv: 'creaseAngle', nt: 'number'}
tree.creaseAngle = [ next.value ]
held = filtered stream
@ -236,13 +242,13 @@ function render(t: string | Tree): string
if 'type' in t and 'value' in t
val := renderList t.value
return switch t.type[0]
/string|number|word/ ` ${val}`
/string|number|word/ val
/comma|oparen|cparen/ val
/obrace|cbrace|obracket|cbracket/ ` ${val}\n`
/obrace|cbrace|obracket|cbracket/ `${val}\n`
else `\nUNKNOWN TYPE ${t.type}\n\n`
result .= ''
for prop in t
result += `${prop} {\n ${renderList t[prop]} }\n`
result += `${prop} {\n ${renderList t[prop]} }\n`
return result
function renderList(l: (string | Tree)[]): string
@ -251,7 +257,10 @@ function renderList(l: (string | Tree)[]): string
commaNewlineOnce .= false
commaTriggersNewline .= false
for each item of l
return.value += render item
next := render item
if return.value and not ',()'.includes next[0]
return.value += ' '
return.value += next
switch item
{type: ['word'], value: ['point']}
commaTriggersNewline = true