Source: SolidML.js

/** @file Core module. SolidML.js does not depend on any other libraries. */
/**
 * Construct fractal-like 3D models by cryptic script based on [Structure Synth]{@link http://structuresynth.sourceforge.net/}'s' Eisen Script.
 * SolidML.js does not depend on any other libraries. If you want to import model into three.js, see {@link SolidML.BufferGeometry}.
 */
class SolidML {
  /** Basically one instance for one script. 
   *  @param {string} [script] SolidML script to compile, calls {@link SolidML#compile} inside.
   *  @param {object} [criteria] default criteria of this structure, specified by "set *" commands in script.
   */
  constructor(script=null, criteria=null) {
    SolidML.ScriptParser._initialize();
    /** random number generator for constuct model.
     *  @type {SolidML.randMT}
     */
    this.randMT = new SolidML.randMT();
    /** criteria of this structure, specified by "set *" commands in script
     *  @type {SolidML.Criteria}
     */
    this.criteria = new SolidML.Criteria(this.randMT, criteria);
    /** hash map of defined variables, specified by "#define *" commands in script
     *  @type {object}
     */
    this.variables = {};
    if (script)
      this.compile(script);
  }
  /** Parse script, make a structure of recursive calls inside.
   *  @param  {string} script SolidML script to compile
   *  @param  {int} [seed] seed number for MT-random number calculations, pass null to set by time. this value is overwritten by "set seed" command in the script.
   *  @return {SolidML} this instance
   */
  compile(script, seed=null) {
    // translate #define and comment out
    script = SolidML.ScriptParser._normalizeScript(script);
    this._root = new SolidML.Rule(this, null, null, null);
    this._root._parse(script, 0);
    this._randMTSeed = seed || new Date().getTime();
    return this;
  }
  /** Build object structure. This function has to be called after calling {@link SolidML#compile}.
   *  @param {SolidML~buildCallback} [callback] function called back when new object has created. Call passed {@link SolidML.BuildStatus#stopBuilding} to stop building immediatly.
   *  @return {any} returned value of callback. when callback is undefined, returns the array of cloned {@link SolidML.BuildStatus}.
   */
  build(callback=null) {
    if (!this._root)
      throw Error("SolidML.complie() should be called before calling SolidML.build()");
    this.randMT.seed = this.criteria.seed || this._randMTSeed;
    const status = this._root._build(new SolidML.BuildStatus(callback || this.defaultBuildCallback));
    return status.result;
  }
  defaultBuildCallback(stat) {
    stat.result = stat.result || [];
    stat.result.push(stat.reference());
  }
}
/** callback function passed to {@link SolidML#build}
 *  @callback SolidML~buildCallback
 *  @param {SolidML.BuildStatus} status the status while building structure
 *  @return {any} value of {@link SolidML.BuildStatus.result}.
 */
/** current version number 
 *  @type {String}
 */
