"use strict"
/**
* Builds rectangular frames with varying corner details
* @memberof models.profiles
* @namespace frameRect
*/
const rectangularFrameInit = ({ lib, swLib }) => {
const { circle, rectangle } = lib.primitives
const { rotate, align, mirror } = lib.transforms
const { subtract, union } = lib.booleans
const { measureDimensions } = lib.measurements
const { TAU } = lib.maths.constants
const { position } = swLib.core
const { profiles } = swLib.models
// Defining corner styles and available tag options
const cornerStyleTypes = ['round', 'tri', 'rect', 'ellipse', 'cornerBez']
const cStyleDefault = { inv: false, inset: true, offset: false }
const cStRound = { inv: true, offset: true }
const cStRect = { inv: true, offset: true }
const cStEllipse = { ...cStRect }
const cStCornerBez = { inv: true }
const cRoundStyles = {
round: {
id: 'round',
...cStyleDefault,
...cStRound,
func: ({ radius }) => {
return circle({ radius })
}
},
}
const cTriStyles = {
tri45deg: {
id: 'tri45deg',
...cStyleDefault,
func: profiles.triangle.right45
},
tri30deg: {
id: 'tri30deg',
...cStyleDefault,
func: profiles.triangle.right30
},
triGolden: {
id: 'triGolden',
...cStyleDefault,
func: profiles.triangle.rightGolden
},
triSilver: {
id: 'triSilver',
...cStyleDefault,
func: profiles.triangle.rightSilver
},
triBronze: {
id: 'triBronze',
...cStyleDefault,
func: profiles.triangle.rightBronze
},
triCopper: {
id: 'triCopper',
...cStyleDefault,
func: profiles.triangle.rightCopper
},
}
const cStDefRect = {
...cStyleDefault,
...cStRect,
}
const cRectStyles = {
rectGolden: {
id: 'rectGolden',
...cStDefRect,
func: profiles.rectangle.golden
},
rectSixtyThirty: {
id: 'rectSixtyThirty',
...cStDefRect,
func: profiles.rectangle.sixtyThirty
},
rectSilver: {
id: 'rectSilver',
...cStDefRect,
func: profiles.rectangle.silver
},
rectBronze: {
id: 'rectBronze',
...cStDefRect,
func: profiles.rectangle.bronze
},
rectCopper: {
id: 'rectCopper',
...cStDefRect,
func: profiles.rectangle.copper
},
rectSuperGolden: {
id: 'rectSuperGolden',
...cStDefRect,
func: profiles.rectangle.superGolden
},
rectPlastic: {
id: 'rectPlastic',
...cStDefRect,
func: profiles.rectangle.plastic
},
}
const cStDefEllipse = {
...cStyleDefault,
...cStEllipse
}
const cEllipseStyles = {
ellipseGolden: {
id: 'ellipseGolden',
...cStDefEllipse,
func: profiles.ellipse.golden
},
ellipseSixtyThirty: {
id: 'ellipseSixtyThirty',
...cStDefEllipse,
func: profiles.ellipse.sixtyThirty
},
ellipseSilver: {
id: 'ellipseSilver',
...cStDefEllipse,
func: profiles.ellipse.silver
},
ellipseBronze: {
id: 'ellipseBronze',
...cStDefEllipse,
func: profiles.ellipse.bronze
},
ellipseCopper: {
id: 'ellipseCopper',
...cStDefEllipse,
func: profiles.ellipse.copper
},
ellipseSuperGolden: {
id: 'ellipseSuperGolden',
...cStDefEllipse,
func: profiles.ellipse.superGolden
},
ellipsePlastic: {
id: 'ellipsePlastic',
...cStDefEllipse,
func: profiles.ellipse.plastic
},
}
const cStDefCornBez = {
...cStyleDefault,
...cStCornerBez,
}
const cCornerBezStyles = {
cornerBezGolden: {
id: 'cornerBezGolden',
...cStDefCornBez,
func: profiles.curves.rightCorner.golden
},
cornerBezSixtyThirty: {
id: 'cornerBezSixtyThirty',
...cStDefCornBez,
func: profiles.curves.rightCorner.sixtyThirty
},
cornerBezSilver: {
id: 'cornerBezSilver',
...cStDefCornBez,
func: profiles.curves.rightCorner.silver
},
cornerBezBronze: {
id: 'cornerBezBronze',
...cStDefCornBez,
func: profiles.curves.rightCorner.bronze
},
cornerBezCopper: {
id: 'cornerBezCopper',
...cStDefCornBez,
func: profiles.curves.rightCorner.copper
},
cornerBezSuperGolden: {
id: 'cornerBezSuperGolden',
...cStDefCornBez,
func: profiles.curves.rightCorner.superGolden
},
cornerBezPlastic: {
id: 'cornerBezPlastic',
...cStDefCornBez,
func: profiles.curves.rightCorner.plastic
},
}
const cornerStyles = {
...cRoundStyles,
...cTriStyles,
...cRectStyles,
...cEllipseStyles,
...cCornerBezStyles,
}
const rectangularFrameDefOpts = {
direction: 'in',
frameWidth: 5,
cornerOpts: {
style: 'round',
radius: 2.5,
},
cornerStyle: cRoundStyles.round,
}
rectangularFrameDefOpts.outCornerOpts = rectangularFrameDefOpts.cornerOpts
/**
* Builds a shaped rectangle with the given corner options
* @memberof models.profiles.frameRect
* @param {object} opts
* @param {number[]} opts.size
* @param {object} opts.cornerOpts - options for interior side
* @param {string} opts.cornerOpts.style - profile type, like "tri45deg", "rectSixtyThirty", "ellipseGolden", "cornerBezSilver"
* @param {number} opts.cornerOpts.size
* @param {number} opts.cornerOpts.length
* @param {number} opts.cornerOpts.width
* @param {number} opts.cornerOpts.radius
* @param {string} opts.cornerOpts.longAxis
* @param {string} opts.cornerOpts.opt - Option for each corner. Choose between "inv", "inset", "offset"
* @returns ...
*/
const shapedRectangle = ({
size,
cornerOpts = rectangularFrameDefOpts.cornerOpts,
}) => {
let outRect = rectangle({ size })
const outRectCoords = position.getGeomCoords(outRect)
const outRectCorners = position.rectangle.getRectangleCorners(outRect)
const cornerStyle = cornerStyles[cornerOpts.style]
const cornerPieces = []
const notches = []
if (cornerStyle) {
const styleType = cornerStyleTypes.find(cStyleType => cornerStyle.id.startsWith(cStyleType))
const cornerPiece = cornerStyle.func(cornerOpts)
const cornerPieceDims = measureDimensions(cornerPiece)
let baseNotch = rectangle({ size: [cornerPieceDims[0] / 2, cornerPieceDims[1] / 2] })
if (['tri', 'cornerBez'].includes(styleType)) {
baseNotch = rectangle({ size: [cornerPieceDims[0] - 0.01, cornerPieceDims[1] - 0.01] })
}
Object.entries(outRectCorners).forEach(([cName, cPt]) => {
let inPieceRotation = null
let inPieceAlign = ['center', 'center', 'center']
let inPieceMirror = null
if (cornerOpts.longAxis == 'y') {
if (styleType == 'tri') {
inPieceRotation = [0, 0, 0]
} else {
inPieceRotation = [0, 0, TAU / 4]
}
} else {
if (styleType == 'tri') {
inPieceRotation = [0, 0, TAU / 4]
} else {
inPieceRotation = [0, 0, 0]
}
}
if (cName == 'c4') {
// (-X, -Y)
inPieceAlign = ['min', 'min', 'center']
if (styleType == 'cornerBez') {
inPieceRotation = [inPieceRotation[0], inPieceRotation[1], inPieceRotation[2]]
inPieceMirror = cornerOpts.longAxis == 'y' ? null : [0, 1, 0]
}
if (styleType == 'tri') {
inPieceRotation = [inPieceRotation[0], inPieceRotation[1], inPieceRotation[2]]
inPieceMirror = cornerOpts.longAxis == 'y' ? [0, 1, 0] : null
}
} else if (cName == 'c3') {
// (-X, +Y)
inPieceAlign = ['min', 'max', 'center']
if (styleType == 'cornerBez') {
inPieceRotation = cornerOpts.longAxis == 'y' ?
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2] + TAU / 2] :
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2]]
inPieceMirror = cornerOpts.longAxis == 'y' ? [1, 0, 0] : null
}
if (styleType == 'tri') {
inPieceRotation = cornerOpts.longAxis == 'y' ?
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2]] :
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2] + TAU / 2]
inPieceMirror = cornerOpts.longAxis == 'y' ? null : [1, 0, 0]
}
} else if (cName == 'c2') {
// (+X, +Y)
inPieceAlign = ['max', 'max', 'center']
if (styleType == 'cornerBez') {
inPieceRotation = [inPieceRotation[0], inPieceRotation[1], inPieceRotation[2] + TAU / 2]
inPieceMirror = cornerOpts.longAxis == 'y' ? null : [0, 1, 0]
}
if (styleType == 'tri') {
inPieceRotation = [inPieceRotation[0], inPieceRotation[1], inPieceRotation[2] + TAU / 2]
inPieceMirror = cornerOpts.longAxis == 'y' ? [0, 1, 0] : null
}
} else {
// defaults to c1 (+X, -Y)
inPieceAlign = ['max', 'min', 'center']
if (styleType == 'cornerBez') {
inPieceRotation = cornerOpts.longAxis == 'y' ?
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2]] :
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2] + TAU / 2]
inPieceMirror = cornerOpts.longAxis == 'y' ? [1, 0, 0] : null
}
if (styleType == 'tri') {
inPieceRotation = cornerOpts.longAxis == 'y' ?
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2] + TAU / 2] :
[inPieceRotation[0], inPieceRotation[1], inPieceRotation[2]]
inPieceMirror = cornerOpts.longAxis == 'y' ? null : [1, 0, 0]
}
}
const rotatedNotch = rotate(inPieceRotation, baseNotch)
const newNotch = align(
{ modes: inPieceAlign, relativeTo: cPt },
rotatedNotch
)
notches.push(newNotch)
let rotatedCornerPiece = rotate(inPieceRotation, cornerPiece)
if (inPieceMirror) {
rotatedCornerPiece = mirror({ normal: inPieceMirror }, rotatedCornerPiece)
}
const newCornerPiece = align(
{ modes: inPieceAlign, relativeTo: cPt },
rotatedCornerPiece
)
cornerPieces.push(newCornerPiece)
})
outRect = subtract(outRect, ...notches)
outRect = union(outRect, ...cornerPieces)
}
return outRect
}
/**
* Builds a rectangular frame with the given corner options (interior and exterior)
* @memberof models.profiles.frameRect
* @param {object} opts
* @param {number[]} opts.size - Size of interior space
* @param {string} opts.direction - Where ornaments are applied. Choose between "in" (default), "out", or "both"
* @param {number} opts.frameWidth - Width of frame around interior space
* @param {object} opts.cornerOpts - options for interior side
* @param {string} opts.cornerOpts.style - profile type, like "tri45deg", "rectSixtyThirty", "ellipseGolden", "cornerBezSilver"
* @param {number} opts.cornerOpts.size
* @param {number} opts.cornerOpts.length
* @param {number} opts.cornerOpts.width
* @param {number} opts.cornerOpts.radius
* @param {string} opts.cornerOpts.longAxis
* @param {string} opts.cornerOpts.opt - Option for each corner. Choose between "inv", "inset", "offset"
* @param {object} opts.outCornerOpts - options for exterior side
* @returns ...
*/
const rectangularFrame = ({
size,
direction = rectangularFrameDefOpts.direction,
frameWidth = rectangularFrameDefOpts.frameWidth,
cornerOpts = rectangularFrameDefOpts.cornerOpts,
outCornerOpts = rectangularFrameDefOpts.outCornerOpts,
}) => {
const specs = {
totalSize: [
frameWidth * 2 + size[0],
frameWidth * 2 + size[1],
],
longAxis: cornerOpts.longAxis || position.findLongAxis(size)
}
const iCornOpts = { ...cornerOpts, longAxis: specs.longAxis }
const oCornOpts = { ...outCornerOpts, longAxis: specs.longAxis }
const inRectBase = rectangle({ size })
const inCornerStyle = cornerStyles[iCornOpts.style]
const outRectBase = rectangle({ size: specs.totalSize })
const outCornerStyle = cornerStyles[oCornOpts.style]
let inRect = inRectBase
let outRect = outRectBase
if (direction != 'out' && inCornerStyle) {
inRect = shapedRectangle({
size,
cornerOpts: iCornOpts,
})
}
if (direction != 'in' && outCornerStyle) {
outRect = shapedRectangle({
size: specs.totalSize,
cornerOpts: oCornOpts,
})
}
return subtract(outRect, inRect);
}
return {
rectangularFrame,
}
}
module.exports = { init: rectangularFrameInit };