pxlNav/core/Utils.js

import {
  Vector2,
  Vector3,
  ImageLoader,
  Texture,
  VideoTexture,
  CanvasTexture,
  LinearFilter,
  AlphaFormat,
  RedFormat,
  RGBAFormat,
  RGFormat,
  RGBFormat,
  DepthFormat,
  MeshBasicMaterial
} from "three";
/*LinearSRGBColorSpace,
SRGBColorSpace,
CubeUVRefractionMapping,*/


 /**
  * @alias pxlUtils
  * @class
  * @description Utility class providing various helper functions for pxlNav.
  * @property {number} curMS - Read-Only; Current time in milliseconds since the last frame update.
  */
export class Utils{
  /**
   * Create a Utils instance.
   * @param {string} [assetRoot="images/assets/"] - Root path for assets.
   * @param {boolean} [mobile] - Whether running on a mobile device.
   */
  constructor(assetRoot="images/assets/", mobile ){
    this.assetRoot=assetRoot;
    this.mobile=mobile;
    this.pxlTimer=null;
    this.pxlEnums=null;
    this.verboseLoading=false;
    this.texLoader=new ImageLoader();
    this.textLoaderArray=[];
    // Texture formats, use as needed
    // ImageLoader's defauls; images load as RGBAFormat by default, and JPG as RGBAFormat
    this.channelFormats=[ AlphaFormat, RedFormat, RGFormat, RGBFormat, RGBAFormat, DepthFormat ];
  }
  
  get curMS(){
      return this.pxlTimer.curMS;
  }

  setDependencies( pxlNav ){
    this.pxlTimer=pxlNav.pxlTimer;
    this.pxlEnums=pxlNav.pxlEnums;
  }
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Update the browser URL using history API.
   * @param {string} url - The new URL.
   * @param {Object} [state={}] - State object for history.
   * @param {string} [title=""] - Title for the history entry.
   */
  updateUrl(url,state={},title=""){
      if (window.history.replaceState) {
          window.history.replaceState(state, title, url);
      }else{
          window.history.pushState(state, title, url);
      }
  }
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Copy the current room URL to the clipboard.
   * @returns {boolean|string} Status of the copy operation.
   */
  copyRoomUrl(){
      let url=window.location;
      let tmpFocus=document.activeElement;
      let tmpText = document.createElement("textarea");
      tmpText.value = url;

      document.body.appendChild(tmpText);
      tmpText.focus();
      tmpText.select();

      let status=false;
      try{
          let callback = document.execCommand('copy');
          status = callback ? 'successful' : 'unsuccessful';
      }catch(err){}

      document.body.removeChild(tmpText);
      tmpFocus.focus();  
      
      return status;
  }
    
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Check if a value is an integer.
   * @param {number} val - The value to check.
   * @returns {boolean} True if integer, false otherwise.
   */
  checkInt(val){
    return (val%1)===0;
  }

  /**
   * @method
   * @memberof pxlUtils
   * @description Convert degrees to radians.
   * @param {number} deg - Degrees.
   * @returns {number} Radians.
   */
  degToRad(deg){
    return deg*(Math.PI/180);
  }

  
  /**
   * @method
   * @memberof pxlUtils
   * @description Round a number to the nearest hundredths (2 decimal places).
   * @param {number} val - Value to round.
   * @returns {number} Rounded value.
   */
  toHundreths(val){ // int(val*100)*.01 returns an erronious float on semi ussual basis...
    if(!val) return 0;

    if( Number.isInteger(val) ){
      return val;
    }else{
      let sp=(val+"").split(".");
      let ret=parseFloat(sp[0]+"."+sp[1].substr(0,2));
      return ret;
    }
  }

  
  /**
   * @method
   * @memberof pxlUtils
   * @description Round a number to the nearest tenths (1 decimal place).
   * @param {number} val - Value to round.
   * @returns {number} Rounded value.
   */
  toTenths(val){ // int(val*100)*.01 returns an erronious float on semi ussual basis...
    if(!val) return 0;

    if( Number.isInteger(val) ){
      return val;
    }else{
      let sp=(val+"").split(".");
      let ret=parseFloat(sp[0]+"."+sp[1].substr(0,1));
      return ret;
    }
  }
  
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Get the current date and time as an array.
   * @returns {Array<string>} [date, time] in "YYYY-MM-DD" and "HH:MM:SS" format.
   */
  getDateTime(){
    let d=new Date();
    let date=(d.getFullYear()+"").padStart(2,'0')+"-"+((d.getMonth()+1)+"").padStart(2,'0')+"-"+(d.getDate()+"").padStart(2,'0');
    let time=(d.getHours()+"").padStart(2,'0')+":"+(d.getMinutes()+"").padStart(2,'0')+":"+(d.getSeconds()+"").padStart(2,'0');
    return [date, time];
  }
  