SolidML.VERSION = "0.3.0";
/** Represents a criteria of the structure, specified as "set [key] [value]" in script. Keep any user-defined criteria for your own purpose. {@link SolidML.Criteria#getValue} method refers them. */
SolidML.Criteria = class {
  /** SolidML.Criteria is created in the constructor of {@link SolidML}.  */
  constructor(randMT, hash) {
    /** the maximum limit of {@link SolidML.Criteria.maxdepth}. default is 5000 
     *  @type {int}
     */
    this.systemMaxDepth = 5000;
    /** the maximum limit of {@link SolidML.Criteria.maxobjects}. default is 5000000
     *  @type {int}
     */
    this.systemMaxObjects = 5000000;
    /** maximum depth to terminate recursive rule calls. default is 2000 
     *  @type {int}
     */
    this.maxdepth = 2000;
    /** maximum object count to terminate constuction. default is 1000000
     *  @type {int}
     */
    this.maxobjects = 1000000;
    /** minimun size to create object, check [the 3dim-determinant of matrix] < [minsize] ^ 3. default is 0
     *  @type {number}
     */
    this.minsize = 0;
    /** maximun size to create object, check [the 3dim-determinant of matrix] > [maxsize] ^ 3. default is 10000
     *  @type {number}
     */
    this.maxsize = 10000;
    /** seed number for Mersenne Twister random number generator. set null to seed by time. detfault is null
     *  @type {int}
     */
    this.seed =  null;
    /** background color in #HEX type string. set null to use system defined background. default is null
     *  @type {string}
     */
    this.background = null;
    // private color pool instance
    this._colorpoolInstance = new SolidML.ColorPool((hash&&hash["colorpool"]) || "randomrgb", randMT);
    if (hash && ("colorpool" in hash))
      delete hash["colorpool"];
    // copy hash to this
    Object.assign(this, hash);
  }
  /** colorpool referd from "color rondom" command.  default is "randomrgb"
   *  @type {string}
   */
  get colorpool() {
    return this._colorpoolInstance.scheme;
  }
  set colorpool(scheme) {
    this._colorpoolInstance.scheme = scheme;
  }
  _set(key, value) {
    switch(key) {
      case "maxdepth": case "":
        this.maxdepth = this._getNumber(value, this.maxdepth);
        if (this.maxdepth > this.systemMaxDepth) this.maxdepth = this.systemMaxDepth;
        break;
      case "maxobjects": case "mo":
        this.maxobjects = this._getNumber(value, this.maxobjects);
        if (this.maxobjects > this.systemMaxObjects) this.maxobjects = this.systemMaxObjects;
        break;
      case "minsize": case "min":
        this.minsize = this._getNumber(value, this.minsize);
        break;
      case "maxsize": case "max":
        this.maxsize = this._getNumber(value, this.maxsize);
        break;
      case "seed": case "s":
        this.seed = this._getNumber(value, null);
        break;
      case "background": 
        this.background = this._getColor(value);
        break;
      case "colorpool": case "cp":
        this.colorpool = value;
        break;
      case "cp:g":
        this.colorpool = "grayscale:"+value;
        break;
      case "cp:h":
        this.colorpool = "randomhue:"+value;
        break;
      default:
        this[key] = value;
        break;
    }
  }
  /** get criterla by key and type
   *  @param {string} key criteria name 
   *  @param {string} type criteria value type "number", "int", "color", "array" or "string" is available. the "string" type returns the raw string data in script.
   *  @return {any} value for specified criteria
   */
  getValue(key, type) {
    if (key in this) {
      switch(type) {
        case "number":
          return this._getNumber(this[key], 0);
        case "int":
          return (this._getNumber(this[key], 0)>>0);
        case "color":
          return this._getColor(this[key], "#000");
        case "array":
          return this._getArray(this[key]);
        case "hash":
          return this._getHash(key);
        default:
          return this[key] || "";
      }
    }
    return null;
  }
  _getNumber(value, numFailure) {
    const n = Number(value);
    return isNaN(n) ? numFailure : n;
  }
  _getColor(value, strFailure) {
    if (value in SolidML.ColorTable) return SolidML.ColorTable[value];
    if (/^#[0-9a-fA-F]+$/.test(value)) return value;
    const n = Number(value);
    if (isNaN(n)) return strFailure;
    const f = (Math.min(Math.max(n*2.56, 0), 255)).toString(16);
    return "#" + f + f + f;
  }
  _getArray(value) {
    return value.replace(/\[|\]/g, "").split(",");
  }
  _getHash(key) {
    return Object.keys(this).filter(name=>(name.indexOf(key)==0)).reduce((accum,name)=>{
      const k = name.substring(key.length+2);
      accum[k] = this[name];
      return accum;
    }, {});
  }
}
/** Represents 4dim-matrix inside, compatible for THREE.Matrix4.copy() method. */
SolidML.Matrix4 = class {
  /** SolidML.Matrix4 passed by collback function of {@link SolidML#build}. you can convert for three.js by THREE.Matrix4.copy(SolidML.Matrix4). */
  constructor() { this.elements = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]); }
  fromArray(a) { this.elements.set(a); return this; }
  identity() { return this.fromArray([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]);}
  copy(m) { return this.fromArray(m.elements); }
  clone() { return new SolidML.Matrix4().copy(this); }
  makeTranslation(x, y, z) { return this.fromArray([1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1]); }
  makeScale(x, y, z) { return this.fromArray([x,0,0,0, 0,y,0,0, 0,0,z,0, 0,0,0,1]); }
  makeRotationX(theta) { const c=Math.cos(theta), s=Math.sin(theta); return this.fromArray([1,0,0,0, 0,c,-s,0, 0,s,c,0, 0,0,0,1]); }
  makeRotationY(theta) { const c=Math.cos(theta), s=Math.sin(theta); return this.fromArray([c,0,s,0, 0,1,0,0, -s,0,c,0, 0,0,0,1]); }
  makeRotationZ(theta) { const c=Math.cos(theta), s=Math.sin(theta); return this.fromArray([c,-s,0,0, s,c,0,0, 0,0,1,0, 0,0,0,1]); }
  multiply(m) { return this.multiplyMatrices(this, m); }
  premultiply(m) { return this.multiplyMatrices(m, this); }
  /** @return {number} 3dim-determinant calclate for size checking. */
  det3() {
    return Math.abs(this.signed_det3());
  }
  signed_det3() {
    const te = this.elements;
    return (te[0]*te[5]*te[10] - te[0]* te[6]*te[9] - te[1]*te[4]*te[10] + te[1]* te[6]*te[8] + te[2]*te[4]* te[9] - te[2]*te[5]*te[8]);
  }
  multiplyMatrices(a, b) {
    const ae = a.elements, be = b.elements;
    const a11 = ae[0], a12 = ae[4], a13 = ae[8],  a14 = ae[12];
    const a21 = ae[1], a22 = ae[5], a23 = ae[9],  a24 = ae[13];
    const a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14];
    const a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15];
    const b11 = be[0], b12 = be[4], b13 = be[8],  b14 = be[12];
    const b21 = be[1], b22 = be[5], b23 = be[9],  b24 = be[13];
    const b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14];
    const b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15];
    this.elements[0]  = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
    this.elements[4]  = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
    this.elements[8]  = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
    this.elements[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
    this.elements[1]  = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
    this.elements[5]  = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
    this.elements[9]  = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
    this.elements[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
    this.elements[2]  = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
    this.elements[6]  = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
    this.elements[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
    this.elements[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
    this.elements[3]  = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
    this.elements[7]  = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
    this.elements[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
    this.elements[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
    return this;
  }
  _applyToSrc_copyToDst(src, srcLength, dst, dstIndex, itemSize) {
    const te = this.elements;
    for (let i=0,j=dstIndex; i<srcLength; i++, j++) {
      const x = src[i*itemSize], y = src[i*itemSize+1], z = src[i*itemSize+2];
      const dx = te[0] * x + te[4] * y + te[8]  * z + te[12];
      const dy = te[1] * x + te[5] * y + te[9]  * z + te[13];
      const dz = te[2] * x + te[6] * y + te[10] * z + te[14];
      const iw = 1/(te[3] * x + te[7] * y + te[11] * z + te[15]);
      dst[j*itemSize]   = dx * iw;
      dst[j*itemSize+1] = dy * iw;
      dst[j*itemSize+2] = dz * iw;
    }
  }
}
/** Keep color infomations as (hue, saturation, brightness, alpha) */
SolidML.ColorHSBA = class {
  /** 
   * @param {number} [h] hue in degree (0-360)
   * @param {number} [s] satulation between 0 and 1
   * @param {number} [b] brightness between 0 and 1
   * @param {number} [a] alpha between 0 and 1
   */
  constructor(h=0,s=0,b=0,a=1) { 
    /** hue in degree (0-360)
     *  @type {number}
     */
    this.h = h; 
    /** satulation between 0 and 1
     *  @type {number}
     */
    this.s = s; 
    /** brightness between 0 and 1
     *  @type {number}
     */
    this.b = b; 
    /** alpha between 0 and 1
     *  @type {number}
     */
    this.a = a; 
    /** colorpool scheme for random generation, null for constant value
     *  @type {SolidML.ColorPool}
     */
    this.cp = null; 
  }
  /** set
   * @param {number} h hue in degree (0-360)
   * @param {number} s satulation between 0 and 1
   * @param {number} b brightness between 0 and 1
   * @param {number} a alpha between 0 and 1
   * @return {SolidML.ColorHSBA} this instance
   */
  set(h,s,b,a) {
    this.h = h;
    this.s = s;
    this.b = b;
    this.a = a;
    this.cp = null;
    return this;
  }
  /** copy from
   *  @param {SolidML.ColorHSBA} c source color copy from
   *  @return {SolidML.ColorHSBA} this instance
   */
  copy(c) {
    this.h = c.h;
    this.s = c.s;
    this.b = c.b;
    this.a = c.a;
    this.cp = c.cp;
    return this;
  }
  /** create clone
   * @return {SolidML.ColorHSBA} new instance
   */
  clone() {
    return new SolidML.ColorHSBA().copy(this);
  }
  /** update color by another color
   *  @param {SolidML.ColorHSBA} c color to multiply
   *  @return {SolidML.ColorHSBA} this instance
   */
  multiply(c) { 
    this.cp = null;
    this.h += c.h;
    this.s *= c.s;
    this.b *= c.b;
    this.a *= c.a;
    return this;
  }
  /** blend color with another SolidML.ColorHSBA 
   *  @param {SolidML.ColorHSBA} dst color to blend with
   *  @param {number} ratio blending ratio
   *  @return {SolidML.ColorHSBA} this instance
   */
  blend(dst, ratio) {
    this.cp = null; 
  	if (this.s > 0) {
	    let hdif = (dst.s == 0) ? 0 : dst.h - this.h;
	    hdif += (hdif<-180) ? 360 : ((hdif>180) ? -360 : 0);
	    this.h += hdif * ratio.h * dst.a;
  	} else 
  	  this.h = dst.h;
    this.s += (dst.s-this.s)*ratio.s*dst.a;
    this.b += (dst.b-this.b)*ratio.b*dst.a;
    return this;
  }
  /** set color by {r,g,b}
   *  @param {object} col object that has keys {r, g, b}
   *  @return {SolidML.ColorHSBA} this instance
   */
  setRGB(col) {
    const min = (col.g<=col.r)?((col.b<=col.g)?col.b:col.g):((col.b<=col.r)?col.b:col.r),
          max = (col.g>=col.r)?((col.b>=col.g)?col.b:col.g):((col.b>=col.r)?col.b:col.r);
    this.h = max-min;
    this.s = max?(this.h/max):0;
    this.b = max;
    if (this.h > 0) {
      if (max == col.r) 
        this.h = 60 * (col.g-col.b)/this.h + ((col.g>=col.b)?0:360);
      else if (max == col.g) 
        this.h = 60 * (col.b-col.r)/this.h + 120;
      else if (max == col.b) 
        this.h = 60 * (col.r-col.g)/this.h + 240;
    }
    return this;
  }
  /** set color by "#HEX" string
   *  @param {string} hexString color string stating with "#" and 3 or 6 hex.
   *  @return {SolidML.ColorHSBA} this instance
   */
  setHex(hexString) {
    const dig6 = (hexString.length > 6), 
          m = hexString.match(dig6 ? /#(..)(..)(..)/ : /#(.)(.)(.)/) || ["#fff"], 
          scale = dig6 ? 255 : 15;
    this.setRGB({r:parseInt(m[1],16)/scale, g:parseInt(m[2],16)/scale, b:parseInt(m[3],16)/scale});
    return this;
  }
  /** set random color generator 
   *  @param {SolidML.ColorPool} colorpoolInstance colorpool instance to generate random color
   */
  setColorPool(colorpoolInstance) {
    this.cp = colorpoolInstance;
    return this;
  }
  /** get color as {r,g,b,a}
   *  @return {object} object has keys {r,g,b,a}.
   */
  getRGBA() {
    if (this.cp) 
    	return this.cp.getRGBA();
    const hp = (this.h - Math.floor(this.h/360)*360) / 60, 
          c  = this.b * this.s, 
          x  = c * (1 - Math.abs(hp%2-1)), 
          m  = this.b - c, 
          a  = this.a;
    return (hp<1) ? {r:c+m,g:x+m,b:m,a} : (hp<2) ? {r:x+m,g:c+m,b:m,a} : (hp<3) ? {r:m,g:c+m,b:x+m,a} : 
           (hp<4) ? {r:m,g:x+m,b:c+m,a} : (hp<5) ? {r:x+m,g:m,b:c+m,a} : {r:c+m,g:m,b:x+m,a};
  }
  _incrementRandMT() {
  	if (this.cp) 
      this.cp._incrementRandMT();
  }
  _fillArray(dst, startat, count) {
    const col = this.getRGBA();
    for (let index=startat*4, c=0; c<count; c++) {
      dst[index++] = col.r;
      dst[index++] = col.g;
      dst[index++] = col.b;
      dst[index++] = col.a;
    }
  }
}
/** Represents various status while building structure, passed by the callback of {@link SolidML#build} */
SolidML.BuildStatus = class {
  /** [SHOULD NOT CREATE new instance] SolidML.BuildStatus instance is created by {@link SolidML}  */
  constructor(funcNewObject) {
    /** matrix to transform the object. convert for three.js by pass this to THREE.Matrix4.copy(). this instance has been updated in the building computation. Use {@link SolidML.Matrix4#clone} to remember and reuse the status.
     *  @type {SolidML.Matrix4}
     */
    this.matrix = new SolidML.Matrix4();
    /** color for the object. get RGBA values by {@link SolidML.ColorHSBA#getRGBA}. this instance has been updated in the building computation. Use {@link SolidML.ColorHSBA#clone} to remember and reuse the status.
     *  @type {SolidML.ColorHSBA}
     */
    this.color  = new SolidML.ColorHSBA(0,1,1,1);
    /** name of the object
     *  @type {string}
     */
    this.label = null;
    /** Array of options of the object written as "name:option"
     *  @type {string}
     */
    this.option = null;
    /** parameters of the object written as "name[param]"
     *  @type {string}
     */
    this.param = null;
    /** increment when the rule is called. used in composing contnuous mesh
     *  @type {int}
     */
    this.referenceID = 0;
    /** current object count
     *  @type {int}
     */
    this.objectCount = 0;
    /** parent rule's last called contnuous mesh ("mesh", "cmesh", "tube" and "ctube")
     *  @type {SolidML.BuildStatus}
     */
    this.lastContinuousMesh = null;
    /** current rule
     *  @type {SolidML.Rule}
     */
    this.rule = null;
    /** return value of {@link SolidML#build}
     *  @type {object}
     */
    this.result = null;
    // private
    this._stacMatrix = [];
    this._stacRule = [];
    this._referCounter = 0;
    this._continuousMesh = null;
    this._ruleDepth = {};
    this._rule_min3 = 0;
    this._rule_max3 = 0;
    this._newObjectStatus = true;
    this._funcNewObject = funcNewObject;
  }
  /** create new Object including clones of current status
   *  @return {Object} typeof {matrix, color, label, option, param, referenceID, objectCount}. rule, lastContinuousMesh and result are not cloned. 
   */
  reference() {
    return {"matrix": this.matrix.clone(),
            "color":  this.color.clone(),
            "label":  this.label,
            "option": this.option,
            "param":  this.param,
            "referenceID" : this.referenceID,
            "objectCount" : this.objectCount};
  }
  /** call this method to stop building immediately */
  stopBuilding() {
    this._newObjectStatus = false;
  }
  _push() {
    this._stacMatrix.push({"matrix":this.matrix.clone(), "color":this.color.clone()});
  }
  _pop() {
    const copyFrom = this._stacMatrix.pop();
    this.matrix.copy(copyFrom.matrix);
    this.color.copy(copyFrom.color);
  }
  _pushRule(rule) {
    if (++this._referCounter > 1)
      this.referenceID++;
    this._stacRule.push({
      "rule": this.rule, 
      "referCounter": this._referCounter, 
      "continuousMesh": this._continuousMesh
    });
    this.rule = rule;
    this._referCounter = 0;
    this.lastContinuousMesh = this._continuousMesh;
    this._continuousMesh = null;
    if (!(rule.name in this._ruleDepth))
      this._ruleDepth[rule.name] = 0;
    let imin = this.rule.minsize,
        imax = this.rule.maxsize;
    this._rule_min3 = imin * imin * imin;
    this._rule_max3 = imax * imax * imax;
    return (++this._ruleDepth[rule.name] <= rule.maxdepth);
  }
  _popRule() {
    this._ruleDepth[this.rule.name]--;
    const refCall = this._stacRule.pop();
    this.rule = refCall.rule;
    this._referCounter = refCall.referCounter;
    this._continuousMesh = refCall.continuousMesh;
    this.lastContinuousMesh = (this._stacRule.length > 0) ? this._stacRule[this._stacRule.length-1].continuousMesh : null;
  }
  _newObject(reference) {
    if (!this._checkCriteria())
      return false;
    this.label = reference.label;
    this.param = reference.param;
    this.option = reference.option;
    if (this.label in SolidML.ScriptParser._continuousMeshLabel) 
      this._continuousMesh = this.reference();
    this.objectCount++;
    this._funcNewObject(this);
    return this._newObjectStatus;
  }
  _checkCriteria() {
    const det3 = this.matrix.det3();
    return (this.objectCount < this.rule.rootInstance.criteria.maxobjects && this._rule_min3 < det3 && det3 < this._rule_max3);
  }
}
/** Represents rules in Eisen Script.  */
SolidML.Rule = class {
  /** [SHOULD NOT CREATE new instance] SolidML.Rule is create by {@link SolidML} */
  constructor(rootInstance, parentRule, name, option) {
    /** name of this rule. "$root" is set for top most rule 
     *  @type {string}
     */
    this.name = name || "$root";
    /** weight value of the rule set by weight option in Eisen Script. default is 1
     *  @type {number}
     */
    this.weight = 1;
    /** maxdepth value of the rule set by maxdepth option in Eisen Script. default is same as the {@link Criteria.maxdepth}
     *  @type {number}
     */
    this.maxdepth = rootInstance.criteria.maxdepth;
    /** termination rule name set by ">" option in Eisen Script. default is null
     *  @type {string}
     */
    this.maxdepthLabel = null;
    /** parent rule. null for top most rule
     *  @type {SolidML.Rule}
     */
    this.parent = parentRule;
    // private
    this._minsize = null;
    this._maxsize = null;
    this._colorpoolInstance = null;
    this.rootInstance = rootInstance;
    this.childRules = {};
    this.sequence = new SolidML.Reference(null, null);
    this._parser = new SolidML.ScriptParser(this);
    if (option) this._parser.ruleOption(option);
  }
  /** [read only] minimum size for this rule. 
   *  @type {number}
   */
  get minsize() {
    return this._minsize || (this.parent) ? this.parent.minsize : this.rootInstance.criteria.minsize;
  }
  /** [read only] maximum size for this rule. 
   *  @type {number}
   */
  get maxsize() {
    return this._maxsize || (this.parent) ? this.parent.maxsize : this.rootInstance.criteria.maxsize;
  }
  /** [read only] colorpool instance for this rule. 
   *  @type {SolidML.ColorPool}
   */
  get colorpoolInstance() {
    return this._colorpoolInstance || (this.parent) ? this.parent.colorpoolInstance : this.rootInstance.criteria._colorpoolInstance;
  }
  /** @return Returns true when there are no operation inside */
  isEmpty() {
    return (!this.sequence.next);
  }
  /** search rules by label
   *  @param {string} label label to search. search child rules first, then search ancestor rules.
   *  @return {SolidML.Rule} returns rule insatnce found by the label
   */
  find(label) {
    if (label in this.childRules) {
      const list = this.childRules[label],
            total = list.reduce((acm, rule)=>acm+rule.weight, 0),
            randnum = this.rootInstance.randMT.next() * total;
      for (let i=0, acm=0; i<list.length; i++) {
        acm += list[i].weight;
        if (randnum < acm) 
          return list[i];
      }
      return list[list.length-1];
    }
    return this.parent && this.parent.find(label);
  }
  _appendChild(rule) {
    if (!(rule.name in this.childRules)) this.childRules[rule.name] = [];
    this.childRules[rule.name].push(rule);
    return rule
  }
  _parse(script, lastIndex) {
    return this._parser.parse(script, lastIndex);
  }
  _build(stat) {
    if (this.isEmpty())
      return stat;
    if (stat._pushRule(this)) {
      let operator = this.sequence.next;
      while (operator) 
        operator = operator._build(stat);
    }
    else if (this.maxdepthLabel) {
      const rule = this.find(this.maxdepthLabel);
      if (rule) 
        rule._build(stat);
      else 
        throw Error("label [" + this.maxdepthLabel + "] not found in rule " + this.name);
    }
    stat._popRule();
    return stat;
  }
}
// private 
SolidML.Reference = class {
  constructor(prev, label, param) {
    if (prev) prev.next = this;
    if (label) {
      const s = label.split(":");
      this.label = s.shift();
      this.option = (s.length>0) ? s[0] : null;
    } else {
      this.label = null;
      this.option = null;
    }
    this.param = param;
    this.next = null;
  }
  _build(stat) {
    const rule = stat.rule.find(this.label);
    if (rule) {
      if (++stat._ruleDepth[rule.parent.name] <= rule.parent.maxdepth) 
        rule._build(stat);
      stat._ruleDepth[rule.parent.name]--;
    } else 
      if (!stat._newObject(this)) 
        return null;
    return this.next;
  }
}
// private 
SolidML.Operator = class {
  constructor(rule, prev, repeat, opeString) {
    this.parentRule = rule;
    this.repeat = Number(repeat) || 1;
    prev.next = this;
    this.next = null;
    this.opeString = null;
    this._parse(opeString);
  }
  clear() {
    this.matrix = new SolidML.Matrix4();
    this.color = null;
    this.dcolor = null;
    this.blend = new SolidML.ColorHSBA(0,1,1,0);
    this.ratio = new SolidML.ColorHSBA(0,0,0,0);
  }
  _hasVariable() {
    return Boolean(this.opeString);
  }
  _build(stat) {
    if (this._hasVariable()) 
      this._parse(this.opeString);
    let reference_next = null;
    stat._push();
    for (let i=this.repeat; i>0; i--) {
      stat.matrix.multiply(this.matrix);
      if (this.color) 
        stat.color.copy(this.color);
      if (this.dcolor)
        stat.color.multiply(this.dcolor);
      if (this.blend.a) 
        stat.color.blend(this.blend, this.ratio);
      if (this.next)
        reference_next = this.next._build(stat);
    }
    stat._pop();
    return reference_next;
  }
  _parse(opeString) {
    let m;
    const mat = new SolidML.Matrix4();
    const rex = SolidML.ScriptParser.operatorRex;
    const getNumber = (failThrough=false)=>{
      const li = rex.lastIndex, m = rex.exec(opeString);
      if (m) {
        if (m[2]) 
          return Number(m[2]);
        if (m[5]) {
          this.opeString = opeString;
          return this.parentRule.rootInstance.variables[m[5]] || 0;
        } 
      }
      if (!failThrough) 
        throw Error("parameter not defined : "+opeString);
      rex.lastIndex = li;
      return null; 
    };
    const getColor = (failThrough=false)=>{
      const li = rex.lastIndex, m = rex.exec(opeString);
      if (!m)
        throw Error("color not defined : "+opeString);
      if (m[1] in SolidML.ColorTable)
        return SolidML.ColorTable[m[1]];
      if (m[3])
        return m[3];
      if (m[5]) {
        this.opeString = opeString;
        return this.parentRule.rootInstance.variables[m[5]] || "#000";
      } 
      if (!failThrough)
        throw Error("color not defined : "+opeString);
      rex.lastIndex = li;
      return null;
    };
    this.clear();
    
    while (m = rex.exec(opeString)) {
      // direct color name
      if (m[1] in SolidML.ColorTable)
        this.color = new SolidML.ColorHSBA().setHex(SolidML.ColorTable[m[1]]);
      // direct color hex
      else if (m[3])
        this.color = new SolidML.ColorHSBA().setHex(m[3]);
      // blending by "*" command
      else if (m[4]) { 
        const nextColor = getColor(true);
        if (nextColor) {
          this.blend.setHex(nextColor);
          this.blend.a = getNumber();
          this.ratio.set(1,1,1,1);
        } else {
          m = rex.exec(opeString);
          switch (m[1]) {
            case "h":
              this.blend.a = 1;
              this.blend.h = getNumber();
              this.ratio.h = getNumber();
              break;
            case "s": 
              this.blend.a = 1;
              this.blend.s = getNumber();
              this.ratio.s = getNumber();
              break;
            case "b": 
              this.blend.a = 1;
              this.blend.b = getNumber();
              this.ratio.b = getNumber();
              break;
            default:
              throw Error("syntax error: " + opeString);
          }
        }
      } else {
        switch(m[1]) {
          case "hue":case "h":
            this.dcolor = this.dcolor || new SolidML.ColorHSBA(0,1,1,1);
            this.dcolor.h = getNumber();
            break;
          case "sat":
            this.dcolor = this.dcolor || new SolidML.ColorHSBA(0,1,1,1);
            this.dcolor.s = getNumber();
            break;
          case "brightness": case "b":
            this.dcolor = this.dcolor || new SolidML.ColorHSBA(0,1,1,1);
            this.dcolor.b = getNumber();
            break;
          case "alpha": case "a":
            this.dcolor = this.dcolor || new SolidML.ColorHSBA(0,1,1,1);
            this.dcolor.a = getNumber();
            break;
          case "blend":
            this.blend.setHex(getColor());
            this.blend.a = getNumber();
            this.ratio.set(1,1,1,1);
            break;
          case "color":
            const nextColor = getColor(true);
            if (nextColor)
              this.color = new SolidML.ColorHSBA().setHex(nextColor);
            else {
              m = rex.exec(opeString);
              if (m[1] == 'random')
                this.color = new SolidML.ColorHSBA().setColorPool(this.parentRule.colorpoolInstance);
              else
                throw Error("cannot parse : " + m[0]);
            }
            break;
          case "random": case "#?":
            this.color = new SolidML.ColorHSBA().setColorPool(this.parentRule.colorpoolInstance);
            break;
          case "x":
            this.matrix.multiply(mat.makeTranslation(getNumber(),0,0));
            break;
          case "y":
            this.matrix.multiply(mat.makeTranslation(0,getNumber(),0));
            break;
          case "z":
            this.matrix.multiply(mat.makeTranslation(0,0,getNumber()));
            break;
          case "rx":
            this.matrix.multiply(mat.makeRotationX(getNumber()/180*Math.PI));
            break;
          case "ry":
            this.matrix.multiply(mat.makeRotationY(getNumber()/180*Math.PI));
            break;
          case "rz":
            this.matrix.multiply(mat.makeRotationZ(getNumber()/180*Math.PI));
            break;
          case "fx":
            this.matrix.multiply(mat.makeScale(-1,1,1));
            break;
          case "fy":
            this.matrix.multiply(mat.makeScale(1,-1,1));
            break;
          case "fz":
            this.matrix.multiply(mat.makeScale(1,1,-1));
            break;
          case "s":
            const sx = getNumber();
            this.matrix.multiply(mat.makeScale(sx,getNumber(true)||sx,getNumber(true)||sx));
            break;
          case "m":
            mat.fromArray([
              getNumber(),getNumber(),getNumber(),0,
              getNumber(),getNumber(),getNumber(),0,
              getNumber(),getNumber(),getNumber(),0,
              0,0,0,1]);
            this.matrix.multiply(mat);
            break;
          default:
            throw Error("cannot parse : " + m[0]);
        }
      }
    }
  }
}
/** Represents colorpool in Eisen Script. */
SolidML.ColorPool = class {
  /**
   * create colorpool by scheme string
   * @param  {string} scheme scheme string of colorpool command
   * @param  {SolidML.randMT} randMT number generator for random picking
   */
  constructor(scheme, randMT) {
    this._randMT = randMT;
    this._dice = new SolidML.ColorHSBA();
    this.scheme = scheme;
  }
  /** 
   *  colorpool scheme, specifyed by "set colorpool " command 
   *  @type {string}
   */
  get scheme() {
    return this._schemeString;
  }
  set scheme(schemeString) {
    this._schemeString = schemeString;
    const match = /^\[(.*?)\]$/.exec(schemeString);
    if (match) {
      this._scheme = "list";
      this._option = match[1];
    } else { 
      const str = schemeString.split(":");
      this._scheme = str[0];
      this._option = str[1];
    }
    const step = Number(this._option) || 0;
    switch(this._scheme) {
      case "list":
        this.colorList = this._option.split(/\s*,\s*/).map(c=>this._dice.setHex(SolidML.ColorTable[c] || c).getRGBA());
        break;
      case "randomhue":
        if (step > 0) {
          this.colorList = [];
          for (let i=0; i<step; i++)
            this.colorList.push(this._dice.set(i/step*360, 1, 1, 1).getRGBA());
        }
        break;
      case "grayscale":
        if (step > 0) {
          this.colorList = [];
          for (let i=0; i<step; i++) {
            const v = i/(step-1);
            this.colorList.push({r:v, g:v, b:v, a:1});
          }
        }
        break;
      default:
        this.colorList = null;
        break;
    }
  }
  /**
   * get RGBA color value
   * @return {object} object that has {r, g, b, a} 
   */
  getRGBA() {
    const num = this._randMT.next();
    if (this.colorList)
      return this.colorList[(num * this.colorList.length)>>0];
    if (this._scheme == "randomhue")
      return this._dice.set(num*360, 1, 1, 1).getRGBA();
    if (this._scheme == "grayscale")
      return {r:num, g:num, b:num, a:1};
    return {r:num, g:this._randMT.next(), b:this._randMT.next(), a:1};
  }
  _incrementRandMT() {
    this._randMT.next();
    if (!this.colorList && this._scheme != "randomhue" && this._scheme != "grayscale") {
      this._randMT.next();
      this._randMT.next();
    }
  }
}
/** Mersenne Twister random number generator */
SolidML.randMT = class {
  /** constructor. {@link SolidML.randMT} is used for the model construction.
   *  @param {number} [seed] seed for random numbers list
   */
  constructor(seed=null) {
    this._mt = new Uint32Array(624);
    this.seed = seed;
  }
  /** seed for random number table. null to use Date.getTime() inside.
   *  @type {int} 
   */
  get seed() {
    return this._seed;
  }
  set seed(s) {
    if (s === null)
      s = new Date().getTime();
    this._seed = s;
    this._mt[0] = s>>>0;
    for (let i=1; i<this._mt.length; i++) 
      this._mt[i] = 1812433253 * (this._mt[i-1] ^ (this._mt[i-1] >>> 30)) + i;
    this._index = this._mt.length;
  }
  _nextInt() {
    const mt = this._mt;
    let v;
    if (this._index >= mt.length) {
      let k = 0, N = mt.length, M = 397;
      do {
        v = (mt[k] & 0x80000000) | (mt[k+1] & 0x7fffffff);
        mt[k] = mt[k+M] ^ (v >>> 1) ^ ((v & 1) ? 0x9908b0df : 0);
      } while (++k < N-M);
      do {
        v = (mt[k] & 0x80000000) | (mt[k+1] & 0x7fffffff);
        mt[k] = mt[k+M-N] ^ (v >>> 1) ^ ((v & 1) ? 0x9908b0df : 0);
      } while (++k < N-1);
      v = (mt[N-1] & 0x80000000) | (mt[0] & 0x7fffffff);
      mt[N-1] = mt[M-1] ^ (v >>> 1) ^ ((v & 1) ? 0x9908b0df : 0);
      this._index = 0;
    }
    v = mt[this._index++];
    v ^= v >>> 11;
    v ^= (v << 7) & 0x9d2c5680;
    v ^= (v << 15) & 0xefc60000;
    v ^= v >>> 18;
    return v >>> 0;
  }
  /** @return {Number} Returns random number and increment pointer for random number table */
  next() {
    return ((this._nextInt() >>> 5) * 0x4000000 + (this._nextInt() >>> 6)) / 0x20000000000000; 
  }
}
// [private] script parser
SolidML.ScriptParser = class {
  static _initialize() {
    if (!SolidML.ScriptParser.ruleRexString) {
      SolidML.ScriptParser._continuousMeshLabel = {"mesh":true, "cmesh":true, "tube":true, "ctube":true};
      SolidML.ScriptParser.nameRexString = "([a-zA-Z_][a-zA-Z0-9_:]*)";
      SolidML.ScriptParser.defineRexString = "(#define|\\$)\\s*([a-zA-Z_]+)[\\s=]*([^\\s]+)";
      SolidML.ScriptParser.criteriaRexString = "(set|@)\\s*([a-z:]*)\\s*([a-z:,]+|[\\-\\d.]+|#[0-9a-fA-F]+|\\[.+?\\])";
      SolidML.ScriptParser.ruleDefineRexString = "(rule|#)\\s*" + SolidML.ScriptParser.nameRexString + "(.*?){";
      SolidML.ScriptParser.matRexString = "(((\\d+)[\\s*]*)?\\{(.+?)\\})";
      SolidML.ScriptParser.referenceParamRexString = "(\\[(.+?)\\])?";
      SolidML.ScriptParser.ruleRexString = [
        SolidML.ScriptParser.defineRexString,
        SolidML.ScriptParser.criteriaRexString, 
        SolidML.ScriptParser.ruleDefineRexString, 
        SolidML.ScriptParser.matRexString, 
        SolidML.ScriptParser.nameRexString+SolidML.ScriptParser.referenceParamRexString, 
        "(})"
      ].join("|");
      SolidML.ScriptParser.ruleOptionRex = new RegExp("([a-z@]+)\\s*([\\-\\d.]+)|>\\s*" + SolidML.ScriptParser.nameRexString, "gm");
      SolidML.ScriptParser.operatorRex   = new RegExp("([a-z]+|#\\?)|([\\-\\d.]+)|(#[0-9a-fA-F]+)|(\\*)|\\$" + SolidML.ScriptParser.nameRexString, "gm");
      SolidML.REX_DEFINE = 1;
      SolidML.REX_DEFINE_KEY = 2;
      SolidML.REX_DEFINE_VALUE = 3;
      SolidML.REX_CRITERIA = 4;
      SolidML.REX_CRITERIA_CMD = 5;
      SolidML.REX_CRITERIA_PARAM = 6;
      SolidML.REX_RULEDEF = 7;
      SolidML.REX_RULEDEF_LABEL = 8;
      SolidML.REX_RULEDEF_OPTION = 9;
      SolidML.REX_MATRIX = 10;
      SolidML.REX_MATRIX_REPEAT = 12;
      SolidML.REX_MATRIX_OPERATOR = 13;
      SolidML.REX_REFERENCE = 14;
      SolidML.REX_REFERENCE_PARAM = 16;
      SolidML.REX_RULE_END = 17;
    }
  }
  // defined key reference change into "$key" and comment out
  static _normalizeScript(script) {
    let $, $$, ruleIndices=null;
    // remove all comments
    script = script.replace(/\/\/.*?$|\/\*[\s\S]*?\*\//gm, ' ');
    // insert "$" before defined variables
    const rexDefine = /#define\s+([a-zA-Z_]+)/gm;
    while ($ = rexDefine.exec(script)) {
      const rexReplace = new RegExp($[1], "gm");
      script = script.replace(rexReplace, (match, offset, string)=>{
        return (offset < rexDefine.lastIndex || string[offset-1] == "$") ? match : "$"+ match;
      });
    }
    // split rule includes "(A|B)" command
    const rexRuleSplit = /\(([\s\S]*?)\)/gm, 
          rexWeight = /(@w|w|weight)([\d.]+)/,
          mapWeight = s=>{
            const wmatch = s.match(rexWeight), 
                  w = (wmatch) ? parseFloat(wmatch[2]) : 1;
            return {s:" "+s.replace(rexWeight, "")+" ", w};
          };
    // seek "(A|B)"
    while ($ = rexRuleSplit.exec(script)) {
      // list of rule definitions indices sorted from end
      ruleIndices = [];
      const rex = new RegExp(SolidML.ScriptParser.ruleDefineRexString, "gm");
      while ($$ = rex.exec(script))
        ruleIndices.unshift($$);
      // split "A|B" into ["A","B"]
      const branch = $[1].split("|").map(mapWeight);
      // seek rule includes "(A|B)"
      const $ruleStart = ruleIndices.find((e,i,a)=>(e.index < $.index));
      if ($ruleStart) {
        const ruleDefine = $ruleStart[1] + $ruleStart[2],
              ruleOption = mapWeight($ruleStart[3]);
        // seek sequence start "{"
        const rex = /[{}]/gm;
        rex.lastIndex = $ruleStart.index;
        $$ = rex.exec(script);
        if ($$ && $$[0] == "{") {
          const sequenceStart = $$.index;
          // seek sequence end "}"
          let depth = 1;
          while ($$ = rex.exec(script)) 
            if ((depth += ($$[0] == "{") ? 1 : -1) == 0) break;
          if ($$) {
            // construct new rules
            const scriptBefore   = script.substring(0, $ruleStart.index),
                  scriptSequence = script.substring(sequenceStart, $$.index+1),
                  scriptAfter    = script.substring($$.index+1);
            const newRules = branch.map(b=>{
              const newSequence = scriptSequence.replace($[0], b.s),
                    newRuleOption = ruleOption.s + "@w" + String(ruleOption.w * b.w);
              return ruleDefine + newRuleOption + newSequence;
            }).join("\n");
            script = scriptBefore + newRules + scriptAfter;
            rexRuleSplit.lastIndex = $ruleStart.index;
          }
        }
      }
    }
    console.log("final rules : \n"+script);
    return script;
  }
  constructor(rule) {
    this._rule = rule;
  }
  ruleOption(option) {
    let m;
    while (m = SolidML.ScriptParser.ruleOptionRex.exec(option)) {
      switch(m[1]) {
        case "weight": case "w": case "@w":
          this._rule.weight = Number(m[2]);
          break;
        case "maxdepth": case "md": case "@":
          this._rule.maxdepth = Number(m[2]);
          break;
        default:
          this._rule.maxdepthLabel = m[3];
          break;
      }
    }
  }
  parse(script, lastIndex) {
    this._init(script, lastIndex);
    while (this._exec()) {
      if (!this._definition()) 
        if (!this._criteria()) 
          if (!this._matrix()) 
            if (!this._reference())
              if (!this._ruleStart()) 
                if (this._ruleEnd()) 
                  break;
                else 
                  throw Error("syntax error: "+this.$[0]);
    }
    return this._regExp.lastIndex;
  }
  _init(script, lastIndex) {
    this._regExp = new RegExp(SolidML.ScriptParser.ruleRexString, "gm");
    this._regExp.lastIndex = lastIndex;
    this._script = script;
    this._tail = this._rule.sequence;
    this.$ = null;
    return this;
  }
  _exec() {
    this.$ = this._regExp.exec(this._script);
    return Boolean(this.$);
  }
  _definition() {
    if (!this.$[SolidML.REX_DEFINE])
      return false;
    const key = this.$[SolidML.REX_DEFINE_KEY],
          value = this.$[SolidML.REX_DEFINE_VALUE],
          numv  = Number(value);
    this._rule.rootInstance.variables[key] = isNaN(numv) ? value : numv;
    return true;
  }
  _criteria() {
    if (!this.$[SolidML.REX_CRITERIA])
      return false;
    const criteria = this.$[SolidML.REX_CRITERIA_CMD],
          param = this.$[SolidML.REX_CRITERIA_PARAM];
    this._rule.rootInstance.criteria._set(criteria, param);
    return true;
  }
  _ruleStart() {
    if (!this.$[SolidML.REX_RULEDEF]) 
      return false;
    const label  = this.$[SolidML.REX_RULEDEF_LABEL],
          option = this.$[SolidML.REX_RULEDEF_OPTION],
          rule = new SolidML.Rule(this._rule.rootInstance, this._rule, label, option);
    this._rule._appendChild(rule);
    this._regExp.lastIndex = rule._parse(this._script, this._regExp.lastIndex);
    return true;
  }
  _matrix() {
    if (!this.$[SolidML.REX_MATRIX]) 
      return false;
    const repeat = this.$[SolidML.REX_MATRIX_REPEAT],
          operator = this.$[SolidML.REX_MATRIX_OPERATOR];
    this._tail = new SolidML.Operator(this._rule, this._tail, repeat, operator);
    return true;
  }
  _reference() {
    if (!this.$[SolidML.REX_REFERENCE])
      return false;
    const label = this.$[SolidML.REX_REFERENCE],
          param = this.$[SolidML.REX_REFERENCE_PARAM];
    this._tail = new SolidML.Reference(this._tail, label, param);
    return true;
  }
  _ruleEnd() {
    return Boolean(this.$[SolidML.REX_RULE_END]);
  }
}
/** svg name table for colors. SolidML.ColorTable["name"] returns hex string of the color.
 *  @type {Object}
 */
SolidML.ColorTable = {
  "black": "#000000",
  "navy": "#000080",
  "darkblue": "#00008b",
  "mediumblue": "#0000cd",
  "blue": "#0000ff",
  "darkgreen": "#006400",
  "green": "#008000",
  "teal": "#008080",
  "darkcyan": "#008b8b",
  "deepskyblue": "#00bfff",
  "darkturquoise": "#00ced1",
  "mediumspringgreen": "#00fa9a",
  "lime": "#00ff00",
  "springgreen": "#00ff7f",
  "cyan": "#00ffff",
  "aqua": "#00ffff",
  "midnightblue": "#191970",
  "dodgerblue": "#1e90ff",
  "lightseagreen": "#20b2aa",
  "forestgreen": "#228b22",
  "seagreen": "#2e8b57",
  "darkslategray": "#2f4f4f",
  "darkslategrey": "#2f4f4f",
  "limegreen": "#32cd32",
  "mediumseagreen": "#3cb371",
  "turquoise": "#40e0d0",
  "royalblue": "#4169e1",
  "steelblue": "#4682b4",
  "darkslateblue": "#483d8b",
  "mediumturquoise": "#48d1cc",
  "indigo": "#4b0082",
  "darkolivegreen": "#556b2f",
  "cadetblue": "#5f9ea0",
  "cornflowerblue": "#6495ed",
  "mediumaquamarine": "#66cdaa",
  "dimgrey": "#696969",
  "dimgray": "#696969",
  "slateblue": "#6a5acd",
  "olivedrab": "#6b8e23",
  "slategrey": "#708090",
  "slategray": "#708090",
  "lightslategray": "#778899",
  "lightslategrey": "#778899",
  "mediumslateblue": "#7b68ee",
  "lawngreen": "#7cfc00",
  "chartreuse": "#7fff00",
  "aquamarine": "#7fffd4",
  "maroon": "#800000",
  "purple": "#800080",
  "olive": "#808000",
  "gray": "#808080",
  "grey": "#808080",
  "skyblue": "#87ceeb",
  "lightskyblue": "#87cefa",
  "blueviolet": "#8a2be2",
  "darkred": "#8b0000",
  "darkmagenta": "#8b008b",
  "saddlebrown": "#8b4513",
  "darkseagreen": "#8fbc8f",
  "lightgreen": "#90ee90",
  "mediumpurple": "#9370db",
  "darkviolet": "#9400d3",
  "palegreen": "#98fb98",
  "darkorchid": "#9932cc",
  "yellowgreen": "#9acd32",
  "sienna": "#a0522d",
  "brown": "#a52a2a",
  "darkgray": "#a9a9a9",
  "darkgrey": "#a9a9a9",
  "lightblue": "#add8e6",
  "greenyellow": "#adff2f",
  "paleturquoise": "#afeeee",
  "lightsteelblue": "#b0c4de",
  "powderblue": "#b0e0e6",
  "firebrick": "#b22222",
  "darkgoldenrod": "#b8860b",
  "mediumorchid": "#ba55d3",
  "rosybrown": "#bc8f8f",
  "darkkhaki": "#bdb76b",
  "silver": "#c0c0c0",
  "mediumvioletred": "#c71585",
  "indianred": "#cd5c5c",
  "peru": "#cd853f",
  "chocolate": "#d2691e",
  "tan": "#d2b48c",
  "lightgray": "#d3d3d3",
  "lightgrey": "#d3d3d3",
  "thistle": "#d8bfd8",
  "orchid": "#da70d6",
  "goldenrod": "#daa520",
  "palevioletred": "#db7093",
  "crimson": "#dc143c",
  "gainsboro": "#dcdcdc",
  "plum": "#dda0dd",
  "burlywood": "#deb887",
  "lightcyan": "#e0ffff",
  "lavender": "#e6e6fa",
  "darksalmon": "#e9967a",
  "violet": "#ee82ee",
  "palegoldenrod": "#eee8aa",
  "lightcoral": "#f08080",
  "khaki": "#f0e68c",
  "aliceblue": "#f0f8ff",
  "honeydew": "#f0fff0",
  "azure": "#f0ffff",
  "sandybrown": "#f4a460",
  "wheat": "#f5deb3",
  "beige": "#f5f5dc",
  "whitesmoke": "#f5f5f5",
  "mintcream": "#f5fffa",
  "ghostwhite": "#f8f8ff",
  "salmon": "#fa8072",
  "antiquewhite": "#faebd7",
  "linen": "#faf0e6",
  "lightgoldenrodyellow": "#fafad2",
  "oldlace": "#fdf5e6",
  "red": "#ff0000",
  "fuchsia": "#ff00ff",
  "magenta": "#ff00ff",
  "deeppink": "#ff1493",
  "orangered": "#ff4500",
  "tomato": "#ff6347",
  "hotpink": "#ff69b4",
  "coral": "#ff7f50",
  "darkorange": "#ff8c00",
  "lightsalmon": "#ffa07a",
  "orange": "#ffa500",
  "lightpink": "#ffb6c1",
  "pink": "#ffc0cb",
  "gold": "#ffd700",
  "peachpuff": "#ffdab9",
  "navajowhite": "#ffdead",
  "moccasin": "#ffe4b5",
  "bisque": "#ffe4c4",
  "mistyrose": "#ffe4e1",
  "blanchedalmond": "#ffebcd",
  "papayawhip": "#ffefd5",
  "lavenderblush": "#fff0f5",
  "seashell": "#fff5ee",
  "cornsilk": "#fff8dc",
  "lemonchiffon": "#fffacd",
  "floralwhite": "#fffaf0",
  "snow": "#fffafa",
  "yellow": "#ffff00",
  "lightyellow": "#ffffe0",
  "ivory": "#fffff0",
  "white": "#ffffff"
};