Source: details/moulds.js

"use strict"

/**
 * Builds positive mouldings and negative moulds for various ornaments.
 * These would then be subtracted from a shape to produce the final result.
 * Input 2D profiles must be centred at (0, 0, 0)
 * @namespace details.moulds
 */

const mouldBuilder = ({ lib }) => {
  const { measureBoundingBox } = lib.measurements
  const { extrudeLinear, extrudeRotate } = lib.extrusions
  const { union, intersect } = lib.booleans
  const { rotate, align, translate, mirror } = lib.transforms
  const { cuboid, cylinder } = lib.primitives

  /**
   * Builds a cuboid with given 2D profile placed on one edge.
   * @memberof details.moulds
   * @instance
   * @param {Object} opts
   * @param {number[]} opts.size - size (x, y, z)
   * @param {string} opts.alignment - where to align when profile size differs from
   *     base cuboid ('top' | 'middle' | 'bottom'). Defaults to 'middle'
   * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile
   */
  const cuboidMouldingOneEdge = (opts, geomProfile) => {
    const profileBbox = measureBoundingBox(geomProfile);
    const profileSize = [profileBbox[1][0] - profileBbox[0][0], profileBbox[1][1] - profileBbox[0][1]];

    const baseBlock = cuboid({ size: [opts.size[0] - profileSize[0], opts.size[1], opts.size[2]] });
    const edgeBlock = rotate([Math.PI / 2, 0, 0], extrudeLinear({ height: opts.size[1] }, geomProfile));
    const baseBlockBbox = measureBoundingBox(baseBlock);
    const alignedEdgeBlock = align({ modes: ['min', 'max', 'none'], relativeTo: baseBlockBbox[1] }, edgeBlock);

    return align({ modes: ['center', 'center', 'none'] }, union(baseBlock, alignedEdgeBlock));
  }

  return {
    cuboidMouldingOneEdge,
    /**
     * Positive moulding for a cuboid with the given 2D profile placed onto all the side edges.
     * @memberof details.moulds
     * @instance
     * @param {Object} opts
     * @param {number[]} opts.size - size (x, y, z)
     * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile
     */
    cuboidMoulding: (opts, geomProfile) => {
      // // X axis
      const xHalfSize = [opts.size[0] / 2, opts.size[1], opts.size[2]];
      const xHalfBlock = align({ modes: ['min', 'center', 'none'] }, cuboidMouldingOneEdge({ size: xHalfSize }, geomProfile));
      const xBlock = union(xHalfBlock, mirror({ normal: [1, 0, 0] }, xHalfBlock));

      // // Y axis
      const yHalfSize = [opts.size[1] / 2, opts.size[0], opts.size[2]];
      const yHalfBlock = rotate([0, 0, Math.PI / -2], cuboidMouldingOneEdge({ size: yHalfSize }, geomProfile));
      const yHalfBlockAdj = align({ modes: ['center', 'max', 'none'] }, yHalfBlock);
      const yBlock = union(yHalfBlockAdj, mirror({ normal: [0, 1, 0] }, yHalfBlockAdj));

      return intersect(xBlock, yBlock);
    },
    /**
     * Positive moulding for a cylinder with the given 2D profile placed onto the edge.
     * @memberof details.moulds
     * @instance
     * @param {Object} opts
     * @param {number} opts.radius - Cylinder radius.
     * @param {number} opts.height - Cylinder height.
     * @param {number} opts.segments - Cylinder height.
     * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile
     */
    circularMoulding: (opts, geomProfile) => {
      const profileBbox = measureBoundingBox(geomProfile);
      const profileSize = [profileBbox[1][0] - profileBbox[0][0], profileBbox[1][1] - profileBbox[0][1]];
      const baseCylRad = opts.radius - profileSize[0];
      // cylinder expanded by a tiny amount to ensure no gaps
      const baseCyl = cylinder({ radius: baseCylRad + 0.05, height: opts.height });

      const adjProfile = translate([baseCylRad + profileSize[0] / 2], geomProfile);
      const edgeMoulding = extrudeRotate({ segments: opts.segments || 48 }, adjProfile);

      return union(baseCyl, edgeMoulding);
    },
    /**
     * Negative mould for a rectangular sunken panel, to be placed on a wall/ceiling surface
     * @memberof details.moulds
     * @instance
     * @param {Object} opts
     * @param {number[]} opts.edge - size (x, y)
     * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile for edge
     */
    sunkenPanelRect: (opts, geomProfile) => {
      return null;
    },
    /**
     * Negative mould for a circular sunken panel, to be placed on a wall/ceiling surface
     * @memberof details.moulds
     * @instance
     * @param {Object} opts
     * @param {number} opts.radius - panel radius
     * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile for edge
     */
    sunkenPanelCirc: (opts, geomProfile) => {
      return null;
    },
  }
}

module.exports = { init: mouldBuilder }