  // -- -- -- //

  
  /**
   * @method
   * @memberof pxlUtils
   * @description Strictly clean a string from HTML and non-alphanumeric characters.
   * @param {string} messageString - String to clean.
   * @returns {string} Cleaned string.
   */
  cleanStrict( messageString ){
    let strip=document.createElement( "div" );
    strip.innerHTML=messageString;
    strip=strip.innerText;
    let matcher=strip.match(/([a-zA-Z0-9])\w+/g);

    if(matcher){
      strip=matcher.join(" ");
    }
    return strip;
  }
  
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Basic clean of a string, allowing some symbols.
   * @param {string} messageString - String to clean.
   * @returns {string} Cleaned string.
   */
  cleanBasic( messageString ){
    let strip=document.createElement( "div" );
    strip.innerHTML=messageString;
    strip=strip.innerText;
    let matcher=strip.match(/([a-zA-Z0-9\s\w-+()[\]])+/g);

    if(matcher){
      strip=matcher.join("");
    }
    return strip;
  }
    
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Remove HTML from a string.
   * @param {string} messageString - String to clean.
   * @returns {string} Cleaned string.
   */
  cleanString( messageString ){
    let strip=document.createElement( "div" );
    strip.innerHTML=messageString;
    strip=strip.innerText;
    return strip;
  }


  // Round to nearest
  /**
   * @method
   * @memberof pxlUtils
   * @description Round a value to a fixed precision and return as string.
   * @param {number} val - Value to round.
   * @param {number} [precision=2] - Number of decimal places.
   * @returns {string} Rounded value as string.
   */
  toNearestStr( val, precision=2 ){
    let retVal = val.toFixed(precision);
    return retVal;
  }

  // Round array elements to nearest
  /**
   * @method
   * @memberof pxlUtils
   * @description Round each element of an array to a fixed precision and return as array of strings.
   * @param {Array<number>} arr - Array of numbers.
   * @param {number} [precision=2] - Number of decimal places.
   * @returns {Array<string>} Array of rounded strings.
   */
  arrayToStr( arr, precision=2 ){
    let retArr = [];
    arr.forEach( (val)=>{
      retArr.push( this.toNearestStr(val, precision) );
    });
    return retArr;
  }

  // Flatten array to joined string
  /**
   * @method
   * @memberof pxlUtils
   * @description Flatten an array to a joined string with specified precision and delimiter.
   * @param {Array<number>} arr - Array of numbers.
   * @param {number} [precision=2] - Number of decimal places.
   * @param {string} [delimiter=","] - Delimiter for joining.
   * @returns {string} Joined string.
   */
  flattenArrayToStr( arr, precision=2, delimiter="," ){
    return this.arrayToStr( arr, precision ).join(delimiter);
  }

  // -- -- -- //
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Generate a random float between min and max.
   * @param {number} min - Minimum value.
   * @param {number} max - Maximum value.
   * @returns {number} Random float.
   */
  randomFloat(min,max){
    return Math.random()*(max-min)+min;
  }

  // -- -- -- //

  /**
   * @method
   * @memberof pxlUtils
   * @description Convert screen coordinates to normalized device coordinates (NDC).
   * @param {number} x - X coordinate.
   * @param {number} y - Y coordinate.
   * @param {number} width - Screen width.
   * @param {number} height - Screen height.
   * @returns {Vector2} NDC coordinates.
   */
  screenToNDC( x, y, width, height ){
    let ndcX = ( x / width ) * 2 - 1;
    let ndcY = - ( y / height ) * 2 + 1;
    return new Vector2( ndcX, ndcY );
  }
  
