Laminaat leggen

by PhilQ

HTML

<svg id="floor" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="-50 -50 460.5 693.5" fill="none"></svg>

<div id="controls">
  <div>
    <div><label for="floor_w">Vloer (lengte):</label><input id="floor_w" type="number" value="593.5" step="0.1" min="1" /></div>
    <div><label for="floor_h">Vloer (breedte):</label><input id="floor_h" type="number" value="360.5" step="0.1" min="1" /></div>
    <div><label for="margin">Ruimte rondom:</label><input id="margin" type="number" value="1" step="0.1" min="0" /></div>
  </div>
  <div>
    <div><label for="laminaat_w">Laminaat (lengte):</label><input id="laminaat_w" type="number" value="138" step="0.1" min="1" /></div>
    <div><label for="laminaat_h">Laminaat (breedte):</label><input id="laminaat_h" type="number" value="19.3" step="0.1" min="1" /></div>
  </div>
  <div>
    <div><label for="min_w">Minimale lengte:</label><input id="min_w" type="number" value="40" step="1" min="1" /></div>
    <div><label for="min_h">Minimale breedte:</label><input id="min_h" type="number" value="6" step="1" min="1" /></div>
  </div>
  <div>
    <input type="button" id="go" value="Go!" />
  </div>
  <div>
    <select id="pick_pattern">
      <option value="-">-</option>
    </select>
    <div class="left"><input id="show_widths" type="checkbox" /><label for="show_widths">Toon lengtes</label></div>
  </div>
  <div>
    <select id="pick_room">
      <option value="-">Kies een ruimte...</option>
      <option value="483.5x200.5">Keuken</option>
      <option value="593.5x360.5">Woonkamer</option>
      <option value="310.5x360.5">Werkkamer</option>
      <option value="406.5x200.5">Slaapkamer 2</option>
      <option value="406.5x258.5">Slaapkamer 1</option>
    </select>
  </div>
</div>

CSS

body {
  background-color: #eee;
}

svg {
  position: fixed;
  inset: 0;
}

svg g rect:first-child {
  /* stroke: black; */
}

body:not(.show_widths) svg g text {
  opacity: 0;
}

svg g text:hover,
svg g rect:hover + text {
  opacity: 1 !important;
}

#controls {
  position: fixed;
  top: 0px;
  height: auto;
  left: 0px;
  right: 0px;
  z-index: 100;
  padding: 20px;
  background-color: #ccc;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
}

#controls > div {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

#controls > div > div {
  display: flex;
  flex-direction: row;
  gap: 5px;
  justify-content: space-between;
}

#controls > div > div.left {
  justify-content: flex-start;
}

#controls > div > div.right {
  justify-content: flex-end;
}

input[type=button] {
  height: 46px;
}

JavaScript

function laminaatLayout(
  floorWidth = 0,
  floorHeight = 0,
  panelWidth = 0,
  panelHeight = 0,
  minimalWidth = 30,
  minimalHeight = 6,
  startShift = 0,
  rowShifts = [],
) {
  const pieces = [];
  const wastedPieces = [];
  let fullPanelsUsed = 0;

  let curX = 0;
  let curY = 0;
  let remainder = 0;

  let smallestEnd = null;
  let smallestStart = null;
  let firstPiece = panelWidth;

  let repeatingRows = null;

  while (curY < floorHeight) {
    // Pick new panel
    if (remainder === 0) {
      fullPanelsUsed++;
      remainder = panelWidth;
    }

    // Start of new row
    if (curX === 0) {
      let rowShift = 0;
      if (rowShifts.length > 0) {
        const idx = Math.round(curY / panelHeight) % rowShifts.length;
        rowShift += rowShifts[idx] + startShift;

        // Discard remainder if too short
        if (remainder < panelWidth + rowShift) {
          wastedPieces.push(remainder);
          fullPanelsUsed++;
          remainder = panelWidth;
        } else {
          rowShift = panelWidth + rowShift - remainder;
        }
      } else if (remainder === panelWidth) {
        // Start of new cycle
        rowShift += startShift;
      }

      if (rowShift < 0) {
        // Trim beginning of start piece
        wastedPieces.push(-rowShift);
        remainder += rowShift;
      }
    }

    if (curX + remainder >= floorWidth) {
      // Overshoot or exactly-to-edge panel
      const offcut = curX + remainder - floorWidth;
      pieces.push({
        x: curX,
        y: curY,
        width: remainder - offcut,
        height: panelHeight,
      });

      if (smallestEnd === null) {
        smallestEnd = remainder - offcut;
      }

      remainder = offcut;
      curX = 0;
      curY += panelHeight;

      // Discard any invalid start offcuts
      if (remainder < minimalWidth) {
        if (repeatingRows === null) {
          repeatingRows = curY /...