Source: modules/ColorPalette.js

/**
 * Internal representation of the ColorPalette React component.
 * Used to track each color of the voxels within the scene.
 *
 * @property {Array.<Color>} colors - Array containing all of the colors in the color palette
 * @property {number} selectedColor - The currently selected color from the colors array
 * @property {number} maxColors - The maximum number of colors that the colors array can hold
 */
class ColorPalette {
  /**
   * Creates a new ColorPalette.
   * @param {Array} [colors=null] Array of colors to create the color palette with
   * @param {number} [selectedColor=0] The initial selected color
   */
  constructor(colors = null, selectedColor = 0) {
    // Set the default values for member variables
    this.colors = null;
    this.selectedColor = 0;

    // Initialize the color array
    this.setNewColorsArray(
      colors ? colors : [new Color(0.5176, 0.7843, 0.0902)]
    );

    // Set the currently selected color
    this.setSelectedColor(selectedColor);

    // The VoxelWorld can only hold up to 255 colors
    this.maxColors = 128;
  }

  /**
   * Sets the color palette to its default settings.
   */
  restoreDefaults() {
    this.selectedColor = 0;
    this.colors = [new Color(0.5176, 0.7843, 0.0902)];
  }

  /**
   * Creates a new color array from the new one.
   * @param {Array} newColors
   */
  setNewColorsArray(newColors) {
    this.colors = newColors.map(({ r, g, b }) => new Color(r, g, b));
  }

  /**
   * Adds a new color to the end of the colors array. Red by default.
   * @param {number} [r = 1]
   * @param {number} [g = 0]
   * @param {number} [b = 0]
   */
  addColor(r = 1, g = 0, b = 0) {
    if (!this.isColorsFull()) {
      this.colors.push(new Color(r, g, b));
      this.selectedColor = this.colors.length - 1;
    }
  }

  /**
   * Checks if the colors array is full. True if it is. False otherwise.
   * @returns {boolean}
   */
  isColorsFull() {
    return this.colors.length >= this.maxColors;
  }

  /**
   * Sets the color at the given index to the given r, g, b values.
   * @param {number} index
   * @param {number} r
   * @param {number} g
   * @param {number} b
   */
  setColorAtIndex(index, r, g, b) {
    // If index out of range, return
    if (index < 0 || index >= this.colors.length) return;

    // Set the rgb values of the color
    this.colors[index].r = r;
    this.colors[index].g = g;
    this.colors[index].b = b;
  }

  /**
   * Returns the color at the given index.
   * @param {number} index
   * @returns {Color} The color at the index. Null if not found
   */
  getColorAtIndex(index) {
    // If index out of range, return
    if (index < 0 || index >= this.colors.length) return null;
    return this.colors[index];
  }

  /**
   * Returns the currently selected color.
   * @returns {Color}
   */
  getSelectedColor() {
    return this.colors[this.selectedColor];
  }

  /**
   * Changes the currently selected color to another within the colors array.
   * @param {number} index
   */
  setSelectedColor(index) {
    // If index out of range, return
    if (index < 0 || index >= this.colors.length) return null;
    this.selectedColor = index;
  }

  /**
   * Returns the index of the selected color.
   * @returns {number}
   */
  getSelectedColorIndex() {
    return this.selectedColor;
  }

  /**
   * Returns the array of colors currently in the palette.
   * @returns {Array.Color}
   */
  getColorsArray() {
    return this.colors;
  }
}

/**
 * Class that represents a single rgb color with each component being from
 * 0 to 1.
 */
class Color {
  constructor(r, g, b) {
    this.r = r;
    this.g = g;
    this.b = b;
  }

  /**
   * Returns a 0-255 value representation of the color.
   * @returns {Object}
   */
  getRGB255() {
    return { r: this.r * 255, g: this.g * 255, b: this.b * 255 };
  }
}

export default ColorPalette;