  // -- -- -- //

  /**
   * @method
   * @memberof pxlUtils
   * @description Convert a color component to hexadecimal string.
   * @param {number} c - Color component (0-255).
   * @returns {string} Hexadecimal string.
   */
  componentToHex(c) {
    var hex = c.toString(16);
    return hex.padStart(2,'0');
  }

  /**
   * @method
   * @memberof pxlUtils
   * @description Convert RGB values to a hexadecimal color string.
   * @param {number} r - Red (0-255).
   * @param {number} g - Green (0-255).
   * @param {number} b - Blue (0-255).
   * @returns {string} Hex color string.
   */
  rgbToHex(r, g, b) {
    return "#" + this.componentToHex(Math.min(255, Math.max(0,Math.round(r)))) + this.componentToHex(Math.min(255, Math.max(0,Math.round(g)))) + this.componentToHex(Math.min(255, Math.max(0,Math.round(b))));
  }
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Convert a hex color string to RGB array.
   * @param {string} hex - Hex color string.
   * @returns {Array<number>} [r, g, b] array.
   */
  hexToRgb( hex ) {
    let buffer=hex[0];
    if(buffer==="#"){
      hex=hex.substr( 1, 6 );
    }else{
      hex=hex.substr( 0, 6 );
    }
    let r,g,b;
    if(hex.length===3){
      r=hex[0]+hex[0];
      g=hex[1]+hex[1];
      b=hex[2]+hex[2];
    }else{
      r=hex[0]+hex[1];
      g=hex[2]+hex[3];
      b=hex[4]+hex[5];
    }
    r=parseInt(r,16);
    g=parseInt(g,16);
    b=parseInt(b,16);
    return [r,g,b];
  }
  
  // -- -- -- //

  /**
   * @method
   * @memberof pxlUtils
   * @description Generate an RGB color from a string.
   * @param {string} string - Input string.
   * @param {number|null} [boost=null] - Optional boost factor.
   * @param {boolean} [zoFit=false] - If true, normalize to [0,1].
   * @returns {Array<number>} RGB array.
   */
  stringToRgb( string, boost=null, zoFit=false ){
    let stringColor=[255,0,0];
    if( string ){
      let sLength=string.length;
      let charCode="";
      for(let x=0; x<sLength; ++x){
        charCode+=string[ (sLength-1-x) ].charCodeAt(0).toString(16);
      }

      let ccLength=charCode.length;
      if(ccLength>6){
        let offset=1;
        if(string==="tussin"){
          offset=0;
        }else if(string==="fexofenadine"){
          offset=-1;
        }
        let reader=Math.max(0,parseInt((ccLength-6)/2+offset));
        charCode=charCode.substr(reader,6);
      }
      
      stringColor=this.hexToRgb( charCode );
    }
    
    if( boost != null){
      let maxCd=Math.max(...stringColor);
      let minCd=Math.min(...stringColor);
      let boostCd=maxCd*boost;
      stringColor[0]= parseInt( Math.min(255, ((stringColor[0]-minCd) / (maxCd-minCd))*255+boostCd) );
      stringColor[1]= parseInt( Math.min(255, ((stringColor[1]-minCd) / (maxCd-minCd))*255+boostCd) );
      stringColor[2]= parseInt( Math.min(255, ((stringColor[2]-minCd) / (maxCd-minCd))*255+boostCd) );
      /*
      stringColor[0]= Math.max(0, Math.min(255, (stringColor[0]-100)*boost+100));
      stringColor[1]= Math.max(0, Math.min(255, (stringColor[1]-100)*boost+100));
      stringColor[2]= Math.max(0, Math.min(255, (stringColor[2]-100)*boost+100));
      */
    }
    
    if( zoFit===true ){
      stringColor[0]=stringColor[0]/255;
      stringColor[1]=stringColor[1]/255;
      stringColor[2]=stringColor[2]/255;
    }
    
    return stringColor;
  }
    
  // -- -- -- //


