@@ -693,20 +693,25 @@ const {
693693 createModelRenderer,
694694 createNode,
695695 setAttribute,
696+ setGradientDefinitions,
696697} = require ( './util' ) ;
697698
698699module . exports = createLogo ;
699700
700701function createLogo ( options = { } ) {
701702 const cameraDistance = options . cameraDistance || 400 ;
702703 const { height, width } = calculateSizingOptions ( options ) ;
704+ const meshJson = options . meshJson || foxJson ;
703705
704706 const container = createNode ( 'svg' ) ;
705707 setAttribute ( container , 'width' , `${ width } px` ) ;
706708 setAttribute ( container , 'height' , `${ height } px` ) ;
707709 document . body . appendChild ( container ) ;
708710
709- const modelObj = loadModelFromJson ( options . meshJson || foxJson ) ;
711+ setGradientDefinitions ( container , meshJson . gradients ) ;
712+ setMaskDefinitions ( container , meshJson . masks , height , width ) ;
713+
714+ const modelObj = loadModelFromJson ( meshJson ) ;
710715 const renderFox = createModelRenderer ( container , cameraDistance , modelObj ) ;
711716 const renderScene = ( lookCurrent , slowDrift ) => {
712717 const rect = container . getBoundingClientRect ( ) ;
@@ -720,6 +725,27 @@ function createLogo(options = {}) {
720725 ) ;
721726}
722727
728+ function setMaskDefinitions ( container , masks , height , width ) {
729+ for ( const [ maskId , maskDefinition ] of Object . entries ( masks ) ) {
730+ const mask = createNode ( 'mask' ) ;
731+ setAttribute ( mask , 'id' , maskId ) ;
732+
733+ const maskedRect = createNode ( 'rect' ) ;
734+
735+ // Extend mask beyond container to ensure it completely covers the model.
736+ // The model can extend beyond the container as well.
737+ setAttribute ( maskedRect , 'width' , width * 1.5 ) ;
738+ setAttribute ( maskedRect , 'height' , height * 1.5 ) ;
739+ setAttribute ( maskedRect , 'x' , `-${ Math . floor ( width / 4 ) } ` ) ;
740+ setAttribute ( maskedRect , 'y' , `-${ Math . floor ( height / 4 ) } ` ) ;
741+
742+ setAttribute ( maskedRect , 'fill' , maskDefinition . maskColor ) ;
743+ mask . appendChild ( maskedRect ) ;
744+
745+ container . appendChild ( mask ) ;
746+ }
747+ }
748+
723749} , { "./fox.json" :3 , "./util" :14 } ] , 5 :[ function ( require , module , exports ) {
724750'use strict' ;
725751
@@ -1202,6 +1228,7 @@ module.exports = {
12021228 createFaceUpdater,
12031229 createNode,
12041230 setAttribute,
1231+ setGradientDefinitions,
12051232 svgElementToSvgImageContent,
12061233 Polygon,
12071234} ;
@@ -1375,7 +1402,7 @@ function createModelRenderer(container, cameraDistance, modelObj) {
13751402 return ( rect , lookPos , slowDrift ) => {
13761403 const matrix = computeMatrix ( rect , lookPos , slowDrift ) ;
13771404 updatePositions ( matrix ) ;
1378- updateFaces ( rect , container , polygons , transformed ) ;
1405+ updateFaces ( rect ) ;
13791406 } ;
13801407}
13811408
@@ -1394,22 +1421,51 @@ function positionsFromModel(positions, modelJson) {
13941421function createPolygonsFromModelJson ( modelJson , createSvgPolygon ) {
13951422 const polygons = [ ] ;
13961423 const polygonsByChunk = modelJson . chunks . map ( ( chunk ) => {
1397- const { faces } = chunk ;
1424+ const { faces, mask : maskId } = chunk ;
13981425 return faces . map ( ( face ) => {
1399- const svgPolygon = createSvgPolygon ( chunk ) ;
1400- const polygon = new Polygon ( svgPolygon , face ) ;
1426+ const svgPolygon = createSvgPolygon ( chunk , {
1427+ gradients : modelJson . gradients ,
1428+ } ) ;
1429+ let maskPolygon ;
1430+ if ( maskId ) {
1431+ if ( modelJson . masks [ maskId ] === undefined ) {
1432+ throw new Error ( `Unrecognized mask ID: '${ maskId } '` ) ;
1433+ }
1434+
1435+ maskPolygon = createSvgPolygon ( chunk , {
1436+ gradients : modelJson . gradients ,
1437+ mask : modelJson . masks [ maskId ] ,
1438+ } ) ;
1439+ }
1440+ const polygon = new Polygon ( svgPolygon , face , maskPolygon ) ;
14011441 polygons . push ( polygon ) ;
14021442 return polygon ;
14031443 } ) ;
14041444 } ) ;
14051445 return { polygons, polygonsByChunk } ;
14061446}
14071447
1408- function createStandardModelPolygon ( chunk ) {
1409- const color = `rgb(${ chunk . color } )` ;
1448+ function createStandardModelPolygon ( chunk , { gradients = { } , mask } ) {
14101449 const svgPolygon = createNode ( 'polygon' ) ;
1411- setAttribute ( svgPolygon , 'fill' , color ) ;
1412- setAttribute ( svgPolygon , 'stroke' , color ) ;
1450+
1451+ if ( mask ) {
1452+ setAttribute ( svgPolygon , 'mask' , `url('#${ chunk . mask } ')` ) ;
1453+ setAttribute ( svgPolygon , 'fill' , mask . modelColor ) ;
1454+ setAttribute ( svgPolygon , 'stroke' , mask . modelColor ) ;
1455+ } else if ( chunk . gradient ) {
1456+ const gradientId = chunk . gradient ;
1457+ if ( ! gradients [ gradientId ] ) {
1458+ throw new Error ( `Gradient ID not found: '${ gradientId } '` ) ;
1459+ }
1460+
1461+ setAttribute ( svgPolygon , 'fill' , `url('#${ gradientId } ')` ) ;
1462+ setAttribute ( svgPolygon , 'stroke' , `url('#${ gradientId } ')` ) ;
1463+ } else {
1464+ const fill = `rgb(${ chunk . color } )` ;
1465+ setAttribute ( svgPolygon , 'fill' , fill ) ;
1466+ setAttribute ( svgPolygon , 'stroke' , fill ) ;
1467+ }
1468+
14131469 setAttribute ( svgPolygon , 'points' , '0,0, 10,0, 0,10' ) ;
14141470 return svgPolygon ;
14151471}
@@ -1533,7 +1589,6 @@ function createFaceUpdater(container, polygons, transformed) {
15331589 const points = [ ] ;
15341590 let zmax = - Infinity ;
15351591 let zmin = Infinity ;
1536- const element = poly . svg ;
15371592 for ( let j = 0 ; j < 3 ; ++ j ) {
15381593 const idx = indices [ j ] ;
15391594 points . push (
@@ -1545,20 +1600,26 @@ function createFaceUpdater(container, polygons, transformed) {
15451600 zmax = Math . max ( zmax , z ) ;
15461601 zmin = Math . min ( zmin , z ) ;
15471602 }
1603+
15481604 poly . zIndex = zmax + 0.25 * zmin ;
15491605 const joinedPoints = points . join ( ' ' ) ;
15501606
15511607 if ( joinedPoints . indexOf ( 'NaN' ) === - 1 ) {
1552- setAttribute ( element , 'points' , joinedPoints ) ;
1608+ setAttribute ( poly . svg , 'points' , joinedPoints ) ;
1609+ if ( poly . maskSvg ) {
1610+ setAttribute ( poly . maskSvg , 'points' , joinedPoints ) ;
1611+ }
15531612 }
1554-
15551613 toDraw . push ( poly ) ;
15561614 }
15571615 toDraw . sort ( compareZ ) ;
1558- container . innerHTML = '' ;
1559- for ( i = 0 ; i < toDraw . length ; ++ i ) {
1560- container . appendChild ( toDraw [ i ] . svg ) ;
1561- }
1616+
1617+ const newPolygons = toDraw
1618+ . map ( ( poly ) => [ poly . svg , ...( poly . maskSvg ? [ poly . maskSvg ] : [ ] ) ] )
1619+ . flat ( ) ;
1620+ const defs = container . getElementsByTagName ( 'defs' ) ;
1621+ const maskChildren = container . getElementsByTagName ( 'mask' ) ;
1622+ container . replaceChildren ( ...defs , ...maskChildren , ...newPolygons ) ;
15621623 } ;
15631624}
15641625
@@ -1596,10 +1657,173 @@ function svgElementToSvgImageContent(svgElement) {
15961657 return content ;
15971658}
15981659
1599- function Polygon ( svg , indices ) {
1660+ function Polygon ( svg , indices , maskSvg ) {
16001661 this . svg = svg ;
16011662 this . indices = indices ;
16021663 this . zIndex = 0 ;
1664+ if ( maskSvg ) {
1665+ this . maskSvg = maskSvg ;
1666+ }
1667+ }
1668+
1669+ function setGradientDefinitions ( container , gradients ) {
1670+ if ( ! gradients || Object . keys ( gradients ) . length === 0 ) {
1671+ return ;
1672+ }
1673+
1674+ const defsContainer = createNode ( 'defs' ) ;
1675+
1676+ for ( const [ gradientId , gradientDefinition ] of Object . entries ( gradients ) ) {
1677+ let gradient ;
1678+ if ( gradientDefinition . type === 'linear' ) {
1679+ gradient = createNode ( 'linearGradient' ) ;
1680+
1681+ if ( gradientDefinition . href !== undefined ) {
1682+ if ( gradients [ gradientDefinition . href ] === undefined ) {
1683+ throw new Error (
1684+ `Gradient 'href' set to unrecognized gradient ID: '${ gradientDefinition . href } '` ,
1685+ ) ;
1686+ } else if ( gradients [ gradientDefinition . href ] . type !== 'linear' ) {
1687+ throw new Error (
1688+ `Linear gradiants can only extend other linear gradiants. Attribute 'href' has been set to gradient ID '${ gradientDefinition . href } ', which not a linear gradiant.` ,
1689+ ) ;
1690+ }
1691+ setAttribute ( gradient , 'href' , `url('#${ gradientDefinition . href } ')` ) ;
1692+ }
1693+ const coordinateAttributes = [ 'x1' , 'x2' , 'y1' , 'y2' ] ;
1694+ if (
1695+ coordinateAttributes . some (
1696+ ( attributeName ) => gradientDefinition [ attributeName ] !== undefined ,
1697+ )
1698+ ) {
1699+ const missingAttributes = coordinateAttributes . filter (
1700+ ( attributeName ) => gradientDefinition [ attributeName ] === undefined ,
1701+ ) ;
1702+ if ( missingAttributes . length > 0 ) {
1703+ throw new Error (
1704+ `Missing coordinate attributes: '${ missingAttributes . join ( ', ' ) } '` ,
1705+ ) ;
1706+ }
1707+
1708+ for ( const attribute of coordinateAttributes ) {
1709+ if ( typeof gradientDefinition [ attribute ] !== 'string' ) {
1710+ throw new Error (
1711+ `Type of '${ attribute } ' option expected to be 'string'. Instead received type '${ typeof gradientDefinition [
1712+ attribute
1713+ ] } '`,
1714+ ) ;
1715+ }
1716+ setAttribute ( gradient , attribute , gradientDefinition [ attribute ] ) ;
1717+ }
1718+ }
1719+ } else if ( gradientDefinition . type === 'radial' ) {
1720+ gradient = createNode ( 'radialGradient' ) ;
1721+
1722+ if ( gradientDefinition . href !== undefined ) {
1723+ if ( gradients [ gradientDefinition . href ] === undefined ) {
1724+ throw new Error (
1725+ `Gradient 'href' set to unrecognized gradient ID: '${ gradientDefinition . href } '` ,
1726+ ) ;
1727+ } else if ( gradients [ gradientDefinition . href ] . type !== 'radial' ) {
1728+ throw new Error (
1729+ `Radial gradiants can only extend other radial gradiants. Attribute 'href' has been set to gradient ID '${ gradientDefinition . href } ', which not a radial gradiant.` ,
1730+ ) ;
1731+ }
1732+ setAttribute ( gradient , 'href' , `url('#${ gradientDefinition . href } ')` ) ;
1733+ }
1734+ const coordinateAttributes = [ 'cx' , 'cy' , 'fr' , 'fx' , 'fy' , 'r' ] ;
1735+ const presentCoordinateAttributes = coordinateAttributes . filter (
1736+ ( attributeName ) => gradientDefinition [ attributeName ] !== undefined ,
1737+ ) ;
1738+ if ( presentCoordinateAttributes . length > 0 ) {
1739+ for ( const attribute of presentCoordinateAttributes ) {
1740+ if ( typeof gradientDefinition [ attribute ] !== 'string' ) {
1741+ throw new Error (
1742+ `Type of '${ attribute } ' option expected to be 'string'. Instead received type '${ typeof gradientDefinition [
1743+ attribute
1744+ ] } '`,
1745+ ) ;
1746+ }
1747+ setAttribute ( gradient , attribute , gradientDefinition [ attribute ] ) ;
1748+ }
1749+ }
1750+ } else {
1751+ throw new Error ( `Unsupported gradent type: '${ gradientDefinition . type } '` ) ;
1752+ }
1753+
1754+ // Set common attributes
1755+ setAttribute ( gradient , 'id' , gradientId ) ;
1756+ if ( gradientDefinition . gradientUnits !== undefined ) {
1757+ if (
1758+ ! [ 'userSpaceOnUse' , 'objectBoundingBox' ] . includes (
1759+ gradientDefinition . gradientUnits ,
1760+ )
1761+ ) {
1762+ throw new Error (
1763+ `Unrecognized value for 'gradientUnits' attribute: '${ gradientDefinition . gradientUnits } '` ,
1764+ ) ;
1765+ }
1766+ setAttribute ( gradient , 'gradientUnits' , gradientDefinition . gradientUnits ) ;
1767+ }
1768+
1769+ if ( gradientDefinition . gradientTransform !== undefined ) {
1770+ if ( typeof gradientDefinition . gradientTransform !== 'string' ) {
1771+ throw new Error (
1772+ `Type of 'gradientTransform' option expected to be 'string'. Instead received type '${ typeof gradientDefinition . gradientTransform } '` ,
1773+ ) ;
1774+ }
1775+
1776+ setAttribute (
1777+ gradient ,
1778+ 'gradientTransform' ,
1779+ gradientDefinition . gradientTransform ,
1780+ ) ;
1781+ }
1782+
1783+ if ( gradientDefinition . spreadMethod !== undefined ) {
1784+ if (
1785+ ! [ 'pad' , 'reflect' , 'repeat' ] . includes ( gradientDefinition . spreadMethod )
1786+ ) {
1787+ throw new Error (
1788+ `Unrecognized value for 'spreadMethod' attribute: '${ gradientDefinition . spreadMethod } '` ,
1789+ ) ;
1790+ }
1791+ setAttribute ( gradient , 'spreadMethod' , gradientDefinition . spreadMethod ) ;
1792+ }
1793+
1794+ if ( gradientDefinition . stops !== undefined ) {
1795+ if ( ! Array . isArray ( gradientDefinition . stops ) ) {
1796+ throw new Error ( `The 'stop' attribute must be an array` ) ;
1797+ }
1798+
1799+ for ( const stopDefinition of gradientDefinition . stops ) {
1800+ if ( typeof stopDefinition !== 'object' ) {
1801+ throw new Error (
1802+ `Each entry in the 'stop' attribute must be an object. Instead received type '${ typeof stopDefinition } '` ,
1803+ ) ;
1804+ }
1805+ const stop = createNode ( 'stop' ) ;
1806+
1807+ if ( stopDefinition . offset !== undefined ) {
1808+ setAttribute ( stop , 'offset' , stopDefinition . offset ) ;
1809+ }
1810+
1811+ if ( stopDefinition [ 'stop-color' ] !== undefined ) {
1812+ setAttribute ( stop , 'stop-color' , stopDefinition [ 'stop-color' ] ) ;
1813+ }
1814+
1815+ if ( stopDefinition [ 'stop-opacity' ] !== undefined ) {
1816+ setAttribute ( stop , 'stop-opacity' , stopDefinition [ 'stop-opacity' ] ) ;
1817+ }
1818+
1819+ gradient . appendChild ( stop ) ;
1820+ }
1821+ }
1822+
1823+ defsContainer . appendChild ( gradient ) ;
1824+ }
1825+
1826+ container . appendChild ( defsContainer ) ;
16031827}
16041828
16051829} , { "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 ] ) ;
0 commit comments