#VRML_SIM R2022b utf8
# license: Copyright Cyberbotics Ltd. Licensed for use only with Webots.
# license url: https://cyberbotics.com/webots_assets_license
# A customizable cabinet containing elements (shelves, dynamic doors, and drawers).
# The internal cabinet layout is a grid in which the elements can be inserted.
# The grid dimension (and so the cabinet size) is defined by the 'rowsHeights' and the 'columnsWidths' fields.
# The `layout` field is defining the location and the dimension of the elements into the grid layout, according to the following syntax:
#  - `layout`: list("[RightSidedDoor|LeftSiderDoor|Drawer|Shelf] (x, y, column span, row span[, mass])"
#
# The coordinate origin (1,1) of the grid layout is at the bottom left corner.
# template language: javascript

EXTERNPROTO "CabinetHandle.proto"
EXTERNPROTO "webots://projects/appearances/protos/PaintedWood.proto"
EXTERNPROTO "webots://projects/objects/solids/protos/SolidBox.proto"
EXTERNPROTO "CabinetDoor.proto"
EXTERNPROTO "CabinetDrawer.proto"

PROTO Cabinet [
  field SFVec3f    translation    0 0 0
  field SFRotation rotation       0 0 1 0
  field SFString   name           "cabinet"
  field SFFloat    depth          0.5                  # Defines the depth of the cabinet.
  field SFFloat    innerThickness 0.02                 # Defines the inner thickness of the cabinet frame.
  field SFFloat    outerThickness 0.03                 # Defines the outer thickness of the cabinet frame.
  field MFFloat    rowsHeights [                       # Defines the height of the cabinet rows.
    0.24
    0.2
    0.2
    0.4
    0.4
  ]
  field MFFloat columnsWidths [                        # Defines the width of the cabinet columns.
    0.4
    0.17
    0.17
  ]
  field MFString layout [                              # Defines the layout of the cabinet.
    "RightSidedDoor (1, 4, 1, 2, 1.5)"
    "LeftSidedDoor (2, 4, 2, 2, 1.5)"
    "Drawer (3, 3, 1, 1, 1.5)"
    "Drawer (2, 2, 1, 1, 1.5)"
    "Drawer (3, 2, 1, 1, 1.5)"
    "Drawer (1, 1, 3, 1, 3.5)"
    "Shelf (1, 5, 3, 0)"
    "Shelf (1, 4, 3, 0)"
    "Shelf (1, 3, 3, 0)"
    "Shelf (1, 2, 3, 0)"
    "Shelf (1, 3, 0, 1)"
    "Shelf (2, 3, 0, 1)"
  ]
  field SFNode   handle              CabinetHandle {}  # Defines the handle of the cabinet.
  field SFNode   primaryAppearance   PaintedWood {}    # Defines the primary appearance.
  field SFNode   secondaryAppearance PaintedWood {}    # Defines the secondary appearance.
]
{
  %<
    function computeCoordinate (elements, item, dimension) {
      let from = 0.0;
      for (let i = 0; i < (item - 1); ++i)
        if (elements[i] !== undefined)
          from += elements[i];

      let to = 0.0;
      for (let i = 0; i < item + dimension - 1; ++i)
        if (elements[i] !== undefined)
          to += elements[i];

      return 0.5 * (from + to);
    }

    function computeDimension (elements, item, dimension) {
      let length = 0.0;
      for (let i = item - 1; i < item + dimension - 1; ++i)
        if (elements[i] !== undefined)
          length += elements[i];

      return length;
    }

    let innerThickness = fields.innerThickness.value;
    if (innerThickness <= 0.0) {
      innerThickness = fields.innerThickness.defaultValue;
      console.error('\'innerThickness\' must be strictly positive. Value reset to ' + innerThickness + '.');
    }

    let outerThickness = fields.outerThickness.value;
    if (outerThickness <= 0.0) {
      outerThickness = fields.outerThickness.defaultValue;
      console.error('\'outerThickness\' must be strictly positive. Value reset to ' + outerThickness + '.');
    }

    if (2.0 * outerThickness <= innerThickness) {
      outerThickness = 0.51 * innerThickness;
      console.error('\'innerThickness\' should be at least twice bigger than \'outerThickness\'. Value reset to ' + outerThickness + '.');
    }

    let depth = fields.depth.value;
    if (depth <= 0.0) {
      depth = fields.depth.defaultValue;
      console.error('\'depth\' must be strictly positive. Value reset to ' + depth + '.');
    }
    if (depth < 2 * outerThickness + 3 * innerThickness) {
      depth = 2 * outerThickness + 3 * innerThickness;
      console.error('\'depth\' must be bigger than 2 * \'outerThickness\' + 3 * \'innerThickness\'. Value reset to ' + depth + '.');
    }

    let rowsHeights = fields.rowsHeights.value;
    const nRows = fields.rowsHeights.value.length;
    for (let i = 0; i < nRows; ++i) {
      const height = rowsHeights[i];
      if (height < 3 * innerThickness) {
        rowsHeights[i] = 3 * innerThickness; // minimum size
        console.error('\'rowsHeights[' + i + ']\' must be bigger than 3 * \'innerThickness\'. Value reset to ' + rowsHeights[i] + '.');
      }
    }

    let columnsWidths = fields.columnsWidths.value;
    const nColumns = fields.columnsWidths.value.length;
    for (let i = 0; i < nColumns; ++i) {
      const width = columnsWidths[i];
      if (width < 3 * innerThickness) {
        columnsWidths[i] = 3 * innerThickness; // minimum size;
        console.error('\'columnsWidths[' + i + ']\' must be bigger than 3 * \'innerThickness\'. Value reset to ' + columnsWidths[i] + '.');
      }
    }

    let size = {x: depth, y: 2 * outerThickness, z: 2 * outerThickness};
    for (let i = 0; i < nColumns; ++i)
      size.y += columnsWidths[i];

    for (let i = 0; i < nRows; ++i)
      size.z += rowsHeights[i];

    // in order to avoid object collisions
    const objectScaleFactor = 0.995;
  >%
  Solid {
    translation IS translation
    rotation IS rotation
    children [
      SolidBox { # back of the cabinet frame
        translation %<= 0.5 * outerThickness >% 0 %<= 0.5 * size.z >%
        name "back box"
        size %<= outerThickness >% %<= size.y >% %<= size.z - 2.0 * outerThickness >%
        appearance IS primaryAppearance
      }
      SolidBox { # left side of the cabinet frame
        translation %<= 0.5 * outerThickness + 0.5 * size.x >% %<= 0.5 * size.y - 0.5 * outerThickness >% %<= 0.5 * size.z >%
        name "left box"
        size %<= size.x - outerThickness >% %<= outerThickness >% %<= size.z - 2.0 * outerThickness >%
        appearance IS primaryAppearance
      }
      SolidBox { # right side of the cabinet frame
        translation %<= 0.5 * outerThickness + 0.5 * size.x >% %<= -0.5 * size.y + 0.5 * outerThickness >% %<= 0.5 * size.z >%
        name "right box"
        size %<= size.x - outerThickness >% %<= outerThickness >% %<= size.z - 2.0 * outerThickness >%
        appearance IS primaryAppearance
      }
      SolidBox { # top side of the cabinet frame
        translation %<= 0.5 * size.x >% 0 %<= size.z - 0.5 * outerThickness>%
        name "top box"
        size %<= size.x >% %<= size.y >% %<= outerThickness >%
        appearance IS primaryAppearance
      }
      SolidBox { # bottom side of the cabinet frame
        translation %<= 0.5 * size.x >% 0 %<= 0.5 * outerThickness >%
        name "bottom box"
        size %<= size.x >% %<= size.y >% %<= outerThickness >%
        appearance IS primaryAppearance
      }
      # parse layout
      %< for (let i = 0; i < fields.layout.value.length; ++i) { >%
        %<
          const layout = fields.layout.value[i];
          let data = layout.match(/([^", ( ) %s"]+)/g);
          for (let j = 1; j < data.length; ++j) {
            data[j] = Number(data[j]);
            if (data[j] < 0) {
              data[j] = 0.1;
              console.error('\'layout[' + i + '][' + j + ']\' must be positive. Value reset to ' + data[j] + '.');
            }
          }
        >%
        %< if (data.length >= 5) { >%
          %< if (data[1] > 0 && data[1] <= nColumns && data[2] > 0 && data[2] <= nRows &&
                 data[3] >= 0 && data[1] + data[3] - 1 <= nColumns && data[4] >= 0 && data[2] + data[4] - 1 <= nRows) {
          >%
            %< if (data[0] === 'RightSidedDoor' || data[0] === 'LeftSidedDoor' || data[0] === 'Drawer') { >%
              %< if (data[0] === 'RightSidedDoor') { >%
                 CabinetDoor {
                   name %<= '"door ' + i + '"' >%
                   rightSided TRUE
              %< } else if (data[0] === 'LeftSidedDoor') { >%
                CabinetDoor {
                  name %<= '"door ' + i + '"' >%
                  rightSided FALSE
              %< } else if (data[0] === 'Drawer') { >%
                CabinetDrawer {
                  name %<= '"drawer ' + i + '"' >%
              %< } >%
                  translation
                    %<= size.x >%
                    %<= computeCoordinate(columnsWidths, data[1], data[3]) - 0.5 * size.y + outerThickness >%
                    %<= computeCoordinate(rowsHeights,   data[2], data[4]) + outerThickness >%
                  size
                    %<= objectScaleFactor * (size.x - outerThickness) >%
                    %<= objectScaleFactor * computeDimension(columnsWidths, data[1], data[3]) >%
                    %<= objectScaleFactor * computeDimension(rowsHeights,   data[2], data[4]) >%
                  %< if (data[5] !== undefined && data[5] > 0.0) { >%
                    mass %<= data[5] >%
                  %< } >%
                  thickness IS innerThickness
                  handle IS handle
                  %< if (data[0] === 'Drawer') { >%
                    primaryAppearance IS primaryAppearance
                  %< } >%
                  secondaryAppearance IS secondaryAppearance
                }
            %< } else if (data[0] === 'Shelf') { >%
              %<
                const horizontalOffset = data[3] === 0 ? 1 : 0;
                const depthFightingOffset = data[3] < data[4] ? 0.0001 : 0;
              >%
              SolidBox {
                 name %<= '"box ' + i + '"' >%
                appearance IS primaryAppearance
                translation
                  %<= 0.5 * size.x + 0.5 * (outerThickness - innerThickness) + depthFightingOffset >%
                  %<= computeCoordinate(columnsWidths, horizontalOffset + data[1], data[3]) - 0.5 * size.y + outerThickness + depthFightingOffset >%
                  %<= computeCoordinate(rowsHeights, data[2], data[4]) + outerThickness + depthFightingOffset >%
                size
                  %<= objectScaleFactor * (size.x - outerThickness - innerThickness) >%
                  %<= innerThickness + computeDimension(columnsWidths, data[1], data[3]) >%
                  %<= innerThickness + computeDimension(rowsHeights,   data[2], data[4]) >%
               }
            %<  } else { >%
              %< console.error('\'layout[' + i + ']\': unknown object: ' + data[0] + '.'); >%
            %< } >%
          %< } else { >%
            %< console.error('\'layout[' + i + ']\': invalid position or dimension.'); >%
          %< } >%
        %< } else { >%
          %< console.error('\'layout[' + i + ']\': invalid layout.'); >%
        %< } >%
      %< } >%
    ]
    name IS name
    model "cabinet"
  }
}