Skip to content

Commit

Permalink
fix(x3d-serializer): added specular highlights and conversion of shap…
Browse files Browse the repository at this point in the history
…e names
  • Loading branch information
andreasplesch authored May 14, 2023
1 parent 76b7369 commit ba26567
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 18 deletions.
5 changes: 4 additions & 1 deletion packages/io/x3d-serializer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ The serialization of the following geometries are possible.
- serialization of 2D geometries (geom2) to X3D Polyline2D
- serialization of 2D paths (path2) to X3D Polyline2D

Material (color) is added to X3D shapes when found on the geometry.
The id attribute is used as the DEF name for the generated X3D shape when found on a geometry.
Material (color) is added to X3D shapes when found on a geometry.

All shapes are wrapped in a rotation transform to align the positive Z direction of JSCAD with the vertical up direction in X3D.

## Table of Contents

Expand Down
35 changes: 31 additions & 4 deletions packages/io/x3d-serializer/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ const stringify = require('onml/lib/stringify')
// https://x3dgraphics.com/examples/X3dForWebAuthors/Chapter13GeometryTrianglesQuadrilaterals/

const mimeType = 'model/x3d+xml'
const defNames = new Map()

/**
* Serialize the give objects to X3D elements (XML).
* @param {Object} options - options for serialization, REQUIRED
* @param {Array} [options.color=[0,0,1,1]] - default color for objects
* @param {Number} [options.shininess=8/256] - x3d shininess for specular highlights
* @param {Boolean} [options.smooth=false] - use averaged vertex normals
* @param {Number} [options.decimals=1000] - multiplier before rounding to limit precision
* @param {Boolean} [options.metadata=true] - add metadata to 3MF contents, such at CreationDate
Expand All @@ -63,6 +65,7 @@ const mimeType = 'model/x3d+xml'
const serialize = (options, ...objects) => {
const defaults = {
color: [0, 0, 1, 1.0], // default colorRGBA specification
shininess: 8 / 256,
smooth: false,
decimals: 1000,
metadata: true,
Expand Down Expand Up @@ -141,7 +144,7 @@ const convertObjects = (objects, options) => {
const convertPath2 = (object, options) => {
const points = path2.toPoints(object).slice()
if (points.length > 1 && object.isClosed) points.push(points[0])
const shape = ['Shape', {}, convertPolyline2D(poly2.create(points), options)]
const shape = ['Shape', shapeAttributes(object), convertPolyline2D(poly2.create(points), options)]
if (object.color) {
shape.push(convertAppearance(object, 'emissiveColor', options))
}
Expand All @@ -156,7 +159,7 @@ const convertGeom2 = (object, options) => {
const group = ['Group', {}]
outlines.forEach((outline) => {
if (outline.length > 1) outline.push(outline[0]) // close the outline for conversion
const shape = ['Shape', {}, convertPolyline2D(poly2.create(outline), options)]
const shape = ['Shape', shapeAttributes(object), convertPolyline2D(poly2.create(outline), options)]
if (object.color) {
shape.push(convertAppearance(object, 'emissiveColor', options))
}
Expand All @@ -165,6 +168,24 @@ const convertGeom2 = (object, options) => {
return group
}

/*
* generate attributes for Shape node
*/

const shapeAttributes = (object, attributes = {}) => {
if (object.id) {
Object.assign(attributes, { DEF: checkDefName(object.id) })
}
return attributes
}

const checkDefName = (defName) => {
const count = defNames.get(defName) || 0
defNames.set(defName, count + 1)
if (count > 0) console.warn(`Warning: object.id set as DEF but not unique. ${defName} set ${count + 1} times.`)
return defName
}

/*
* Convert the given object (poly2) to X3D source
*/
Expand All @@ -180,14 +201,20 @@ const convertAppearance = (object, colorField, options) => {
const colorRGB = object.color.slice(0, 3)
const color = colorRGB.join(' ')
const transparency = roundToDecimals(1.0 - object.color[3], options)
return ['Appearance', ['Material', { [colorField]: color, transparency }]]
const materialFields = { [colorField]: color, transparency }
if (colorField === 'diffuseColor') {
Object.assign(
materialFields,
{ specularColor: '0.2 0.2 0.2', shininess: options.shininess })
}
return ['Appearance', ['Material', materialFields]]
}