  // Convert Color/Vector3 to sRGB Color Space
  /**
   * @method
   * @memberof pxlUtils
   * @description Convert a color or Vector3 to sRGB color space.
   * @param {Object} color - Color or Vector3 object.
   * @returns {Object} Converted color.
   */
  colorTosRGB( color ){
    // Check if the colorue is a color object
    if( typeof color === "object" ){
      // Color Object
      if( color.hasOwnProperty && color.hasOwnProperty("r") ){
        color.r = this.tosRGB(color.r);
        color.g = this.tosRGB(color.g);
        color.b = this.tosRGB(color.b);
      }
      if( color.hasOwnProperty && color.hasOwnProperty("x") ){
        color.x = this.tosRGB(color.x);
        color.y = this.tosRGB(color.y);
        color.z = this.tosRGB(color.z);
      }
      return color;
    }
    return color;
  }

  // Convert Linear to sRGB
  /**
   * @method
   * @memberof pxlUtils
   * @description Convert a linear value to sRGB.
   * @param {number} val - Linear value.
   * @returns {number} sRGB value.
   */
  tosRGB( val ){
    // Convert the value per channel
    if( val <= 0.0031308 ){
      val *= 12.92;
    }else{
      val = 1.055 * Math.pow(val, this.oneTwoPFour) - 0.055;
    }
    return val;
  }

  // -- -- --

  // Convert Color/Vector3 to Linear Color Space
  /**
   * @method
   * @memberof pxlUtils
   * @description Convert a color or Vector3 to linear color space.
   * @param {Object} color - Color or Vector3 object.
   * @returns {Object} Converted color.
   */
  colorToLinear( color ){
    // Check if the colorue is a color object
    if( typeof color === "object" ){
      // Color Object
      if( color.hasOwnProperty && color.hasOwnProperty("r") ){
        color.r = this.toLinear(color.r);
        color.g = this.toLinear(color.g);
        color.b = this.toLinear(color.b);
      }
      if( color.hasOwnProperty && color.hasOwnProperty("x") ){
        color.x = this.toLinear(color.x);
        color.y = this.toLinear(color.y);
        color.z = this.toLinear(color.z);
      }
      return color;
    }
    return color;
  }
  // Convert sRGB to Linear
  /**
   * @method
   * @memberof pxlUtils
   * @description Convert an sRGB value to linear.
   * @param {number} val - sRGB value.
   * @returns {number} Linear value.
   */
  toLinear( val ){
    if( val <= 0.04045 ){
      val *= this.twelvePNineTwoDiv;
    }else{
      val = Math.pow((val + 0.055) * this.onePOFiveFiveDiv, 2.4);
    }
    return val;
  }


  // -- -- --

  
  /**
   * @method
   * @memberof pxlUtils
   * @description Apply gamma correction to a color or Vector3.
   * @param {Object} color - Color or Vector3 object.
   * @param {number|string} [gammaIn="2.2"] - Input gamma.
   * @param {number|string} [gammaOut="1.8"] - Output gamma.
   * @returns {Object} Gamma-corrected color.
   */
  gammaCorrectColor( color, gammaIn="2.2", gammaOut="1.8" ){
    // Check if the colorue is a color object
    if( typeof color === "object" ){
      // Color Object
      if( color.hasOwnProperty && color.hasOwnProperty("r") ){
        color.r = this.gammaCorrect(color.r, gammaIn, gammaOut);
        color.g = this.gammaCorrect(color.g, gammaIn, gammaOut);
        color.b = this.gammaCorrect(color.b, gammaIn, gammaOut);
      }
      if( color.hasOwnProperty && color.hasOwnProperty("x") ){
        color.x = this.gammaCorrect(color.x, gammaIn, gammaOut);
        color.y = this.gammaCorrect(color.y, gammaIn, gammaOut);
        color.z = this.gammaCorrect(color.z, gammaIn, gammaOut);
      }
      return color;
    }
    return color;
  }

  
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Apply gamma correction to a single channel.
   * @param {number} channel - Channel value.
   * @param {number|string} [gammaIn="2.2"] - Input gamma.
   * @param {number|string} [gammaOut="1.8"] - Output gamma.
   * @returns {number} Gamma-corrected channel.
   */
  gammaCorrection( channel, gammaIn="2.2", gammaOut="1.8" ){
    // Linearize the color
    let linearChannel = Math.pow( channel, gammaIn );

    // Apply the gamma correction
    let channelShift = Math.pow( linearChannel, 1.0 / gammaOut );

    return channelShift;
  }

