From 92be4178459eeedc121b12ccc97e5edef5919f83 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 12 Feb 2024 13:10:49 -0800 Subject: [PATCH] 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. --- README.md | 12 ++++++++++++ package.json5 | 8 ++++---- pnpm-lock.yaml | 50 ++++++++++++++++++++++++------------------------- src/index.civet | 25 +++++++++++++++++-------- 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index f3e61fc..02921df 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/package.json5 b/package.json5 index 47bc539..87aa8ad 100644 --- a/package.json5 +++ b/package.json5 @@ -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', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ae457f..a24b551 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/index.civet b/src/index.civet index 6848624..d256113 100644 --- a/src/index.civet +++ b/src/index.civet @@ -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 -- 2.34.1