From 1ecfa50af4a8aa568c44301f68619a07c4b709bf Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 7 Oct 2021 10:32:03 -0230 Subject: [PATCH 1/5] Add gradient support The model now supports having SVG gradients applied to individual chunks. A new `gradients` property has been added to the JSON model that contains a map of gradient IDs to definitions. A gradient is applied to a chunk by setting the optional chunk property `gradient` to a gradient ID. Both linear and radial gradients are supported, along with most SVG attributes. Any attribute with a limited set of options is validated to ensure it is set correctly, but the attributes that take lengths or colors simply ensure the value is a string (validating them property would be challenging). Validation has been added to throw an error if any unsupported attributes are present. The "gradient" demo has been updated to use this new gradient support. Now that example sets the gradient on each chunk rather than applying an overlay and masking it to match the model's shape. This greatly simplifies the example. Also the example-specific model files have been moved into the `example` directory. A new model was needed for the updated gradient example, and this seemed like a better place for it to go, so that the top-level of the repository doesn't get too cluttered. --- docs/beta/bundle.js | 203 ++++++- docs/distort/bundle.js | 193 ++++++- docs/gradient/bundle.js | 700 +++++++++++++++++++++---- docs/normal/bundle.js | 199 ++++++- docs/recolor/bundle.js | 193 ++++++- beta-fox.json => example/beta-fox.json | 0 example/gradient-fox.json | 347 ++++++++++++ example/src/beta.js | 2 +- example/src/gradient.js | 94 +--- index.js | 6 +- util.js | 193 ++++++- 11 files changed, 1880 insertions(+), 250 deletions(-) rename beta-fox.json => example/beta-fox.json (100%) create mode 100644 example/gradient-fox.json diff --git a/docs/beta/bundle.js b/docs/beta/bundle.js index ba2f49fc..ec04f3ff 100644 --- a/docs/beta/bundle.js +++ b/docs/beta/bundle.js @@ -341,7 +341,7 @@ module.exports={ const copy = require('copy-to-clipboard'); const createViewer = require('../..'); const { svgElementToSvgImageContent } = require('../../util'); -const meshJson = require('../../beta-fox.json'); +const meshJson = require('../beta-fox.json'); document.addEventListener('keypress', function (event) { if (event.keyCode === 99) { @@ -360,7 +360,7 @@ createViewer({ meshJson, }); -},{"../..":4,"../../beta-fox.json":1,"../../util":14,"copy-to-clipboard":5}],3:[function(require,module,exports){ +},{"../..":4,"../../util":14,"../beta-fox.json":1,"copy-to-clipboard":5}],3:[function(require,module,exports){ module.exports={ "positions": [ [111.024597, 52.604599, 46.225899], @@ -693,6 +693,7 @@ const { createModelRenderer, createNode, setAttribute, + setGradientDefinitions, } = require('./util'); module.exports = createLogo; @@ -700,13 +701,16 @@ module.exports = createLogo; function createLogo(options = {}) { const cameraDistance = options.cameraDistance || 400; const { height, width } = calculateSizingOptions(options); + const meshJson = options.meshJson || foxJson; const container = createNode('svg'); setAttribute(container, 'width', `${width}px`); setAttribute(container, 'height', `${height}px`); document.body.appendChild(container); - const modelObj = loadModelFromJson(options.meshJson || foxJson); + setGradientDefinitions(container, meshJson.gradients); + + const modelObj = loadModelFromJson(meshJson); const renderFox = createModelRenderer(container, cameraDistance, modelObj); const renderScene = (lookCurrent, slowDrift) => { const rect = container.getBoundingClientRect(); @@ -1202,6 +1206,7 @@ module.exports = { createFaceUpdater, createNode, setAttribute, + setGradientDefinitions, svgElementToSvgImageContent, Polygon, }; @@ -1396,7 +1401,9 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygonsByChunk = modelJson.chunks.map((chunk) => { const { faces } = chunk; return faces.map((face) => { - const svgPolygon = createSvgPolygon(chunk); + const svgPolygon = createSvgPolygon(chunk, { + gradients: modelJson.gradients, + }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); return polygon; @@ -1405,11 +1412,23 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk) { - const color = `rgb(${chunk.color})`; +function createStandardModelPolygon(chunk, { gradients = {} }) { const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'fill', color); - setAttribute(svgPolygon, 'stroke', color); + + if (chunk.gradient) { + const gradientId = chunk.gradient; + if (!gradients[gradientId]) { + throw new Error(`Gradient ID not found: '${gradientId}'`); + } + + setAttribute(svgPolygon, 'fill', `url('#${gradientId}')`); + setAttribute(svgPolygon, 'stroke', `url('#${gradientId}')`); + } else { + const fill = `rgb(${chunk.color})`; + setAttribute(svgPolygon, 'fill', fill); + setAttribute(svgPolygon, 'stroke', fill); + } + setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); return svgPolygon; } @@ -1555,10 +1574,10 @@ function createFaceUpdater(container, polygons, transformed) { toDraw.push(poly); } toDraw.sort(compareZ); - container.innerHTML = ''; - for (i = 0; i < toDraw.length; ++i) { - container.appendChild(toDraw[i].svg); - } + + const newPolygons = toDraw.map((poly) => poly.svg); + const defs = container.getElementsByTagName('defs'); + container.replaceChildren(...defs, ...newPolygons); }; } @@ -1602,4 +1621,164 @@ function Polygon(svg, indices) { this.zIndex = 0; } +function setGradientDefinitions(container, gradients) { + if (!gradients || Object.keys(gradients).length === 0) { + return; + } + + const defsContainer = createNode('defs'); + + const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; + const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; + const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const allLinearAttributes = [ + ...linearCoordinateAttributes, + ...commonAttributes, + ]; + const allRadialAttributes = [ + ...radialCoordinateAttributes, + ...commonAttributes, + ]; + + for (const [gradientId, gradientDefinition] of Object.entries(gradients)) { + let gradient; + if (gradientDefinition.type === 'linear') { + gradient = createNode('linearGradient'); + + const unsupportedLinearAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allLinearAttributes.includes(attribute), + ); + if (unsupportedLinearAttribute) { + throw new Error( + `Unsupported linear gradient attribute: '${unsupportedLinearAttribute}'`, + ); + } else if ( + linearCoordinateAttributes.some( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ) + ) { + const missingAttributes = linearCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] === undefined, + ); + if (missingAttributes.length > 0) { + throw new Error( + `Missing coordinate attributes: '${missingAttributes.join(', ')}'`, + ); + } + + for (const attribute of linearCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else if (gradientDefinition.type === 'radial') { + gradient = createNode('radialGradient'); + + const presentCoordinateAttributes = radialCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ); + const unsupportedRadialAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allRadialAttributes.includes(attribute), + ); + if (unsupportedRadialAttribute) { + throw new Error( + `Unsupported radial gradient attribute: '${unsupportedRadialAttribute}'`, + ); + } else if (presentCoordinateAttributes.length > 0) { + for (const attribute of presentCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else { + throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + } + + // Set common attributes + setAttribute(gradient, 'id', gradientId); + if (gradientDefinition.gradientUnits !== undefined) { + if ( + !['userSpaceOnUse', 'objectBoundingBox'].includes( + gradientDefinition.gradientUnits, + ) + ) { + throw new Error( + `Unrecognized value for 'gradientUnits' attribute: '${gradientDefinition.gradientUnits}'`, + ); + } + setAttribute(gradient, 'gradientUnits', gradientDefinition.gradientUnits); + } + + if (gradientDefinition.gradientTransform !== undefined) { + if (typeof gradientDefinition.gradientTransform !== 'string') { + throw new Error( + `Type of 'gradientTransform' option expected to be 'string'. Instead received type '${typeof gradientDefinition.gradientTransform}'`, + ); + } + + setAttribute( + gradient, + 'gradientTransform', + gradientDefinition.gradientTransform, + ); + } + + if (gradientDefinition.spreadMethod !== undefined) { + if ( + !['pad', 'reflect', 'repeat'].includes(gradientDefinition.spreadMethod) + ) { + throw new Error( + `Unrecognized value for 'spreadMethod' attribute: '${gradientDefinition.spreadMethod}'`, + ); + } + setAttribute(gradient, 'spreadMethod', gradientDefinition.spreadMethod); + } + + if (gradientDefinition.stops !== undefined) { + if (!Array.isArray(gradientDefinition.stops)) { + throw new Error(`The 'stop' attribute must be an array`); + } + + for (const stopDefinition of gradientDefinition.stops) { + if (typeof stopDefinition !== 'object') { + throw new Error( + `Each entry in the 'stop' attribute must be an object. Instead received type '${typeof stopDefinition}'`, + ); + } + const stop = createNode('stop'); + + if (stopDefinition.offset !== undefined) { + setAttribute(stop, 'offset', stopDefinition.offset); + } + + if (stopDefinition['stop-color'] !== undefined) { + setAttribute(stop, 'stop-color', stopDefinition['stop-color']); + } + + if (stopDefinition['stop-opacity'] !== undefined) { + setAttribute(stop, 'stop-opacity', stopDefinition['stop-opacity']); + } + + gradient.appendChild(stop); + } + } + + defsContainer.appendChild(gradient); + } + + container.appendChild(defsContainer); +} + },{"gl-mat4/invert":7,"gl-mat4/lookAt":8,"gl-mat4/multiply":9,"gl-mat4/perspective":10,"gl-mat4/rotate":11,"gl-vec3/transformMat4":12}]},{},[2]); diff --git a/docs/distort/bundle.js b/docs/distort/bundle.js index 00b214f3..8081b744 100644 --- a/docs/distort/bundle.js +++ b/docs/distort/bundle.js @@ -823,6 +823,7 @@ module.exports = { createFaceUpdater, createNode, setAttribute, + setGradientDefinitions, svgElementToSvgImageContent, Polygon, }; @@ -1017,7 +1018,9 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygonsByChunk = modelJson.chunks.map((chunk) => { const { faces } = chunk; return faces.map((face) => { - const svgPolygon = createSvgPolygon(chunk); + const svgPolygon = createSvgPolygon(chunk, { + gradients: modelJson.gradients, + }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); return polygon; @@ -1026,11 +1029,23 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk) { - const color = `rgb(${chunk.color})`; +function createStandardModelPolygon(chunk, { gradients = {} }) { const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'fill', color); - setAttribute(svgPolygon, 'stroke', color); + + if (chunk.gradient) { + const gradientId = chunk.gradient; + if (!gradients[gradientId]) { + throw new Error(`Gradient ID not found: '${gradientId}'`); + } + + setAttribute(svgPolygon, 'fill', `url('#${gradientId}')`); + setAttribute(svgPolygon, 'stroke', `url('#${gradientId}')`); + } else { + const fill = `rgb(${chunk.color})`; + setAttribute(svgPolygon, 'fill', fill); + setAttribute(svgPolygon, 'stroke', fill); + } + setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); return svgPolygon; } @@ -1176,10 +1191,10 @@ function createFaceUpdater(container, polygons, transformed) { toDraw.push(poly); } toDraw.sort(compareZ); - container.innerHTML = ''; - for (i = 0; i < toDraw.length; ++i) { - container.appendChild(toDraw[i].svg); - } + + const newPolygons = toDraw.map((poly) => poly.svg); + const defs = container.getElementsByTagName('defs'); + container.replaceChildren(...defs, ...newPolygons); }; } @@ -1223,4 +1238,164 @@ function Polygon(svg, indices) { this.zIndex = 0; } +function setGradientDefinitions(container, gradients) { + if (!gradients || Object.keys(gradients).length === 0) { + return; + } + + const defsContainer = createNode('defs'); + + const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; + const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; + const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const allLinearAttributes = [ + ...linearCoordinateAttributes, + ...commonAttributes, + ]; + const allRadialAttributes = [ + ...radialCoordinateAttributes, + ...commonAttributes, + ]; + + for (const [gradientId, gradientDefinition] of Object.entries(gradients)) { + let gradient; + if (gradientDefinition.type === 'linear') { + gradient = createNode('linearGradient'); + + const unsupportedLinearAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allLinearAttributes.includes(attribute), + ); + if (unsupportedLinearAttribute) { + throw new Error( + `Unsupported linear gradient attribute: '${unsupportedLinearAttribute}'`, + ); + } else if ( + linearCoordinateAttributes.some( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ) + ) { + const missingAttributes = linearCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] === undefined, + ); + if (missingAttributes.length > 0) { + throw new Error( + `Missing coordinate attributes: '${missingAttributes.join(', ')}'`, + ); + } + + for (const attribute of linearCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else if (gradientDefinition.type === 'radial') { + gradient = createNode('radialGradient'); + + const presentCoordinateAttributes = radialCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ); + const unsupportedRadialAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allRadialAttributes.includes(attribute), + ); + if (unsupportedRadialAttribute) { + throw new Error( + `Unsupported radial gradient attribute: '${unsupportedRadialAttribute}'`, + ); + } else if (presentCoordinateAttributes.length > 0) { + for (const attribute of presentCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else { + throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + } + + // Set common attributes + setAttribute(gradient, 'id', gradientId); + if (gradientDefinition.gradientUnits !== undefined) { + if ( + !['userSpaceOnUse', 'objectBoundingBox'].includes( + gradientDefinition.gradientUnits, + ) + ) { + throw new Error( + `Unrecognized value for 'gradientUnits' attribute: '${gradientDefinition.gradientUnits}'`, + ); + } + setAttribute(gradient, 'gradientUnits', gradientDefinition.gradientUnits); + } + + if (gradientDefinition.gradientTransform !== undefined) { + if (typeof gradientDefinition.gradientTransform !== 'string') { + throw new Error( + `Type of 'gradientTransform' option expected to be 'string'. Instead received type '${typeof gradientDefinition.gradientTransform}'`, + ); + } + + setAttribute( + gradient, + 'gradientTransform', + gradientDefinition.gradientTransform, + ); + } + + if (gradientDefinition.spreadMethod !== undefined) { + if ( + !['pad', 'reflect', 'repeat'].includes(gradientDefinition.spreadMethod) + ) { + throw new Error( + `Unrecognized value for 'spreadMethod' attribute: '${gradientDefinition.spreadMethod}'`, + ); + } + setAttribute(gradient, 'spreadMethod', gradientDefinition.spreadMethod); + } + + if (gradientDefinition.stops !== undefined) { + if (!Array.isArray(gradientDefinition.stops)) { + throw new Error(`The 'stop' attribute must be an array`); + } + + for (const stopDefinition of gradientDefinition.stops) { + if (typeof stopDefinition !== 'object') { + throw new Error( + `Each entry in the 'stop' attribute must be an object. Instead received type '${typeof stopDefinition}'`, + ); + } + const stop = createNode('stop'); + + if (stopDefinition.offset !== undefined) { + setAttribute(stop, 'offset', stopDefinition.offset); + } + + if (stopDefinition['stop-color'] !== undefined) { + setAttribute(stop, 'stop-color', stopDefinition['stop-color']); + } + + if (stopDefinition['stop-opacity'] !== undefined) { + setAttribute(stop, 'stop-opacity', stopDefinition['stop-opacity']); + } + + gradient.appendChild(stop); + } + } + + defsContainer.appendChild(gradient); + } + + container.appendChild(defsContainer); +} + },{"gl-mat4/invert":4,"gl-mat4/lookAt":5,"gl-mat4/multiply":6,"gl-mat4/perspective":7,"gl-mat4/rotate":8,"gl-vec3/transformMat4":9}]},{},[1]); diff --git a/docs/gradient/bundle.js b/docs/gradient/bundle.js index 07256d52..becce964 100644 --- a/docs/gradient/bundle.js +++ b/docs/gradient/bundle.js @@ -1,15 +1,357 @@ (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { - const rect = container.getBoundingClientRect(); - renderFox(rect, lookCurrent, slowDrift); - }; - - return createLogoViewer( - container, - renderScene, - Object.assign({ cameraDistance }, options), - ); -} - -function createMaskedGradientRect(container, height, width) { - // - const maskedRect = createNode('rect'); - maskedRect.setAttribute('width', width); - maskedRect.setAttribute('height', height); - maskedRect.setAttribute('style', 'fill: url(#gradient1); mask: url(#mask1)'); - container.appendChild(maskedRect); -} - -function createSvgDefs(container) { - // - // - // - // - // - // - // - // ... - // - // - const defsContainer = createNode('defs'); - container.appendChild(defsContainer); - const linearGradient = createNode('linearGradient'); - defsContainer.appendChild(linearGradient); - linearGradient.id = 'gradient1'; - // linearGradient.setAttribute('id', 'gradient1') - const color1 = createNode('stop'); - color1.setAttribute('offset', '0'); - color1.setAttribute('stop-color', '#fbaee3'); - linearGradient.appendChild(color1); - const color2 = createNode('stop'); - color2.setAttribute('offset', '1'); - color2.setAttribute('stop-color', '#ffd982'); - linearGradient.appendChild(color2); - const mask1 = createNode('mask'); - mask1.id = 'mask1'; - defsContainer.appendChild(mask1); - - return { defsContainer, linearGradient, mask1 }; -} - -function createMaskPolygon() { - // - const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'style', 'stroke:#ffffff; fill: #ffffff'); - setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); - return svgPolygon; -} - -},{"../../fox.json":2,"../../util":12,"copy-to-clipboard":3}],2:[function(require,module,exports){ +},{"../..":4,"../../util":14,"../gradient-fox.json":1,"copy-to-clipboard":5}],3:[function(require,module,exports){ module.exports={ "positions": [ [111.024597, 52.604599, 46.225899], @@ -429,7 +694,47 @@ module.exports={ ] } -},{}],3:[function(require,module,exports){ +},{}],4:[function(require,module,exports){ +const foxJson = require('./fox.json'); +const { + calculateSizingOptions, + createLogoViewer, + loadModelFromJson, + createModelRenderer, + createNode, + setAttribute, + setGradientDefinitions, +} = require('./util'); + +module.exports = createLogo; + +function createLogo(options = {}) { + const cameraDistance = options.cameraDistance || 400; + const { height, width } = calculateSizingOptions(options); + const meshJson = options.meshJson || foxJson; + + const container = createNode('svg'); + setAttribute(container, 'width', `${width}px`); + setAttribute(container, 'height', `${height}px`); + document.body.appendChild(container); + + setGradientDefinitions(container, meshJson.gradients); + + const modelObj = loadModelFromJson(meshJson); + const renderFox = createModelRenderer(container, cameraDistance, modelObj); + const renderScene = (lookCurrent, slowDrift) => { + const rect = container.getBoundingClientRect(); + renderFox(rect, lookCurrent, slowDrift); + }; + + return createLogoViewer( + container, + renderScene, + Object.assign({ cameraDistance }, options), + ); +} + +},{"./fox.json":3,"./util":14}],5:[function(require,module,exports){ 'use strict'; var deselectCurrent = require('toggle-selection'); @@ -509,7 +814,7 @@ function copy(text, options) { module.exports = copy; -},{"toggle-selection":11}],4:[function(require,module,exports){ +},{"toggle-selection":13}],6:[function(require,module,exports){ module.exports = identity; /** @@ -537,7 +842,7 @@ function identity(out) { out[15] = 1; return out; }; -},{}],5:[function(require,module,exports){ +},{}],7:[function(require,module,exports){ module.exports = invert; /** @@ -593,7 +898,7 @@ function invert(out, a) { return out; }; -},{}],6:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ var identity = require('./identity'); module.exports = lookAt; @@ -684,7 +989,7 @@ function lookAt(out, eye, center, up) { return out; }; -},{"./identity":4}],7:[function(require,module,exports){ +},{"./identity":6}],9:[function(require,module,exports){ module.exports = multiply; /** @@ -727,7 +1032,7 @@ function multiply(out, a, b) { out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; return out; }; -},{}],8:[function(require,module,exports){ +},{}],10:[function(require,module,exports){ module.exports = perspective; /** @@ -761,7 +1066,7 @@ function perspective(out, fovy, aspect, near, far) { out[15] = 0; return out; }; -},{}],9:[function(require,module,exports){ +},{}],11:[function(require,module,exports){ module.exports = rotate; /** @@ -826,7 +1131,7 @@ function rotate(out, a, rad, axis) { } return out; }; -},{}],10:[function(require,module,exports){ +},{}],12:[function(require,module,exports){ module.exports = transformMat4; /** @@ -847,7 +1152,7 @@ function transformMat4(out, a, m) { out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w return out } -},{}],11:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ module.exports = function () { var selection = document.getSelection(); @@ -888,7 +1193,7 @@ module.exports = function () { }; }; -},{}],12:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ const perspective = require('gl-mat4/perspective'); const multiply = require('gl-mat4/multiply'); const lookAt = require('gl-mat4/lookAt'); @@ -911,6 +1216,7 @@ module.exports = { createFaceUpdater, createNode, setAttribute, + setGradientDefinitions, svgElementToSvgImageContent, Polygon, }; @@ -1105,7 +1411,9 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygonsByChunk = modelJson.chunks.map((chunk) => { const { faces } = chunk; return faces.map((face) => { - const svgPolygon = createSvgPolygon(chunk); + const svgPolygon = createSvgPolygon(chunk, { + gradients: modelJson.gradients, + }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); return polygon; @@ -1114,11 +1422,23 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk) { - const color = `rgb(${chunk.color})`; +function createStandardModelPolygon(chunk, { gradients = {} }) { const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'fill', color); - setAttribute(svgPolygon, 'stroke', color); + + if (chunk.gradient) { + const gradientId = chunk.gradient; + if (!gradients[gradientId]) { + throw new Error(`Gradient ID not found: '${gradientId}'`); + } + + setAttribute(svgPolygon, 'fill', `url('#${gradientId}')`); + setAttribute(svgPolygon, 'stroke', `url('#${gradientId}')`); + } else { + const fill = `rgb(${chunk.color})`; + setAttribute(svgPolygon, 'fill', fill); + setAttribute(svgPolygon, 'stroke', fill); + } + setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); return svgPolygon; } @@ -1264,10 +1584,10 @@ function createFaceUpdater(container, polygons, transformed) { toDraw.push(poly); } toDraw.sort(compareZ); - container.innerHTML = ''; - for (i = 0; i < toDraw.length; ++i) { - container.appendChild(toDraw[i].svg); - } + + const newPolygons = toDraw.map((poly) => poly.svg); + const defs = container.getElementsByTagName('defs'); + container.replaceChildren(...defs, ...newPolygons); }; } @@ -1311,4 +1631,164 @@ function Polygon(svg, indices) { this.zIndex = 0; } -},{"gl-mat4/invert":5,"gl-mat4/lookAt":6,"gl-mat4/multiply":7,"gl-mat4/perspective":8,"gl-mat4/rotate":9,"gl-vec3/transformMat4":10}]},{},[1]); +function setGradientDefinitions(container, gradients) { + if (!gradients || Object.keys(gradients).length === 0) { + return; + } + + const defsContainer = createNode('defs'); + + const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; + const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; + const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const allLinearAttributes = [ + ...linearCoordinateAttributes, + ...commonAttributes, + ]; + const allRadialAttributes = [ + ...radialCoordinateAttributes, + ...commonAttributes, + ]; + + for (const [gradientId, gradientDefinition] of Object.entries(gradients)) { + let gradient; + if (gradientDefinition.type === 'linear') { + gradient = createNode('linearGradient'); + + const unsupportedLinearAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allLinearAttributes.includes(attribute), + ); + if (unsupportedLinearAttribute) { + throw new Error( + `Unsupported linear gradient attribute: '${unsupportedLinearAttribute}'`, + ); + } else if ( + linearCoordinateAttributes.some( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ) + ) { + const missingAttributes = linearCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] === undefined, + ); + if (missingAttributes.length > 0) { + throw new Error( + `Missing coordinate attributes: '${missingAttributes.join(', ')}'`, + ); + } + + for (const attribute of linearCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else if (gradientDefinition.type === 'radial') { + gradient = createNode('radialGradient'); + + const presentCoordinateAttributes = radialCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ); + const unsupportedRadialAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allRadialAttributes.includes(attribute), + ); + if (unsupportedRadialAttribute) { + throw new Error( + `Unsupported radial gradient attribute: '${unsupportedRadialAttribute}'`, + ); + } else if (presentCoordinateAttributes.length > 0) { + for (const attribute of presentCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else { + throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + } + + // Set common attributes + setAttribute(gradient, 'id', gradientId); + if (gradientDefinition.gradientUnits !== undefined) { + if ( + !['userSpaceOnUse', 'objectBoundingBox'].includes( + gradientDefinition.gradientUnits, + ) + ) { + throw new Error( + `Unrecognized value for 'gradientUnits' attribute: '${gradientDefinition.gradientUnits}'`, + ); + } + setAttribute(gradient, 'gradientUnits', gradientDefinition.gradientUnits); + } + + if (gradientDefinition.gradientTransform !== undefined) { + if (typeof gradientDefinition.gradientTransform !== 'string') { + throw new Error( + `Type of 'gradientTransform' option expected to be 'string'. Instead received type '${typeof gradientDefinition.gradientTransform}'`, + ); + } + + setAttribute( + gradient, + 'gradientTransform', + gradientDefinition.gradientTransform, + ); + } + + if (gradientDefinition.spreadMethod !== undefined) { + if ( + !['pad', 'reflect', 'repeat'].includes(gradientDefinition.spreadMethod) + ) { + throw new Error( + `Unrecognized value for 'spreadMethod' attribute: '${gradientDefinition.spreadMethod}'`, + ); + } + setAttribute(gradient, 'spreadMethod', gradientDefinition.spreadMethod); + } + + if (gradientDefinition.stops !== undefined) { + if (!Array.isArray(gradientDefinition.stops)) { + throw new Error(`The 'stop' attribute must be an array`); + } + + for (const stopDefinition of gradientDefinition.stops) { + if (typeof stopDefinition !== 'object') { + throw new Error( + `Each entry in the 'stop' attribute must be an object. Instead received type '${typeof stopDefinition}'`, + ); + } + const stop = createNode('stop'); + + if (stopDefinition.offset !== undefined) { + setAttribute(stop, 'offset', stopDefinition.offset); + } + + if (stopDefinition['stop-color'] !== undefined) { + setAttribute(stop, 'stop-color', stopDefinition['stop-color']); + } + + if (stopDefinition['stop-opacity'] !== undefined) { + setAttribute(stop, 'stop-opacity', stopDefinition['stop-opacity']); + } + + gradient.appendChild(stop); + } + } + + defsContainer.appendChild(gradient); + } + + container.appendChild(defsContainer); +} + +},{"gl-mat4/invert":7,"gl-mat4/lookAt":8,"gl-mat4/multiply":9,"gl-mat4/perspective":10,"gl-mat4/rotate":11,"gl-vec3/transformMat4":12}]},{},[2]); diff --git a/docs/normal/bundle.js b/docs/normal/bundle.js index 6dcb81f7..d885dd4c 100644 --- a/docs/normal/bundle.js +++ b/docs/normal/bundle.js @@ -352,6 +352,7 @@ const { createModelRenderer, createNode, setAttribute, + setGradientDefinitions, } = require('./util'); module.exports = createLogo; @@ -359,13 +360,16 @@ module.exports = createLogo; function createLogo(options = {}) { const cameraDistance = options.cameraDistance || 400; const { height, width } = calculateSizingOptions(options); + const meshJson = options.meshJson || foxJson; const container = createNode('svg'); setAttribute(container, 'width', `${width}px`); setAttribute(container, 'height', `${height}px`); document.body.appendChild(container); - const modelObj = loadModelFromJson(options.meshJson || foxJson); + setGradientDefinitions(container, meshJson.gradients); + + const modelObj = loadModelFromJson(meshJson); const renderFox = createModelRenderer(container, cameraDistance, modelObj); const renderScene = (lookCurrent, slowDrift) => { const rect = container.getBoundingClientRect(); @@ -861,6 +865,7 @@ module.exports = { createFaceUpdater, createNode, setAttribute, + setGradientDefinitions, svgElementToSvgImageContent, Polygon, }; @@ -1055,7 +1060,9 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygonsByChunk = modelJson.chunks.map((chunk) => { const { faces } = chunk; return faces.map((face) => { - const svgPolygon = createSvgPolygon(chunk); + const svgPolygon = createSvgPolygon(chunk, { + gradients: modelJson.gradients, + }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); return polygon; @@ -1064,11 +1071,23 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk) { - const color = `rgb(${chunk.color})`; +function createStandardModelPolygon(chunk, { gradients = {} }) { const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'fill', color); - setAttribute(svgPolygon, 'stroke', color); + + if (chunk.gradient) { + const gradientId = chunk.gradient; + if (!gradients[gradientId]) { + throw new Error(`Gradient ID not found: '${gradientId}'`); + } + + setAttribute(svgPolygon, 'fill', `url('#${gradientId}')`); + setAttribute(svgPolygon, 'stroke', `url('#${gradientId}')`); + } else { + const fill = `rgb(${chunk.color})`; + setAttribute(svgPolygon, 'fill', fill); + setAttribute(svgPolygon, 'stroke', fill); + } + setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); return svgPolygon; } @@ -1214,10 +1233,10 @@ function createFaceUpdater(container, polygons, transformed) { toDraw.push(poly); } toDraw.sort(compareZ); - container.innerHTML = ''; - for (i = 0; i < toDraw.length; ++i) { - container.appendChild(toDraw[i].svg); - } + + const newPolygons = toDraw.map((poly) => poly.svg); + const defs = container.getElementsByTagName('defs'); + container.replaceChildren(...defs, ...newPolygons); }; } @@ -1261,4 +1280,164 @@ function Polygon(svg, indices) { this.zIndex = 0; } +function setGradientDefinitions(container, gradients) { + if (!gradients || Object.keys(gradients).length === 0) { + return; + } + + const defsContainer = createNode('defs'); + + const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; + const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; + const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const allLinearAttributes = [ + ...linearCoordinateAttributes, + ...commonAttributes, + ]; + const allRadialAttributes = [ + ...radialCoordinateAttributes, + ...commonAttributes, + ]; + + for (const [gradientId, gradientDefinition] of Object.entries(gradients)) { + let gradient; + if (gradientDefinition.type === 'linear') { + gradient = createNode('linearGradient'); + + const unsupportedLinearAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allLinearAttributes.includes(attribute), + ); + if (unsupportedLinearAttribute) { + throw new Error( + `Unsupported linear gradient attribute: '${unsupportedLinearAttribute}'`, + ); + } else if ( + linearCoordinateAttributes.some( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ) + ) { + const missingAttributes = linearCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] === undefined, + ); + if (missingAttributes.length > 0) { + throw new Error( + `Missing coordinate attributes: '${missingAttributes.join(', ')}'`, + ); + } + + for (const attribute of linearCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else if (gradientDefinition.type === 'radial') { + gradient = createNode('radialGradient'); + + const presentCoordinateAttributes = radialCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ); + const unsupportedRadialAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allRadialAttributes.includes(attribute), + ); + if (unsupportedRadialAttribute) { + throw new Error( + `Unsupported radial gradient attribute: '${unsupportedRadialAttribute}'`, + ); + } else if (presentCoordinateAttributes.length > 0) { + for (const attribute of presentCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else { + throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + } + + // Set common attributes + setAttribute(gradient, 'id', gradientId); + if (gradientDefinition.gradientUnits !== undefined) { + if ( + !['userSpaceOnUse', 'objectBoundingBox'].includes( + gradientDefinition.gradientUnits, + ) + ) { + throw new Error( + `Unrecognized value for 'gradientUnits' attribute: '${gradientDefinition.gradientUnits}'`, + ); + } + setAttribute(gradient, 'gradientUnits', gradientDefinition.gradientUnits); + } + + if (gradientDefinition.gradientTransform !== undefined) { + if (typeof gradientDefinition.gradientTransform !== 'string') { + throw new Error( + `Type of 'gradientTransform' option expected to be 'string'. Instead received type '${typeof gradientDefinition.gradientTransform}'`, + ); + } + + setAttribute( + gradient, + 'gradientTransform', + gradientDefinition.gradientTransform, + ); + } + + if (gradientDefinition.spreadMethod !== undefined) { + if ( + !['pad', 'reflect', 'repeat'].includes(gradientDefinition.spreadMethod) + ) { + throw new Error( + `Unrecognized value for 'spreadMethod' attribute: '${gradientDefinition.spreadMethod}'`, + ); + } + setAttribute(gradient, 'spreadMethod', gradientDefinition.spreadMethod); + } + + if (gradientDefinition.stops !== undefined) { + if (!Array.isArray(gradientDefinition.stops)) { + throw new Error(`The 'stop' attribute must be an array`); + } + + for (const stopDefinition of gradientDefinition.stops) { + if (typeof stopDefinition !== 'object') { + throw new Error( + `Each entry in the 'stop' attribute must be an object. Instead received type '${typeof stopDefinition}'`, + ); + } + const stop = createNode('stop'); + + if (stopDefinition.offset !== undefined) { + setAttribute(stop, 'offset', stopDefinition.offset); + } + + if (stopDefinition['stop-color'] !== undefined) { + setAttribute(stop, 'stop-color', stopDefinition['stop-color']); + } + + if (stopDefinition['stop-opacity'] !== undefined) { + setAttribute(stop, 'stop-opacity', stopDefinition['stop-opacity']); + } + + gradient.appendChild(stop); + } + } + + defsContainer.appendChild(gradient); + } + + container.appendChild(defsContainer); +} + },{"gl-mat4/invert":6,"gl-mat4/lookAt":7,"gl-mat4/multiply":8,"gl-mat4/perspective":9,"gl-mat4/rotate":10,"gl-vec3/transformMat4":11}]},{},[1]); diff --git a/docs/recolor/bundle.js b/docs/recolor/bundle.js index a13ecc01..659a4cd7 100644 --- a/docs/recolor/bundle.js +++ b/docs/recolor/bundle.js @@ -1028,6 +1028,7 @@ module.exports = { createFaceUpdater, createNode, setAttribute, + setGradientDefinitions, svgElementToSvgImageContent, Polygon, }; @@ -1222,7 +1223,9 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygonsByChunk = modelJson.chunks.map((chunk) => { const { faces } = chunk; return faces.map((face) => { - const svgPolygon = createSvgPolygon(chunk); + const svgPolygon = createSvgPolygon(chunk, { + gradients: modelJson.gradients, + }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); return polygon; @@ -1231,11 +1234,23 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk) { - const color = `rgb(${chunk.color})`; +function createStandardModelPolygon(chunk, { gradients = {} }) { const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'fill', color); - setAttribute(svgPolygon, 'stroke', color); + + if (chunk.gradient) { + const gradientId = chunk.gradient; + if (!gradients[gradientId]) { + throw new Error(`Gradient ID not found: '${gradientId}'`); + } + + setAttribute(svgPolygon, 'fill', `url('#${gradientId}')`); + setAttribute(svgPolygon, 'stroke', `url('#${gradientId}')`); + } else { + const fill = `rgb(${chunk.color})`; + setAttribute(svgPolygon, 'fill', fill); + setAttribute(svgPolygon, 'stroke', fill); + } + setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); return svgPolygon; } @@ -1381,10 +1396,10 @@ function createFaceUpdater(container, polygons, transformed) { toDraw.push(poly); } toDraw.sort(compareZ); - container.innerHTML = ''; - for (i = 0; i < toDraw.length; ++i) { - container.appendChild(toDraw[i].svg); - } + + const newPolygons = toDraw.map((poly) => poly.svg); + const defs = container.getElementsByTagName('defs'); + container.replaceChildren(...defs, ...newPolygons); }; } @@ -1428,4 +1443,164 @@ function Polygon(svg, indices) { this.zIndex = 0; } +function setGradientDefinitions(container, gradients) { + if (!gradients || Object.keys(gradients).length === 0) { + return; + } + + const defsContainer = createNode('defs'); + + const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; + const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; + const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const allLinearAttributes = [ + ...linearCoordinateAttributes, + ...commonAttributes, + ]; + const allRadialAttributes = [ + ...radialCoordinateAttributes, + ...commonAttributes, + ]; + + for (const [gradientId, gradientDefinition] of Object.entries(gradients)) { + let gradient; + if (gradientDefinition.type === 'linear') { + gradient = createNode('linearGradient'); + + const unsupportedLinearAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allLinearAttributes.includes(attribute), + ); + if (unsupportedLinearAttribute) { + throw new Error( + `Unsupported linear gradient attribute: '${unsupportedLinearAttribute}'`, + ); + } else if ( + linearCoordinateAttributes.some( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ) + ) { + const missingAttributes = linearCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] === undefined, + ); + if (missingAttributes.length > 0) { + throw new Error( + `Missing coordinate attributes: '${missingAttributes.join(', ')}'`, + ); + } + + for (const attribute of linearCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else if (gradientDefinition.type === 'radial') { + gradient = createNode('radialGradient'); + + const presentCoordinateAttributes = radialCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ); + const unsupportedRadialAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allRadialAttributes.includes(attribute), + ); + if (unsupportedRadialAttribute) { + throw new Error( + `Unsupported radial gradient attribute: '${unsupportedRadialAttribute}'`, + ); + } else if (presentCoordinateAttributes.length > 0) { + for (const attribute of presentCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else { + throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + } + + // Set common attributes + setAttribute(gradient, 'id', gradientId); + if (gradientDefinition.gradientUnits !== undefined) { + if ( + !['userSpaceOnUse', 'objectBoundingBox'].includes( + gradientDefinition.gradientUnits, + ) + ) { + throw new Error( + `Unrecognized value for 'gradientUnits' attribute: '${gradientDefinition.gradientUnits}'`, + ); + } + setAttribute(gradient, 'gradientUnits', gradientDefinition.gradientUnits); + } + + if (gradientDefinition.gradientTransform !== undefined) { + if (typeof gradientDefinition.gradientTransform !== 'string') { + throw new Error( + `Type of 'gradientTransform' option expected to be 'string'. Instead received type '${typeof gradientDefinition.gradientTransform}'`, + ); + } + + setAttribute( + gradient, + 'gradientTransform', + gradientDefinition.gradientTransform, + ); + } + + if (gradientDefinition.spreadMethod !== undefined) { + if ( + !['pad', 'reflect', 'repeat'].includes(gradientDefinition.spreadMethod) + ) { + throw new Error( + `Unrecognized value for 'spreadMethod' attribute: '${gradientDefinition.spreadMethod}'`, + ); + } + setAttribute(gradient, 'spreadMethod', gradientDefinition.spreadMethod); + } + + if (gradientDefinition.stops !== undefined) { + if (!Array.isArray(gradientDefinition.stops)) { + throw new Error(`The 'stop' attribute must be an array`); + } + + for (const stopDefinition of gradientDefinition.stops) { + if (typeof stopDefinition !== 'object') { + throw new Error( + `Each entry in the 'stop' attribute must be an object. Instead received type '${typeof stopDefinition}'`, + ); + } + const stop = createNode('stop'); + + if (stopDefinition.offset !== undefined) { + setAttribute(stop, 'offset', stopDefinition.offset); + } + + if (stopDefinition['stop-color'] !== undefined) { + setAttribute(stop, 'stop-color', stopDefinition['stop-color']); + } + + if (stopDefinition['stop-opacity'] !== undefined) { + setAttribute(stop, 'stop-opacity', stopDefinition['stop-opacity']); + } + + gradient.appendChild(stop); + } + } + + defsContainer.appendChild(gradient); + } + + container.appendChild(defsContainer); +} + },{"gl-mat4/invert":4,"gl-mat4/lookAt":5,"gl-mat4/multiply":6,"gl-mat4/perspective":7,"gl-mat4/rotate":8,"gl-vec3/transformMat4":9}]},{},[1]); diff --git a/beta-fox.json b/example/beta-fox.json similarity index 100% rename from beta-fox.json rename to example/beta-fox.json diff --git a/example/gradient-fox.json b/example/gradient-fox.json new file mode 100644 index 00000000..5f0271a3 --- /dev/null +++ b/example/gradient-fox.json @@ -0,0 +1,347 @@ +{ + "positions": [ + [111.024597, 52.604599, 46.225899], + [114.025002, 87.673302, 58.9818], + [66.192001, 80.898003, 55.394299], + [72.113297, 35.491798, 30.871401], + [97.804497, 116.560997, 73.978798], + [16.7623, 58.010899, 58.078201], + [52.608898, 30.3641, 42.556099], + [106.881401, 31.945499, 46.9133], + [113.484596, 38.6049, 49.121498], + [108.6633, 43.2332, 46.315399], + [101.216599, 15.9822, 46.308201], + [16.6605, -16.2883, 93.618698], + [40.775002, -10.2288, 85.276398], + [23.926901, -2.5103, 86.736504], + [11.1691, -7.0037, 99.377602], + [9.5692, -34.393902, 141.671997], + [12.596, 7.1655, 88.740997], + [61.180901, 8.8142, 76.996803], + [39.719501, -28.927099, 88.963799], + [13.7962, -68.575699, 132.057007], + [15.2674, -62.32, 129.688004], + [14.8446, -52.6096, 140.113007], + [12.8917, -49.771599, 144.740997], + [35.604198, -71.758003, 81.063904], + [47.462502, -68.606102, 63.369701], + [38.2486, -64.730202, 38.909901], + [-12.8917, -49.771599, 144.740997], + [-13.7962, -68.575699, 132.057007], + [17.802099, -71.758003, 81.063904], + [19.1243, -69.0168, 49.420101], + [38.2486, -66.275597, 17.776199], + [12.8928, -36.703499, 141.671997], + [109.283997, -93.589897, 27.824301], + [122.117996, -36.8894, 35.025002], + [67.7668, -30.197001, 78.417801], + [33.180698, 101.851997, 25.3186], + [9.4063, -35.589802, 150.722], + [-9.5692, -34.393902, 141.671997], + [-9.4063, -35.589802, 150.722], + [11.4565, -37.899399, 150.722], + [-12.596, 7.1655, 88.740997], + [-11.1691, -7.0037, 99.377602], + [70.236504, 62.836201, -3.9475], + [47.263401, 54.293999, -27.414801], + [28.7302, 91.731102, -24.972601], + [69.167603, 6.5862, -12.7757], + [28.7302, 49.1003, -48.3596], + [31.903, 5.692, -47.821999], + [35.075802, -34.432899, -16.280899], + [115.284103, 48.681499, 48.684101], + [110.842796, 28.4821, 49.176201], + [-19.1243, -69.0168, 49.420101], + [-38.2486, -66.275597, 17.776199], + [-111.024597, 52.604599, 46.225899], + [-72.113297, 35.491798, 30.871401], + [-66.192001, 80.898003, 55.394299], + [-114.025002, 87.673302, 58.9818], + [-97.804497, 116.560997, 73.978798], + [-52.608898, 30.3641, 42.556099], + [-16.7623, 58.010899, 58.078201], + [-106.881401, 31.945499, 46.9133], + [-108.6633, 43.2332, 46.315399], + [-113.484596, 38.6049, 49.121498], + [-101.216599, 15.9822, 46.308201], + [-16.6605, -16.2883, 93.618698], + [-23.926901, -2.5103, 86.736504], + [-40.775002, -10.2288, 85.276398], + [-61.180901, 8.8142, 76.996803], + [-39.719501, -28.927099, 88.963799], + [-14.8446, -52.6096, 140.113007], + [-15.2674, -62.32, 129.688004], + [-47.462502, -68.606102, 63.369701], + [-35.604198, -71.758003, 81.063904], + [-38.2486, -64.730202, 38.909901], + [-17.802099, -71.758003, 81.063904], + [-12.8928, -36.703499, 141.671997], + [-67.7668, -30.197001, 78.417801], + [-122.117996, -36.8894, 35.025002], + [-109.283997, -93.589897, 27.824301], + [-33.180698, 101.851997, 25.3186], + [-11.4565, -37.899399, 150.722], + [-70.236504, 62.836201, -3.9475], + [-28.7302, 91.731102, -24.972601], + [-47.263401, 54.293999, -27.414801], + [-69.167603, 6.5862, -12.7757], + [-28.7302, 49.1003, -48.3596], + [-31.903, 5.692, -47.821999], + [-35.075802, -34.432899, -16.280899], + [-115.284103, 48.681499, 48.684101], + [-110.842796, 28.4821, 49.176201] + ], + "chunks": [ + { + "color": [119, 57, 0], + "faces": [ + [0, 1, 2], + [2, 3, 0], + [4, 5, 2], + [6, 3, 2], + [2, 5, 6], + [7, 8, 9], + [10, 3, 6], + [10, 50, 7], + [7, 3, 10], + [7, 9, 3], + [49, 0, 9], + [3, 9, 0], + [53, 54, 55], + [55, 56, 53], + [57, 56, 55], + [58, 59, 55], + [55, 54, 58], + [60, 61, 62], + [63, 58, 54], + [63, 60, 89], + [60, 63, 54], + [60, 54, 61], + [88, 61, 53], + [54, 53, 61], + [2, 1, 4], + [55, 59, 57] + ], + "gradient": "linear1" + }, + { + "color": [36, 51, 67], + "faces": [ + [11, 12, 13], + [64, 65, 66] + ], + "gradient": "linear1" + }, + { + "color": [228, 116, 36], + "faces": [ + [14, 15, 11], + [11, 16, 14], + [17, 12, 18], + [41, 64, 37], + [67, 68, 66] + ], + "gradient": "linear1" + }, + { + "color": [192, 172, 157], + "faces": [ + [19, 20, 21], + [21, 22, 19], + [20, 19, 23], + [23, 24, 20], + [23, 25, 24], + [19, 22, 26], + [26, 27, 19], + [23, 28, 29], + [23, 29, 30], + [25, 23, 30], + [29, 51, 52], + [52, 30, 29], + [27, 26, 69], + [69, 70, 27], + [70, 71, 72], + [72, 27, 70], + [72, 71, 73], + [51, 74, 72], + [52, 51, 72], + [73, 52, 72], + [19, 27, 74], + [74, 28, 19], + [51, 29, 28], + [28, 74, 51], + [74, 27, 72], + [28, 23, 19] + ], + "gradient": "linear1" + }, + { + "color": [214, 194, 178], + "faces": [ + [21, 20, 24], + [24, 31, 21], + [69, 71, 70], + [71, 69, 75] + ], + "gradient": "linear1" + }, + { + "color": [228, 119, 25], + "faces": [ + [31, 24, 18], + [6, 5, 16], + [16, 17, 6], + [24, 32, 33], + [33, 34, 24], + [5, 4, 35], + [75, 68, 71], + [58, 67, 40], + [40, 59, 58], + [71, 76, 77], + [77, 78, 71] + ], + "gradient": "linear1" + }, + { + "color": [205, 98, 0], + "faces": [ + [24, 34, 18], + [16, 13, 12], + [12, 17, 16], + [13, 16, 11], + [71, 68, 76], + [40, 67, 66], + [66, 65, 40], + [65, 64, 40] + ], + "gradient": "linear1" + }, + { + "color": [0, 0, 0], + "faces": [ + [36, 15, 37], + [37, 38, 36], + [31, 39, 22], + [22, 21, 31], + [31, 15, 36], + [36, 39, 31], + [75, 69, 26], + [26, 80, 75], + [75, 80, 38], + [38, 37, 75], + [38, 80, 39], + [39, 36, 38], + [39, 80, 26], + [26, 22, 39] + ], + "gradient": "linear1" + }, + { + "color": [247, 132, 25], + "faces": [ + [17, 33, 10], + [17, 18, 34], + [34, 33, 17], + [10, 6, 17], + [11, 15, 31], + [31, 18, 11], + [18, 12, 11], + [14, 16, 40], + [40, 41, 14], + [59, 5, 35], + [35, 79, 59], + [67, 63, 77], + [67, 77, 76], + [76, 68, 67], + [63, 67, 58], + [64, 68, 75], + [75, 37, 64], + [68, 64, 66], + [14, 41, 37], + [37, 15, 14], + [5, 59, 40], + [40, 16, 5] + ], + "gradient": "linear1" + }, + { + "color": [225, 119, 25], + "faces": [ + [35, 4, 42], + [4, 1, 42], + [42, 43, 44], + [44, 35, 42], + [45, 43, 42], + [42, 10, 45], + [30, 32, 24], + [24, 25, 30], + [30, 33, 32], + [33, 30, 10], + [44, 43, 46], + [43, 45, 47], + [47, 46, 43], + [48, 47, 45], + [45, 30, 48], + [30, 45, 10], + [49, 42, 0], + [8, 7, 42], + [50, 42, 7], + [50, 10, 42], + [1, 0, 42], + [42, 9, 8], + [42, 49, 9], + [64, 41, 40], + [57, 59, 79], + [79, 81, 57], + [57, 81, 56], + [82, 79, 35], + [35, 44, 82], + [81, 79, 82], + [82, 83, 81], + [84, 63, 81], + [81, 83, 84], + [44, 46, 85], + [85, 82, 44], + [52, 73, 71], + [71, 78, 52], + [52, 78, 77], + [77, 63, 52], + [82, 85, 83], + [83, 85, 86], + [86, 84, 83], + [87, 52, 84], + [84, 86, 87], + [52, 63, 84], + [88, 53, 81], + [62, 81, 60], + [89, 60, 81], + [89, 81, 63], + [56, 81, 53], + [81, 62, 61], + [81, 61, 88], + [48, 87, 86], + [86, 47, 48], + [47, 86, 85], + [85, 46, 47], + [48, 30, 52], + [52, 87, 48] + ], + "gradient": "linear1" + } + ], + "gradients": { + "linear1": { + "gradientUnits": "userSpaceOnUse", + "type": "linear", + "stops": [ + { + "stop-color": "#fbaee3" + }, + { + "offset": "1", + "stop-color": "#ffd982" + } + ] + } + } +} diff --git a/example/src/beta.js b/example/src/beta.js index 736b32c4..17e71524 100644 --- a/example/src/beta.js +++ b/example/src/beta.js @@ -1,7 +1,7 @@ const copy = require('copy-to-clipboard'); const createViewer = require('../..'); const { svgElementToSvgImageContent } = require('../../util'); -const meshJson = require('../../beta-fox.json'); +const meshJson = require('../beta-fox.json'); document.addEventListener('keypress', function (event) { if (event.keyCode === 99) { diff --git a/example/src/gradient.js b/example/src/gradient.js index 861159a9..4eff0641 100644 --- a/example/src/gradient.js +++ b/example/src/gradient.js @@ -1,14 +1,7 @@ const copy = require('copy-to-clipboard'); -const { - calculateSizingOptions, - createLogoViewer, - loadModelFromJson, - createModelRenderer, - createNode, - setAttribute, - svgElementToSvgImageContent, -} = require('../../util'); -const foxJson = require('../../fox.json'); +const createViewer = require('../..'); +const { svgElementToSvgImageContent } = require('../../util'); +const meshJson = require('../gradient-fox.json'); document.addEventListener('keypress', function (event) { if (event.keyCode === 99) { @@ -19,87 +12,10 @@ document.addEventListener('keypress', function (event) { } }); -createGradientLogo({ +createViewer({ width: 0.4, height: 0.4, followMouse: true, followMotion: true, + meshJson, }); - -function createGradientLogo(options) { - const cameraDistance = options.cameraDistance || 400; - const { height, width } = calculateSizingOptions(options); - - const container = createNode('svg'); - setAttribute(container, 'width', `${width}px`); - setAttribute(container, 'height', `${height}px`); - document.body.appendChild(container); - const { mask1 } = createSvgDefs(container); - createMaskedGradientRect(container, height, width); - - const modelObj = loadModelFromJson(foxJson, createMaskPolygon); - const renderFox = createModelRenderer(mask1, cameraDistance, modelObj); - const renderScene = (lookCurrent, slowDrift) => { - const rect = container.getBoundingClientRect(); - renderFox(rect, lookCurrent, slowDrift); - }; - - return createLogoViewer( - container, - renderScene, - Object.assign({ cameraDistance }, options), - ); -} - -function createMaskedGradientRect(container, height, width) { - // - const maskedRect = createNode('rect'); - maskedRect.setAttribute('width', width); - maskedRect.setAttribute('height', height); - maskedRect.setAttribute('style', 'fill: url(#gradient1); mask: url(#mask1)'); - container.appendChild(maskedRect); -} - -function createSvgDefs(container) { - // - // - // - // - // - // - // - // ... - // - // - const defsContainer = createNode('defs'); - container.appendChild(defsContainer); - const linearGradient = createNode('linearGradient'); - defsContainer.appendChild(linearGradient); - linearGradient.id = 'gradient1'; - // linearGradient.setAttribute('id', 'gradient1') - const color1 = createNode('stop'); - color1.setAttribute('offset', '0'); - color1.setAttribute('stop-color', '#fbaee3'); - linearGradient.appendChild(color1); - const color2 = createNode('stop'); - color2.setAttribute('offset', '1'); - color2.setAttribute('stop-color', '#ffd982'); - linearGradient.appendChild(color2); - const mask1 = createNode('mask'); - mask1.id = 'mask1'; - defsContainer.appendChild(mask1); - - return { defsContainer, linearGradient, mask1 }; -} - -function createMaskPolygon() { - // - const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'style', 'stroke:#ffffff; fill: #ffffff'); - setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); - return svgPolygon; -} diff --git a/index.js b/index.js index 5ab0e643..fc8f64bd 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ const { createModelRenderer, createNode, setAttribute, + setGradientDefinitions, } = require('./util'); module.exports = createLogo; @@ -13,13 +14,16 @@ module.exports = createLogo; function createLogo(options = {}) { const cameraDistance = options.cameraDistance || 400; const { height, width } = calculateSizingOptions(options); + const meshJson = options.meshJson || foxJson; const container = createNode('svg'); setAttribute(container, 'width', `${width}px`); setAttribute(container, 'height', `${height}px`); document.body.appendChild(container); - const modelObj = loadModelFromJson(options.meshJson || foxJson); + setGradientDefinitions(container, meshJson.gradients); + + const modelObj = loadModelFromJson(meshJson); const renderFox = createModelRenderer(container, cameraDistance, modelObj); const renderScene = (lookCurrent, slowDrift) => { const rect = container.getBoundingClientRect(); diff --git a/util.js b/util.js index d980b87f..7de19519 100644 --- a/util.js +++ b/util.js @@ -20,6 +20,7 @@ module.exports = { createFaceUpdater, createNode, setAttribute, + setGradientDefinitions, svgElementToSvgImageContent, Polygon, }; @@ -214,7 +215,9 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygonsByChunk = modelJson.chunks.map((chunk) => { const { faces } = chunk; return faces.map((face) => { - const svgPolygon = createSvgPolygon(chunk); + const svgPolygon = createSvgPolygon(chunk, { + gradients: modelJson.gradients, + }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); return polygon; @@ -223,11 +226,23 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk) { - const color = `rgb(${chunk.color})`; +function createStandardModelPolygon(chunk, { gradients = {} }) { const svgPolygon = createNode('polygon'); - setAttribute(svgPolygon, 'fill', color); - setAttribute(svgPolygon, 'stroke', color); + + if (chunk.gradient) { + const gradientId = chunk.gradient; + if (!gradients[gradientId]) { + throw new Error(`Gradient ID not found: '${gradientId}'`); + } + + setAttribute(svgPolygon, 'fill', `url('#${gradientId}')`); + setAttribute(svgPolygon, 'stroke', `url('#${gradientId}')`); + } else { + const fill = `rgb(${chunk.color})`; + setAttribute(svgPolygon, 'fill', fill); + setAttribute(svgPolygon, 'stroke', fill); + } + setAttribute(svgPolygon, 'points', '0,0, 10,0, 0,10'); return svgPolygon; } @@ -373,10 +388,10 @@ function createFaceUpdater(container, polygons, transformed) { toDraw.push(poly); } toDraw.sort(compareZ); - container.innerHTML = ''; - for (i = 0; i < toDraw.length; ++i) { - container.appendChild(toDraw[i].svg); - } + + const newPolygons = toDraw.map((poly) => poly.svg); + const defs = container.getElementsByTagName('defs'); + container.replaceChildren(...defs, ...newPolygons); }; } @@ -419,3 +434,163 @@ function Polygon(svg, indices) { this.indices = indices; this.zIndex = 0; } + +function setGradientDefinitions(container, gradients) { + if (!gradients || Object.keys(gradients).length === 0) { + return; + } + + const defsContainer = createNode('defs'); + + const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; + const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; + const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const allLinearAttributes = [ + ...linearCoordinateAttributes, + ...commonAttributes, + ]; + const allRadialAttributes = [ + ...radialCoordinateAttributes, + ...commonAttributes, + ]; + + for (const [gradientId, gradientDefinition] of Object.entries(gradients)) { + let gradient; + if (gradientDefinition.type === 'linear') { + gradient = createNode('linearGradient'); + + const unsupportedLinearAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allLinearAttributes.includes(attribute), + ); + if (unsupportedLinearAttribute) { + throw new Error( + `Unsupported linear gradient attribute: '${unsupportedLinearAttribute}'`, + ); + } else if ( + linearCoordinateAttributes.some( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ) + ) { + const missingAttributes = linearCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] === undefined, + ); + if (missingAttributes.length > 0) { + throw new Error( + `Missing coordinate attributes: '${missingAttributes.join(', ')}'`, + ); + } + + for (const attribute of linearCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else if (gradientDefinition.type === 'radial') { + gradient = createNode('radialGradient'); + + const presentCoordinateAttributes = radialCoordinateAttributes.filter( + (attributeName) => gradientDefinition[attributeName] !== undefined, + ); + const unsupportedRadialAttribute = Object.keys(gradientDefinition).find( + (attribute) => !allRadialAttributes.includes(attribute), + ); + if (unsupportedRadialAttribute) { + throw new Error( + `Unsupported radial gradient attribute: '${unsupportedRadialAttribute}'`, + ); + } else if (presentCoordinateAttributes.length > 0) { + for (const attribute of presentCoordinateAttributes) { + if (typeof gradientDefinition[attribute] !== 'string') { + throw new Error( + `Type of '${attribute}' option expected to be 'string'. Instead received type '${typeof gradientDefinition[ + attribute + ]}'`, + ); + } + setAttribute(gradient, attribute, gradientDefinition[attribute]); + } + } + } else { + throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + } + + // Set common attributes + setAttribute(gradient, 'id', gradientId); + if (gradientDefinition.gradientUnits !== undefined) { + if ( + !['userSpaceOnUse', 'objectBoundingBox'].includes( + gradientDefinition.gradientUnits, + ) + ) { + throw new Error( + `Unrecognized value for 'gradientUnits' attribute: '${gradientDefinition.gradientUnits}'`, + ); + } + setAttribute(gradient, 'gradientUnits', gradientDefinition.gradientUnits); + } + + if (gradientDefinition.gradientTransform !== undefined) { + if (typeof gradientDefinition.gradientTransform !== 'string') { + throw new Error( + `Type of 'gradientTransform' option expected to be 'string'. Instead received type '${typeof gradientDefinition.gradientTransform}'`, + ); + } + + setAttribute( + gradient, + 'gradientTransform', + gradientDefinition.gradientTransform, + ); + } + + if (gradientDefinition.spreadMethod !== undefined) { + if ( + !['pad', 'reflect', 'repeat'].includes(gradientDefinition.spreadMethod) + ) { + throw new Error( + `Unrecognized value for 'spreadMethod' attribute: '${gradientDefinition.spreadMethod}'`, + ); + } + setAttribute(gradient, 'spreadMethod', gradientDefinition.spreadMethod); + } + + if (gradientDefinition.stops !== undefined) { + if (!Array.isArray(gradientDefinition.stops)) { + throw new Error(`The 'stop' attribute must be an array`); + } + + for (const stopDefinition of gradientDefinition.stops) { + if (typeof stopDefinition !== 'object') { + throw new Error( + `Each entry in the 'stop' attribute must be an object. Instead received type '${typeof stopDefinition}'`, + ); + } + const stop = createNode('stop'); + + if (stopDefinition.offset !== undefined) { + setAttribute(stop, 'offset', stopDefinition.offset); + } + + if (stopDefinition['stop-color'] !== undefined) { + setAttribute(stop, 'stop-color', stopDefinition['stop-color']); + } + + if (stopDefinition['stop-opacity'] !== undefined) { + setAttribute(stop, 'stop-opacity', stopDefinition['stop-opacity']); + } + + gradient.appendChild(stop); + } + } + + defsContainer.appendChild(gradient); + } + + container.appendChild(defsContainer); +} From d1cb31b6e957200775cec89cf32e54ca55e0d821 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 11 Oct 2021 13:14:11 -0700 Subject: [PATCH 2/5] Update util.js --- util.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util.js b/util.js index 7de19519..8bf5bb9a 100644 --- a/util.js +++ b/util.js @@ -517,7 +517,9 @@ function setGradientDefinitions(container, gradients) { } } } else { - throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + throw new Error( + `Unsupported gradient type: '${gradientDefinition.type}'`, + ); } // Set common attributes From 23c6622e0e6ecc00adad2bb8f9e5852746fec826 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 12 Oct 2021 16:22:07 -0230 Subject: [PATCH 3/5] Prevent specifying both color and gradient Color and gradient are mutually exclusive options, so they should not both be set. An error is now thrown if both are set, so that there is no ambiguoity about how the polygon will be colored. --- docs/beta/bundle.js | 15 +++++++++++---- docs/distort/bundle.js | 15 +++++++++++---- docs/gradient/bundle.js | 25 +++++++++++-------------- docs/normal/bundle.js | 15 +++++++++++---- docs/recolor/bundle.js | 15 +++++++++++---- example/gradient-fox.json | 10 ---------- util.js | 11 ++++++++--- 7 files changed, 63 insertions(+), 43 deletions(-) diff --git a/docs/beta/bundle.js b/docs/beta/bundle.js index ec04f3ff..0f8261e3 100644 --- a/docs/beta/bundle.js +++ b/docs/beta/bundle.js @@ -1398,11 +1398,12 @@ function positionsFromModel(positions, modelJson) { function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygons = []; - const polygonsByChunk = modelJson.chunks.map((chunk) => { + const polygonsByChunk = modelJson.chunks.map((chunk, index) => { const { faces } = chunk; return faces.map((face) => { const svgPolygon = createSvgPolygon(chunk, { gradients: modelJson.gradients, + index, }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); @@ -1412,10 +1413,14 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk, { gradients = {} }) { +function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); - if (chunk.gradient) { + if (chunk.gradient && chunk.color) { + throw new Error( + `Both gradient and color for chunk '${index}'. These options are mutually exclusive.`, + ); + } else if (chunk.gradient) { const gradientId = chunk.gradient; if (!gradients[gradientId]) { throw new Error(`Gradient ID not found: '${gradientId}'`); @@ -1703,7 +1708,9 @@ function setGradientDefinitions(container, gradients) { } } } else { - throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + throw new Error( + `Unsupported gradient type: '${gradientDefinition.type}'`, + ); } // Set common attributes diff --git a/docs/distort/bundle.js b/docs/distort/bundle.js index 8081b744..7ced2130 100644 --- a/docs/distort/bundle.js +++ b/docs/distort/bundle.js @@ -1015,11 +1015,12 @@ function positionsFromModel(positions, modelJson) { function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygons = []; - const polygonsByChunk = modelJson.chunks.map((chunk) => { + const polygonsByChunk = modelJson.chunks.map((chunk, index) => { const { faces } = chunk; return faces.map((face) => { const svgPolygon = createSvgPolygon(chunk, { gradients: modelJson.gradients, + index, }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); @@ -1029,10 +1030,14 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk, { gradients = {} }) { +function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); - if (chunk.gradient) { + if (chunk.gradient && chunk.color) { + throw new Error( + `Both gradient and color for chunk '${index}'. These options are mutually exclusive.`, + ); + } else if (chunk.gradient) { const gradientId = chunk.gradient; if (!gradients[gradientId]) { throw new Error(`Gradient ID not found: '${gradientId}'`); @@ -1320,7 +1325,9 @@ function setGradientDefinitions(container, gradients) { } } } else { - throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + throw new Error( + `Unsupported gradient type: '${gradientDefinition.type}'`, + ); } // Set common attributes diff --git a/docs/gradient/bundle.js b/docs/gradient/bundle.js index becce964..df55e1fa 100644 --- a/docs/gradient/bundle.js +++ b/docs/gradient/bundle.js @@ -94,7 +94,6 @@ module.exports={ ], "chunks": [ { - "color": [119, 57, 0], "faces": [ [0, 1, 2], [2, 3, 0], @@ -126,7 +125,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [36, 51, 67], "faces": [ [11, 12, 13], [64, 65, 66] @@ -134,7 +132,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [228, 116, 36], "faces": [ [14, 15, 11], [11, 16, 14], @@ -145,7 +142,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [192, 172, 157], "faces": [ [19, 20, 21], [21, 22, 19], @@ -177,7 +173,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [214, 194, 178], "faces": [ [21, 20, 24], [24, 31, 21], @@ -187,7 +182,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [228, 119, 25], "faces": [ [31, 24, 18], [6, 5, 16], @@ -204,7 +198,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [205, 98, 0], "faces": [ [24, 34, 18], [16, 13, 12], @@ -218,7 +211,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [0, 0, 0], "faces": [ [36, 15, 37], [37, 38, 36], @@ -238,7 +230,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [247, 132, 25], "faces": [ [17, 33, 10], [17, 18, 34], @@ -266,7 +257,6 @@ module.exports={ "gradient": "linear1" }, { - "color": [225, 119, 25], "faces": [ [35, 4, 42], [4, 1, 42], @@ -1408,11 +1398,12 @@ function positionsFromModel(positions, modelJson) { function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygons = []; - const polygonsByChunk = modelJson.chunks.map((chunk) => { + const polygonsByChunk = modelJson.chunks.map((chunk, index) => { const { faces } = chunk; return faces.map((face) => { const svgPolygon = createSvgPolygon(chunk, { gradients: modelJson.gradients, + index, }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); @@ -1422,10 +1413,14 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk, { gradients = {} }) { +function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); - if (chunk.gradient) { + if (chunk.gradient && chunk.color) { + throw new Error( + `Both gradient and color for chunk '${index}'. These options are mutually exclusive.`, + ); + } else if (chunk.gradient) { const gradientId = chunk.gradient; if (!gradients[gradientId]) { throw new Error(`Gradient ID not found: '${gradientId}'`); @@ -1713,7 +1708,9 @@ function setGradientDefinitions(container, gradients) { } } } else { - throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + throw new Error( + `Unsupported gradient type: '${gradientDefinition.type}'`, + ); } // Set common attributes diff --git a/docs/normal/bundle.js b/docs/normal/bundle.js index d885dd4c..e7db1cfd 100644 --- a/docs/normal/bundle.js +++ b/docs/normal/bundle.js @@ -1057,11 +1057,12 @@ function positionsFromModel(positions, modelJson) { function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygons = []; - const polygonsByChunk = modelJson.chunks.map((chunk) => { + const polygonsByChunk = modelJson.chunks.map((chunk, index) => { const { faces } = chunk; return faces.map((face) => { const svgPolygon = createSvgPolygon(chunk, { gradients: modelJson.gradients, + index, }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); @@ -1071,10 +1072,14 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk, { gradients = {} }) { +function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); - if (chunk.gradient) { + if (chunk.gradient && chunk.color) { + throw new Error( + `Both gradient and color for chunk '${index}'. These options are mutually exclusive.`, + ); + } else if (chunk.gradient) { const gradientId = chunk.gradient; if (!gradients[gradientId]) { throw new Error(`Gradient ID not found: '${gradientId}'`); @@ -1362,7 +1367,9 @@ function setGradientDefinitions(container, gradients) { } } } else { - throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + throw new Error( + `Unsupported gradient type: '${gradientDefinition.type}'`, + ); } // Set common attributes diff --git a/docs/recolor/bundle.js b/docs/recolor/bundle.js index 659a4cd7..9a113960 100644 --- a/docs/recolor/bundle.js +++ b/docs/recolor/bundle.js @@ -1220,11 +1220,12 @@ function positionsFromModel(positions, modelJson) { function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygons = []; - const polygonsByChunk = modelJson.chunks.map((chunk) => { + const polygonsByChunk = modelJson.chunks.map((chunk, index) => { const { faces } = chunk; return faces.map((face) => { const svgPolygon = createSvgPolygon(chunk, { gradients: modelJson.gradients, + index, }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); @@ -1234,10 +1235,14 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk, { gradients = {} }) { +function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); - if (chunk.gradient) { + if (chunk.gradient && chunk.color) { + throw new Error( + `Both gradient and color for chunk '${index}'. These options are mutually exclusive.`, + ); + } else if (chunk.gradient) { const gradientId = chunk.gradient; if (!gradients[gradientId]) { throw new Error(`Gradient ID not found: '${gradientId}'`); @@ -1525,7 +1530,9 @@ function setGradientDefinitions(container, gradients) { } } } else { - throw new Error(`Unsupported gradent type: '${gradientDefinition.type}'`); + throw new Error( + `Unsupported gradient type: '${gradientDefinition.type}'`, + ); } // Set common attributes diff --git a/example/gradient-fox.json b/example/gradient-fox.json index 5f0271a3..9ea4f37b 100644 --- a/example/gradient-fox.json +++ b/example/gradient-fox.json @@ -93,7 +93,6 @@ ], "chunks": [ { - "color": [119, 57, 0], "faces": [ [0, 1, 2], [2, 3, 0], @@ -125,7 +124,6 @@ "gradient": "linear1" }, { - "color": [36, 51, 67], "faces": [ [11, 12, 13], [64, 65, 66] @@ -133,7 +131,6 @@ "gradient": "linear1" }, { - "color": [228, 116, 36], "faces": [ [14, 15, 11], [11, 16, 14], @@ -144,7 +141,6 @@ "gradient": "linear1" }, { - "color": [192, 172, 157], "faces": [ [19, 20, 21], [21, 22, 19], @@ -176,7 +172,6 @@ "gradient": "linear1" }, { - "color": [214, 194, 178], "faces": [ [21, 20, 24], [24, 31, 21], @@ -186,7 +181,6 @@ "gradient": "linear1" }, { - "color": [228, 119, 25], "faces": [ [31, 24, 18], [6, 5, 16], @@ -203,7 +197,6 @@ "gradient": "linear1" }, { - "color": [205, 98, 0], "faces": [ [24, 34, 18], [16, 13, 12], @@ -217,7 +210,6 @@ "gradient": "linear1" }, { - "color": [0, 0, 0], "faces": [ [36, 15, 37], [37, 38, 36], @@ -237,7 +229,6 @@ "gradient": "linear1" }, { - "color": [247, 132, 25], "faces": [ [17, 33, 10], [17, 18, 34], @@ -265,7 +256,6 @@ "gradient": "linear1" }, { - "color": [225, 119, 25], "faces": [ [35, 4, 42], [4, 1, 42], diff --git a/util.js b/util.js index 8bf5bb9a..8bec03a8 100644 --- a/util.js +++ b/util.js @@ -212,11 +212,12 @@ function positionsFromModel(positions, modelJson) { function createPolygonsFromModelJson(modelJson, createSvgPolygon) { const polygons = []; - const polygonsByChunk = modelJson.chunks.map((chunk) => { + const polygonsByChunk = modelJson.chunks.map((chunk, index) => { const { faces } = chunk; return faces.map((face) => { const svgPolygon = createSvgPolygon(chunk, { gradients: modelJson.gradients, + index, }); const polygon = new Polygon(svgPolygon, face); polygons.push(polygon); @@ -226,10 +227,14 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } -function createStandardModelPolygon(chunk, { gradients = {} }) { +function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); - if (chunk.gradient) { + if (chunk.gradient && chunk.color) { + throw new Error( + `Both gradient and color for chunk '${index}'. These options are mutually exclusive.`, + ); + } else if (chunk.gradient) { const gradientId = chunk.gradient; if (!gradients[gradientId]) { throw new Error(`Gradient ID not found: '${gradientId}'`); From ce1bc75a8736e4252c834a5a09346cd645b1dc4a Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 12 Oct 2021 17:37:31 -0230 Subject: [PATCH 4/5] Add missing `gradientTransform` attribute This attribute was supported by both types of gradient, but it was missing from the list of common attributes. --- util.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/util.js b/util.js index 8bec03a8..48c3db05 100644 --- a/util.js +++ b/util.js @@ -449,7 +449,13 @@ function setGradientDefinitions(container, gradients) { const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; - const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const commonAttributes = [ + 'gradientTransform', + 'gradientUnits', + 'spreadMethod', + 'stops', + 'type', + ]; const allLinearAttributes = [ ...linearCoordinateAttributes, ...commonAttributes, From 0e3fb237fd7eb686bc2856065bd8b81a2cd0b44f Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 12 Oct 2021 17:48:25 -0230 Subject: [PATCH 5/5] Add JSDoc comments JSDoc comments have been added for the two functions that have been modified in this PR. --- docs/beta/bundle.js | 107 +++++++++++++++++++++++++++++++++++++++- docs/distort/bundle.js | 107 +++++++++++++++++++++++++++++++++++++++- docs/gradient/bundle.js | 107 +++++++++++++++++++++++++++++++++++++++- docs/normal/bundle.js | 107 +++++++++++++++++++++++++++++++++++++++- docs/recolor/bundle.js | 107 +++++++++++++++++++++++++++++++++++++++- util.js | 99 +++++++++++++++++++++++++++++++++++++ 6 files changed, 629 insertions(+), 5 deletions(-) diff --git a/docs/beta/bundle.js b/docs/beta/bundle.js index 0f8261e3..0bd43d7b 100644 --- a/docs/beta/bundle.js +++ b/docs/beta/bundle.js @@ -1211,6 +1211,81 @@ module.exports = { Polygon, }; +/** + * A distance measurement used for SVG attributes. A length is specified as a number followed by a + * unit identifier. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length} for further + * information. + * + * @typedef {`${number}${'em' | 'ex' | 'px' | 'in' | 'cm' | 'mm' | 'pt' | 'pc' | '%'}`} SvgLength + */ + +/** + * A definition for a `` SVG element, which defines a color and the position for that color + * on a gradient. This element is always a child of either a `` or + * `` element. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop} for more information + * about the `` element. + * + * @typedef {object} StopDefinition + * @property {number | `${number}%`} [offset] - The location of the gradient stop along the + * gradient vector. + * @property {string} [stop-color] - The color of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop}. + * @property {number} [stop-opacity] - The opacity of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity}. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient} for more + * information about the `` element. + * + * @typedef {object} LinearGradientDefinition + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used. + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'linear'} type - The type of the gradient. + * @property {SvgLength} [x1] - The x coordinate of the starting point of the vector gradient. + * @property {SvgLength} [x2] - The x coordinate of the ending point of the vector gradient. + * @property {SvgLength} [y1] - The y coordinate of the starting point of the vector gradient. + * @property {SvgLength} [y2] - The y coordinate of the ending point of the vector gradient. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient} for more + * information about the `` element. + * + * @typedef {object} RadialGradientDefinition + * @property {SvgLength} [cx] - The x coordinate of the end circle of the radial gradiant. + * @property {SvgLength} [cy] - The y coordinate of the end circle of the radial gradient. + * @property {SvgLength} [fr] - The radius of the start circle of the radial gradient. + * @property {SvgLength} [fx] - The x coordinate of the start circle of the radial gradient. + * @property {SvgLength} [fy] - The y coordinate of the start circle of the radial gradient. + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {SvgLength} [r] - The radius of the end circle of the radial gradient. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'radial'} type - The type of the gradient. + */ + function createLogoViewer( container, renderScene, @@ -1413,6 +1488,21 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } +/** + * Create an SVG ` element. + * + * This polygon is assigned the correct `fill` and `stroke` attributes, according to the chunk + * definition provided. But the `points` attribute is always set to a dummy value, as it gets reset + * later to the correct position during each render loop. + * + * @param {object} chunk - The definition for the chunk of the model this polygon is a part of. + * This includes the color or gradient to apply to the polygon. + * @param {object} options - Polygon options. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [options.gradients] - The set of + * all gradient definitions used in this model. + * @param options.index - The index for the chunk this polygon is found in. + * @returns {Element} The `` SVG element. + */ function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); @@ -1626,6 +1716,15 @@ function Polygon(svg, indices) { this.zIndex = 0; } +/** + * Parse gradient definitions and construct them in the DOM. + * + * Both `` and `` are supported. All gradients get added to a + * `` element that is added as a direct child of the container element. + * + * @param {Element} container - The `` HTML element that the definitions should be added to. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [gradients] - The gradient definitions. + */ function setGradientDefinitions(container, gradients) { if (!gradients || Object.keys(gradients).length === 0) { return; @@ -1635,7 +1734,13 @@ function setGradientDefinitions(container, gradients) { const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; - const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const commonAttributes = [ + 'gradientTransform', + 'gradientUnits', + 'spreadMethod', + 'stops', + 'type', + ]; const allLinearAttributes = [ ...linearCoordinateAttributes, ...commonAttributes, diff --git a/docs/distort/bundle.js b/docs/distort/bundle.js index 7ced2130..e212f3d6 100644 --- a/docs/distort/bundle.js +++ b/docs/distort/bundle.js @@ -828,6 +828,81 @@ module.exports = { Polygon, }; +/** + * A distance measurement used for SVG attributes. A length is specified as a number followed by a + * unit identifier. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length} for further + * information. + * + * @typedef {`${number}${'em' | 'ex' | 'px' | 'in' | 'cm' | 'mm' | 'pt' | 'pc' | '%'}`} SvgLength + */ + +/** + * A definition for a `` SVG element, which defines a color and the position for that color + * on a gradient. This element is always a child of either a `` or + * `` element. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop} for more information + * about the `` element. + * + * @typedef {object} StopDefinition + * @property {number | `${number}%`} [offset] - The location of the gradient stop along the + * gradient vector. + * @property {string} [stop-color] - The color of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop}. + * @property {number} [stop-opacity] - The opacity of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity}. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient} for more + * information about the `` element. + * + * @typedef {object} LinearGradientDefinition + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used. + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'linear'} type - The type of the gradient. + * @property {SvgLength} [x1] - The x coordinate of the starting point of the vector gradient. + * @property {SvgLength} [x2] - The x coordinate of the ending point of the vector gradient. + * @property {SvgLength} [y1] - The y coordinate of the starting point of the vector gradient. + * @property {SvgLength} [y2] - The y coordinate of the ending point of the vector gradient. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient} for more + * information about the `` element. + * + * @typedef {object} RadialGradientDefinition + * @property {SvgLength} [cx] - The x coordinate of the end circle of the radial gradiant. + * @property {SvgLength} [cy] - The y coordinate of the end circle of the radial gradient. + * @property {SvgLength} [fr] - The radius of the start circle of the radial gradient. + * @property {SvgLength} [fx] - The x coordinate of the start circle of the radial gradient. + * @property {SvgLength} [fy] - The y coordinate of the start circle of the radial gradient. + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {SvgLength} [r] - The radius of the end circle of the radial gradient. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'radial'} type - The type of the gradient. + */ + function createLogoViewer( container, renderScene, @@ -1030,6 +1105,21 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } +/** + * Create an SVG ` element. + * + * This polygon is assigned the correct `fill` and `stroke` attributes, according to the chunk + * definition provided. But the `points` attribute is always set to a dummy value, as it gets reset + * later to the correct position during each render loop. + * + * @param {object} chunk - The definition for the chunk of the model this polygon is a part of. + * This includes the color or gradient to apply to the polygon. + * @param {object} options - Polygon options. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [options.gradients] - The set of + * all gradient definitions used in this model. + * @param options.index - The index for the chunk this polygon is found in. + * @returns {Element} The `` SVG element. + */ function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); @@ -1243,6 +1333,15 @@ function Polygon(svg, indices) { this.zIndex = 0; } +/** + * Parse gradient definitions and construct them in the DOM. + * + * Both `` and `` are supported. All gradients get added to a + * `` element that is added as a direct child of the container element. + * + * @param {Element} container - The `` HTML element that the definitions should be added to. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [gradients] - The gradient definitions. + */ function setGradientDefinitions(container, gradients) { if (!gradients || Object.keys(gradients).length === 0) { return; @@ -1252,7 +1351,13 @@ function setGradientDefinitions(container, gradients) { const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; - const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const commonAttributes = [ + 'gradientTransform', + 'gradientUnits', + 'spreadMethod', + 'stops', + 'type', + ]; const allLinearAttributes = [ ...linearCoordinateAttributes, ...commonAttributes, diff --git a/docs/gradient/bundle.js b/docs/gradient/bundle.js index df55e1fa..dc750a03 100644 --- a/docs/gradient/bundle.js +++ b/docs/gradient/bundle.js @@ -1211,6 +1211,81 @@ module.exports = { Polygon, }; +/** + * A distance measurement used for SVG attributes. A length is specified as a number followed by a + * unit identifier. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length} for further + * information. + * + * @typedef {`${number}${'em' | 'ex' | 'px' | 'in' | 'cm' | 'mm' | 'pt' | 'pc' | '%'}`} SvgLength + */ + +/** + * A definition for a `` SVG element, which defines a color and the position for that color + * on a gradient. This element is always a child of either a `` or + * `` element. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop} for more information + * about the `` element. + * + * @typedef {object} StopDefinition + * @property {number | `${number}%`} [offset] - The location of the gradient stop along the + * gradient vector. + * @property {string} [stop-color] - The color of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop}. + * @property {number} [stop-opacity] - The opacity of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity}. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient} for more + * information about the `` element. + * + * @typedef {object} LinearGradientDefinition + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used. + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'linear'} type - The type of the gradient. + * @property {SvgLength} [x1] - The x coordinate of the starting point of the vector gradient. + * @property {SvgLength} [x2] - The x coordinate of the ending point of the vector gradient. + * @property {SvgLength} [y1] - The y coordinate of the starting point of the vector gradient. + * @property {SvgLength} [y2] - The y coordinate of the ending point of the vector gradient. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient} for more + * information about the `` element. + * + * @typedef {object} RadialGradientDefinition + * @property {SvgLength} [cx] - The x coordinate of the end circle of the radial gradiant. + * @property {SvgLength} [cy] - The y coordinate of the end circle of the radial gradient. + * @property {SvgLength} [fr] - The radius of the start circle of the radial gradient. + * @property {SvgLength} [fx] - The x coordinate of the start circle of the radial gradient. + * @property {SvgLength} [fy] - The y coordinate of the start circle of the radial gradient. + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {SvgLength} [r] - The radius of the end circle of the radial gradient. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'radial'} type - The type of the gradient. + */ + function createLogoViewer( container, renderScene, @@ -1413,6 +1488,21 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } +/** + * Create an SVG ` element. + * + * This polygon is assigned the correct `fill` and `stroke` attributes, according to the chunk + * definition provided. But the `points` attribute is always set to a dummy value, as it gets reset + * later to the correct position during each render loop. + * + * @param {object} chunk - The definition for the chunk of the model this polygon is a part of. + * This includes the color or gradient to apply to the polygon. + * @param {object} options - Polygon options. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [options.gradients] - The set of + * all gradient definitions used in this model. + * @param options.index - The index for the chunk this polygon is found in. + * @returns {Element} The `` SVG element. + */ function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); @@ -1626,6 +1716,15 @@ function Polygon(svg, indices) { this.zIndex = 0; } +/** + * Parse gradient definitions and construct them in the DOM. + * + * Both `` and `` are supported. All gradients get added to a + * `` element that is added as a direct child of the container element. + * + * @param {Element} container - The `` HTML element that the definitions should be added to. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [gradients] - The gradient definitions. + */ function setGradientDefinitions(container, gradients) { if (!gradients || Object.keys(gradients).length === 0) { return; @@ -1635,7 +1734,13 @@ function setGradientDefinitions(container, gradients) { const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; - const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const commonAttributes = [ + 'gradientTransform', + 'gradientUnits', + 'spreadMethod', + 'stops', + 'type', + ]; const allLinearAttributes = [ ...linearCoordinateAttributes, ...commonAttributes, diff --git a/docs/normal/bundle.js b/docs/normal/bundle.js index e7db1cfd..6e54b6de 100644 --- a/docs/normal/bundle.js +++ b/docs/normal/bundle.js @@ -870,6 +870,81 @@ module.exports = { Polygon, }; +/** + * A distance measurement used for SVG attributes. A length is specified as a number followed by a + * unit identifier. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length} for further + * information. + * + * @typedef {`${number}${'em' | 'ex' | 'px' | 'in' | 'cm' | 'mm' | 'pt' | 'pc' | '%'}`} SvgLength + */ + +/** + * A definition for a `` SVG element, which defines a color and the position for that color + * on a gradient. This element is always a child of either a `` or + * `` element. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop} for more information + * about the `` element. + * + * @typedef {object} StopDefinition + * @property {number | `${number}%`} [offset] - The location of the gradient stop along the + * gradient vector. + * @property {string} [stop-color] - The color of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop}. + * @property {number} [stop-opacity] - The opacity of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity}. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient} for more + * information about the `` element. + * + * @typedef {object} LinearGradientDefinition + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used. + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'linear'} type - The type of the gradient. + * @property {SvgLength} [x1] - The x coordinate of the starting point of the vector gradient. + * @property {SvgLength} [x2] - The x coordinate of the ending point of the vector gradient. + * @property {SvgLength} [y1] - The y coordinate of the starting point of the vector gradient. + * @property {SvgLength} [y2] - The y coordinate of the ending point of the vector gradient. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient} for more + * information about the `` element. + * + * @typedef {object} RadialGradientDefinition + * @property {SvgLength} [cx] - The x coordinate of the end circle of the radial gradiant. + * @property {SvgLength} [cy] - The y coordinate of the end circle of the radial gradient. + * @property {SvgLength} [fr] - The radius of the start circle of the radial gradient. + * @property {SvgLength} [fx] - The x coordinate of the start circle of the radial gradient. + * @property {SvgLength} [fy] - The y coordinate of the start circle of the radial gradient. + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {SvgLength} [r] - The radius of the end circle of the radial gradient. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'radial'} type - The type of the gradient. + */ + function createLogoViewer( container, renderScene, @@ -1072,6 +1147,21 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } +/** + * Create an SVG ` element. + * + * This polygon is assigned the correct `fill` and `stroke` attributes, according to the chunk + * definition provided. But the `points` attribute is always set to a dummy value, as it gets reset + * later to the correct position during each render loop. + * + * @param {object} chunk - The definition for the chunk of the model this polygon is a part of. + * This includes the color or gradient to apply to the polygon. + * @param {object} options - Polygon options. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [options.gradients] - The set of + * all gradient definitions used in this model. + * @param options.index - The index for the chunk this polygon is found in. + * @returns {Element} The `` SVG element. + */ function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); @@ -1285,6 +1375,15 @@ function Polygon(svg, indices) { this.zIndex = 0; } +/** + * Parse gradient definitions and construct them in the DOM. + * + * Both `` and `` are supported. All gradients get added to a + * `` element that is added as a direct child of the container element. + * + * @param {Element} container - The `` HTML element that the definitions should be added to. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [gradients] - The gradient definitions. + */ function setGradientDefinitions(container, gradients) { if (!gradients || Object.keys(gradients).length === 0) { return; @@ -1294,7 +1393,13 @@ function setGradientDefinitions(container, gradients) { const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; - const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const commonAttributes = [ + 'gradientTransform', + 'gradientUnits', + 'spreadMethod', + 'stops', + 'type', + ]; const allLinearAttributes = [ ...linearCoordinateAttributes, ...commonAttributes, diff --git a/docs/recolor/bundle.js b/docs/recolor/bundle.js index 9a113960..ae148efb 100644 --- a/docs/recolor/bundle.js +++ b/docs/recolor/bundle.js @@ -1033,6 +1033,81 @@ module.exports = { Polygon, }; +/** + * A distance measurement used for SVG attributes. A length is specified as a number followed by a + * unit identifier. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length} for further + * information. + * + * @typedef {`${number}${'em' | 'ex' | 'px' | 'in' | 'cm' | 'mm' | 'pt' | 'pc' | '%'}`} SvgLength + */ + +/** + * A definition for a `` SVG element, which defines a color and the position for that color + * on a gradient. This element is always a child of either a `` or + * `` element. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop} for more information + * about the `` element. + * + * @typedef {object} StopDefinition + * @property {number | `${number}%`} [offset] - The location of the gradient stop along the + * gradient vector. + * @property {string} [stop-color] - The color of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop}. + * @property {number} [stop-opacity] - The opacity of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity}. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient} for more + * information about the `` element. + * + * @typedef {object} LinearGradientDefinition + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used. + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'linear'} type - The type of the gradient. + * @property {SvgLength} [x1] - The x coordinate of the starting point of the vector gradient. + * @property {SvgLength} [x2] - The x coordinate of the ending point of the vector gradient. + * @property {SvgLength} [y1] - The y coordinate of the starting point of the vector gradient. + * @property {SvgLength} [y2] - The y coordinate of the ending point of the vector gradient. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient} for more + * information about the `` element. + * + * @typedef {object} RadialGradientDefinition + * @property {SvgLength} [cx] - The x coordinate of the end circle of the radial gradiant. + * @property {SvgLength} [cy] - The y coordinate of the end circle of the radial gradient. + * @property {SvgLength} [fr] - The radius of the start circle of the radial gradient. + * @property {SvgLength} [fx] - The x coordinate of the start circle of the radial gradient. + * @property {SvgLength} [fy] - The y coordinate of the start circle of the radial gradient. + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {SvgLength} [r] - The radius of the end circle of the radial gradient. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'radial'} type - The type of the gradient. + */ + function createLogoViewer( container, renderScene, @@ -1235,6 +1310,21 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } +/** + * Create an SVG ` element. + * + * This polygon is assigned the correct `fill` and `stroke` attributes, according to the chunk + * definition provided. But the `points` attribute is always set to a dummy value, as it gets reset + * later to the correct position during each render loop. + * + * @param {object} chunk - The definition for the chunk of the model this polygon is a part of. + * This includes the color or gradient to apply to the polygon. + * @param {object} options - Polygon options. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [options.gradients] - The set of + * all gradient definitions used in this model. + * @param options.index - The index for the chunk this polygon is found in. + * @returns {Element} The `` SVG element. + */ function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); @@ -1448,6 +1538,15 @@ function Polygon(svg, indices) { this.zIndex = 0; } +/** + * Parse gradient definitions and construct them in the DOM. + * + * Both `` and `` are supported. All gradients get added to a + * `` element that is added as a direct child of the container element. + * + * @param {Element} container - The `` HTML element that the definitions should be added to. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [gradients] - The gradient definitions. + */ function setGradientDefinitions(container, gradients) { if (!gradients || Object.keys(gradients).length === 0) { return; @@ -1457,7 +1556,13 @@ function setGradientDefinitions(container, gradients) { const linearCoordinateAttributes = ['x1', 'x2', 'y1', 'y2']; const radialCoordinateAttributes = ['cx', 'cy', 'fr', 'fx', 'fy', 'r']; - const commonAttributes = ['gradientUnits', 'spreadMethod', 'stops', 'type']; + const commonAttributes = [ + 'gradientTransform', + 'gradientUnits', + 'spreadMethod', + 'stops', + 'type', + ]; const allLinearAttributes = [ ...linearCoordinateAttributes, ...commonAttributes, diff --git a/util.js b/util.js index 48c3db05..027af70d 100644 --- a/util.js +++ b/util.js @@ -25,6 +25,81 @@ module.exports = { Polygon, }; +/** + * A distance measurement used for SVG attributes. A length is specified as a number followed by a + * unit identifier. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#length} for further + * information. + * + * @typedef {`${number}${'em' | 'ex' | 'px' | 'in' | 'cm' | 'mm' | 'pt' | 'pc' | '%'}`} SvgLength + */ + +/** + * A definition for a `` SVG element, which defines a color and the position for that color + * on a gradient. This element is always a child of either a `` or + * `` element. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop} for more information + * about the `` element. + * + * @typedef {object} StopDefinition + * @property {number | `${number}%`} [offset] - The location of the gradient stop along the + * gradient vector. + * @property {string} [stop-color] - The color of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop}. + * @property {number} [stop-opacity] - The opacity of the gradient stop. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity}. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient} for more + * information about the `` element. + * + * @typedef {object} LinearGradientDefinition + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used. + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'linear'} type - The type of the gradient. + * @property {SvgLength} [x1] - The x coordinate of the starting point of the vector gradient. + * @property {SvgLength} [x2] - The x coordinate of the ending point of the vector gradient. + * @property {SvgLength} [y1] - The y coordinate of the starting point of the vector gradient. + * @property {SvgLength} [y2] - The y coordinate of the ending point of the vector gradient. + */ + +/** + * A definition for a `` SVG element. This definition includes all supported + * `` attributes, and it includes a `stops` property which is an array of + * definitions for each `` child node. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient} for more + * information about the `` element. + * + * @typedef {object} RadialGradientDefinition + * @property {SvgLength} [cx] - The x coordinate of the end circle of the radial gradiant. + * @property {SvgLength} [cy] - The y coordinate of the end circle of the radial gradient. + * @property {SvgLength} [fr] - The radius of the start circle of the radial gradient. + * @property {SvgLength} [fx] - The x coordinate of the start circle of the radial gradient. + * @property {SvgLength} [fy] - The y coordinate of the start circle of the radial gradient. + * @property {string} [gradientTransform] - A transform from the gradient coordinate system to the + * target coordinate system. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform}. + * @property {'userSpaceOnUse' | 'objectBoundingBox'} [gradientUnits] - The coordinate system used + * for the coordinate attributes. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits}. + * @property {SvgLength} [r] - The radius of the end circle of the radial gradient. + * @property {'pad' | 'reflect' | 'repeat'} [spreadMethod] - The method used to fill a shape beyond + * the defined edges of a gradient. See {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod}. + * @property {StopDefinition[]} [stops] - The colors of the gradient, and the position of each + * color along the gradient vector. + * @property {'radial'} type - The type of the gradient. + */ + function createLogoViewer( container, renderScene, @@ -227,6 +302,21 @@ function createPolygonsFromModelJson(modelJson, createSvgPolygon) { return { polygons, polygonsByChunk }; } +/** + * Create an SVG ` element. + * + * This polygon is assigned the correct `fill` and `stroke` attributes, according to the chunk + * definition provided. But the `points` attribute is always set to a dummy value, as it gets reset + * later to the correct position during each render loop. + * + * @param {object} chunk - The definition for the chunk of the model this polygon is a part of. + * This includes the color or gradient to apply to the polygon. + * @param {object} options - Polygon options. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [options.gradients] - The set of + * all gradient definitions used in this model. + * @param options.index - The index for the chunk this polygon is found in. + * @returns {Element} The `` SVG element. + */ function createStandardModelPolygon(chunk, { gradients = {}, index }) { const svgPolygon = createNode('polygon'); @@ -440,6 +530,15 @@ function Polygon(svg, indices) { this.zIndex = 0; } +/** + * Parse gradient definitions and construct them in the DOM. + * + * Both `` and `` are supported. All gradients get added to a + * `` element that is added as a direct child of the container element. + * + * @param {Element} container - The `` HTML element that the definitions should be added to. + * @param {(LinearGradientDefinition | RadialGradientDefinition)[]} [gradients] - The gradient definitions. + */ function setGradientDefinitions(container, gradients) { if (!gradients || Object.keys(gradients).length === 0) { return;