  // -- -- --



  // TODO : Prep & re-implement THREE.GammaFactor -> pxlNav.pxlDevice.GammaFactor
  // TODO : pxlDevice OS detect needs to be implement for color conversion between known OS color spaces
  /**
   * @method
   * @memberof pxlUtils
   * @description Convert a color between color spaces.
   * @param {Object} color - Color object.
   * @param {number} [space=this.pxlEnums.COLOR_SHIFT.KEEP] - Target color space.
   * @returns {Object} Converted color.
   */
  convertColor( color, space=this.pxlEnums.COLOR_SHIFT.KEEP ){
    if( space === this.pxlEnums.COLOR_SHIFT.KEEP ){
      return color;
    }

    // Notes on OS Gamma levels --
    //   Linear Gamma is 1.0
    //   Windows default Gamma is 2.2
    //   Mac, Linux, & Androids default Gamma is 1.8
    //   Other Unix systems are 2.3-2.5

    // Notes on Color Spaces --
    //   sRGB & Rec.709 are the same
    //     sRGB is more in line with how the human eye percieves color
    //   Linear is an uncorrected color space, not data is lost between programs or devices
    //     Linear Colors should then be converted to sRGB for display,
    //       But not converted if the color is for Data needs

    let retColor = color.clone();
    switch (space) {
      // Assuming color adjustments have been made with shader math --
      case this.pxlEnums.COLOR_SHIFT.sRGB_TO_LINEAR:
        retColor = this.colorTosRGB(retColor);
        break;
      case this.pxlEnums.COLOR_SHIFT.LINEAR_TO_sRGB:
        retColor = this.colorToLinear(retColor);
        break;

      // TODO : These need to be checked, added for completeness, not tested
      case this.pxlEnums.COLOR_SHIFT.WINDOWS_TO_UNIX:
        retColor = this.gammaCorrectColor(retColor, "2.2", "1.8");
        break;
      case this.pxlEnums.COLOR_SHIFT.UNIX_TO_WINDOWS:
        retColor = this.gammaCorrectColor(retColor, "1.8", "2.2");
        break;
      case this.pxlEnums.COLOR_SHIFT.LINEAR_TO_WINDOWS:
        retColor = this.gammaCorrectColor(retColor, "1.0", "2.2");
        break;
      case this.pxlEnums.COLOR_SHIFT.WINDOWS_TO_LINEAR:
        retColor = this.gammaCorrectColor(retColor, "2.2", "1.0");
        break;
      case this.pxlEnums.COLOR_SHIFT.LINEAR_TO_UNIX:
        retColor = this.gammaCorrectColor(retColor, "1.0", "1.8");
        break;
      case this.pxlEnums.COLOR_SHIFT.UNIX_TO_LINEAR:
        retColor = this.gammaCorrectColor(retColor, "1.8", "1.0");
        break;
      default:
        break;
    }

    return retColor;
  }


  // -- -- -- //

  
  /**
   * @method
   * @memberof pxlUtils
   * @description Randomize the order of elements in an array.
   * @param {Array} inputArr - Input array.
   * @returns {Array} Randomized array.
   */
  randomizeArray(inputArr){
    let tmpArr=[...inputArr];
    let retArr=[];
    while( tmpArr.length > 0){
      let rand=tmpArr.length===1 ? 0 : parseInt(Math.random()*21*tmpArr.length)%tmpArr.length;
      retArr.push( tmpArr.splice( rand, 1 )[0] );
    }
    return retArr;
  }

  
  /**
   * @method
   * @memberof pxlUtils
   * @description Get a random element from a list.
   * @param {Array} list - List of elements.
   * @param {number} [seed=1.14] - Optional seed.
   * @returns {*} Random element.
   */
  getRandom( list, seed=1.14 ){
    let randEl= Math.floor( Math.random( seed ) * list.length);
    return list[ randEl ];
  }
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Apply a transformation list to an object (position, rotation, scale).
   * @param {Object} curObj - The object to transform.
   * @param {Object} transList - Transformation list with keys "r", "t", "s", and optional "rOrder".
   */
  applyTransformList(curObj,transList){
    var rotate=transList["r"];
    curObj.rotateX(rotate[0]);
    curObj.rotateY(rotate[1]);
    curObj.rotateZ(rotate[2]);
    if(typeof(transList["rOrder"]) !== "undefined" ){
      curObj.rotation.order=transList["rOrder"];
    }
    var pos=transList["t"];
    curObj.position.set(pos[0],pos[1],pos[2]);
    var scale=transList["s"];
    curObj.scale.set(scale[0],scale[1],scale[2]);
    
    curObj.matrixAutoUpdate=false;
    curObj.updateMatrix();
  }
  