/*
* Convert the given object (geom3) to X3D source
*/
const convertGeom3 = (object, options) => {
const shape = ['Shape', {}, convertMesh(object, options)]
const shape = ['Shape', shapeAttributes(object), convertMesh(object, options)]
let appearance = ['Appearance', {}, ['Material']]
if (object.color) {
appearance = convertAppearance(object, 'diffuseColor', options)
Expand Down
9 changes: 4 additions & 5 deletions packages/io/x3d-serializer/tests/geom2ToX3D.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {

const shape2 = primitives.rectangle()

results = serialize({metadata: false}, shape2)
results = serialize({ metadata: false }, shape2)
t.is(results.length, 1)

obs = results[0]
Expand All @@ -41,7 +41,7 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {

const shape3 = colors.colorize([0, 0, 0], shape2)

results = serialize({metadata: false}, shape3)
results = serialize({ metadata: false }, shape3)
t.is(results.length, 1)

obs = results[0]
Expand All @@ -61,8 +61,7 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {
t.is(countOf('diffuseColor', obs), 0)
t.is(countOf('emissiveColor', obs), 1)


results = serialize({metadata: false}, shape2, shape3)
results = serialize({ metadata: false }, shape2, shape3)
t.is(results.length, 1)

obs = results[0]
Expand All @@ -80,6 +79,6 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {
t.is(countOf('Appearance', obs), 2)
t.is(countOf('Material', obs), 1)
t.is(countOf('diffuseColor', obs), 0)
t.is(countOf('specularColor', obs), 0)
t.is(countOf('emissiveColor', obs), 1)
})

11 changes: 7 additions & 4 deletions packages/io/x3d-serializer/tests/geom3ToX3D.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test('serialize 3D geometry to X3D IndexedTriangleSet', (t) => {

const geom2 = primitives.cube()

results = serializer.serialize({metadata: false}, geom2)
results = serializer.serialize({ metadata: false }, geom2)
t.is(results.length, 1)

obs = results[0]
Expand All @@ -36,13 +36,15 @@ test('serialize 3D geometry to X3D IndexedTriangleSet', (t) => {
t.is(countOf('Scene', obs), 2)
t.is(countOf('Transform', obs), 2)
t.is(countOf('Shape', obs), 2)
t.is(countOf('DEF', obs), 0)
t.is(countOf('IndexedTriangleSet', obs), 2)
t.is(countOf('Coordinate', obs), 1)
t.is(countOf('Color', obs), 1)

const geom3 = colors.colorize([0.5, 1, 0.5, 1.0], transforms.center({ relativeTo: [5, 5, 5] }, primitives.cube()))
geom2.id = geom3.id = 'g23'

results = serializer.serialize({metadata: false}, geom2, geom3)
results = serializer.serialize({ metadata: false }, geom2, geom3)
t.is(results.length, 1)

obs = results[0]
Expand All @@ -54,14 +56,15 @@ test('serialize 3D geometry to X3D IndexedTriangleSet', (t) => {
t.is(countOf('Created by JSCAD', obs), 1)
t.is(countOf('Scene', obs), 2)
t.is(countOf('Shape', obs), 4)
t.is(countOf('DEF', obs), 2)
t.is(countOf('IndexedTriangleSet', obs), 4)
t.is(countOf('Coordinate', obs), 2)
// for color
t.is(countOf('<Color', obs), 1)
t.is(countOf('Appearance', obs), 4)
// for RGB
t.is(countOf('diffuseColor="0.5 1 0.5"', obs), 1)
t.is(countOf('specularColor', obs), 1)
// for facets
t.is(countOf('normalPerVertex="false"', obs), 2)

})
})
6 changes: 2 additions & 4 deletions packages/io/x3d-serializer/tests/path2ToX3D.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test('serialize 2D path to X3D Polyline2D', (t) => {

const path2 = primitives.arc({ center: [5, 5], endAngle: 45, segments: 16 })

results = serialize({metadata: false}, path2)
results = serialize({ metadata: false }, path2)
t.is(results.length, 1)

obs = results[0]
Expand All @@ -35,7 +35,7 @@ test('serialize 2D path to X3D Polyline2D', (t) => {

const path3 = colors.colorize([0, 0, 0], path2)

results = serialize({metadata: false}, path2, path3)
results = serialize({ metadata: false }, path2, path3)
t.is(results.length, 1)

obs = results[0]
Expand All @@ -50,6 +50,4 @@ test('serialize 2D path to X3D Polyline2D', (t) => {
t.is(countOf('Material', obs), 1)
t.is(countOf('diffuseColor', obs), 0)
t.is(countOf('emissiveColor', obs), 1)

})

0 comments on commit ba26567

Please sign in to comment.