    vec2(x=null,y=null){
        return new Vector2(x,y);
    }
    vec3(x=0,y=0,z=0){
        return new Vector3(x,y,z);
    }
    
  //this.channelFormats=[ AlphaFormat, RedFormat, RGFormat, RGBFormat, RGBAFormat, DepthFormat ];
  /**
   * @method
   * @memberof pxlUtils
   * @description Load a texture from an image path.
   * @param {string} imgPath - Image path.
   * @param {number|null} [channels=null] - Channel format index.
   * @param {Object} [mods={}] - Texture modifications.
   * @returns {Texture} Loaded texture.
   */
  loadTexture(imgPath,channels=null,mods={}){
    // ## Check how textLoaderArray textures are being handled after being disposed

    // No path, default to the asset root
    if( !imgPath.includes( "/") ){
      imgPath = this.assetRoot + imgPath;
    }
    
    let texture=null;
    if(typeof(this.textLoaderArray[imgPath]) != "undefined"){
      texture=this.textLoaderArray[imgPath];
    }else{
      //var texLoader=new ImageLoader(verboseLoading);
      texture=new Texture();
      let modKeys = Object.keys(mods);
      this.texLoader.load(imgPath,
        (tex)=>{
          if(channels!=null){
            texture.format = this.channelFormats[ channels ];
          }
          texture.image=tex;
          texture.needsUpdate=true;

          // Apply texture mods passed from the user
          if(modKeys.length>0){
            modKeys.forEach((x)=>{
              texture[x]=mods[x];
            });
          }
        },
        undefined,
        (err)=>{
          console.error(" TextureLoader :: Error loading texture :: "+imgPath);
          console.error(err);
        }
      );
      this.textLoaderArray[imgPath]=texture;
    }
    return texture;
  }

  
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Create a VideoTexture from a video element.
   * @param {HTMLVideoElement} videoObject - Video element.
   * @returns {VideoTexture} Video texture.
   */
  getVideoTexture( videoObject ){
    let videoTexture=new VideoTexture(videoObject);
    videoTexture.minFilter=LinearFilter;
    videoTexture.magFilter=LinearFilter; // faster, lower samples, NearestFilter
    videoTexture.format=RGBFormat;
    
    return videoTexture;
  }
  
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Create a CanvasTexture and MeshBasicMaterial from a canvas.
   * @param {HTMLCanvasElement} canvas - Canvas element.
   * @returns {{texture: CanvasTexture, material: MeshBasicMaterial}} Texture and material.
   */
  getCanvasTexture( canvas ){
    const texture = new CanvasTexture(canvas);
      
    const material = new MeshBasicMaterial({
      map: texture,
    });
    return {texture, material};
  }

  // -- -- -- //

  
  
  /**
   * @method
   * @memberof pxlUtils
   * @description Duplicate an array a specified number of times.
   * @param {Array} val - The array to duplicate.
   * @param {number} count - Number of times to duplicate.
   * @returns {Array} Duplicated array.
   * @example
   * // Duplicate an array
   * import { pxlNav } from 'pxlNav.js';
   * const pxlParticleBase = pxlNav.pxlEffects.pxlParticles.pxlParticle
   * 
   * build(){
   *  pxlParticleBase.dupeArray( [0.0,0.75], 4 );
   *  // Output: [0.0,0.75], [0.0,0.75], [0.0,0.75], [0.0,0.75]
   * }
   */
  dupeArray( val, count ){
    return Array.from({length:count}).fill(val);
  }
  
  
}