title: Is it possible to extend Three.JS Mesh using classes?
body: <p>As i understand it ECMAscript 6 class syntax is just syntactical sugar for the prototypical pattern of inheritance used prior, however I am confused about how to extend the Three.js Mesh object to use with the class syntax.</p>
  
  <p>At the moment I have a working 'class' based on the prototypical pattern:</p>
  
  <pre><code>function GFAElement(params) {
      //
      var p = params.split("-");
      var userData = {
          type:    "GFA",
          flights: parseInt(p[0]),
          pitch:   parseInt(p[1]),
          length:  parseInt(p[2])
      };
  
      var shape = getProfile();
  
      var extrudeSettings = {
          steps: 5*userData.length,
          depth: userData.length,
          bevelEnabled: false
      };
  
      var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
      var material = new THREE.MeshStandardMaterial( {
          color: 0xffffff,
          metalness: 0.5,
          roughness: 0.5,
      } );
  
      THREE.Mesh.call( this, geometry, material );
  
      this.geometry.vertices.forEach( vertex =&gt; {
          var angle = 2*Math.PI/userData.flights*vertex.z/userData.pitch;
          var updateX = vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle);
          var updateY = vertex.y * Math.cos(angle) + vertex.x * Math.sin(angle);
          vertex.x = updateX;
          vertex.y = updateY;
      });
      this.geometry.computeFaceNormals();
      this.geometry.computeVertexNormals();
  
      this.type = 'GFAElement';
      this.userData = userData;
  
  }
  
  GFAElement.prototype = Object.create( THREE.Mesh.prototype );
  GFAElement.prototype.constructor = GFAElement;
  </code></pre>
  
  <p>I have made an attempt to convert this into the class syntax:</p>
  
  <pre><code>class GFAElement extends THREE.Mesh {
  
      constructor(params){
          super();
  
          this.type = 'GFAElement';
  
          var p = params.split("-");
          this.userData.type = "GFA",
          this.userData.flights = parseInt(p[0]);
          this.userData.pitch   = parseInt(p[1]);
          this.userData.length  = parseInt(p[2]);
  
          this.generate();
  
      }
  
      generate(){
  
          var shape = getProfile();
  
          var extrudeSettings = {
              steps: 5*this.userData.length,
              depth: this.userData.length,
              bevelEnabled: false
          };
  
          var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
          var material = new THREE.MeshStandardMaterial( {
              color: 0xffffff,
              metalness: 0.5,
              roughness: 0.5,
          } );
  
          THREE.Mesh.call( this, geometry, material );
  
          this.geometry.vertices.forEach( vertex =&gt; {
              var angle = 2*Math.PI/this.userData.flights*vertex.z/this.userData.pitch;
              var updateX = vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle);
              var updateY = vertex.y * Math.cos(angle) + vertex.x * Math.sin(angle);
              vertex.x = updateX;
              vertex.y = updateY;
          });
          this.geometry.computeFaceNormals();
          this.geometry.computeVertexNormals();
  
      }
  
  }
  </code></pre>
  
  <p>This however gives me an error:</p>
  
  <pre><code>three.min.js:11 Uncaught TypeError: Cannot redefine property: id
  </code></pre>
  
  <p>which i think is the result of an incompatible <code>extends THREE.Mesh</code> and <code>THREE.Mesh.call( this, geometry, material );</code>, however i have not been able to correct this incompatibility.</p>
  
  <p>This leaves me to my main question, how do i convert the above presented prototypical pattern into the class syntax?</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>'use strict';
  
  var container;
  var camera, scene, renderer, controls;
  var screw, mirror;
  
  // Screw parameters
  var P = 2; // number of flights
  
  var D = 50, // outer diameter
    Dr = D/1.66, // root diameter
    Cl = (Dr+D)/2, // centerline distance
    αi = 2*Math.acos(Cl/D),
    Ih = D*Math.sin(αi/2)/2,
    H = D-Cl;
  
  var αf = αi,
    αt = Math.PI/P - αf,
    αr = αt;
  
  //console.log(D, Dr, Cl, Ih, H);
  //console.log(αi, αf, αt, αr);
  
  function getFlankParams(α1, D1, α2, D2, ctr){
   // flanks are arcs with origin (xc, yc) of radius Cl passing through (x1, y1) and (x2, y2):
   // (x1-xc)^2 + (y1-yc)^2 = Cl^2
   // (x2-xc)^2 + (y2-yc)^2 = Cl^2
   var x1 = D1*Math.cos(α1),
     y1 = D1*Math.sin(α1),
     x2 = D2*Math.cos(α2),
     y2 = D2*Math.sin(α2);
   // Solving system of equations yields linear eq:
   // y1-yc = beta - alpha*(x1-xc)
   var alpha = (x1-x2)/(y1-y2), 
     beta = (y1-y2)*(1+Math.pow(alpha,2))/2;
   // Substitution and applying quadratic equation:
   var xc = x1 - alpha*beta/(1+Math.pow(alpha,2))*(1+Math.pow(-1,ctr)*Math.sqrt(1-(1-Math.pow(Cl/beta,2))*(1+1/Math.pow(alpha,2)))),
     yc = y1 + alpha*(x1-xc) - beta;
   // Following from law of consines, the angle the flank extends wrt its own origin:
   var asq = Math.pow(Dr/2,2)+Math.pow(D/2,2)-2*(Dr/2)*(D/2)*Math.cos(αf),
     af = Math.acos(1-asq/Math.pow(Cl, 2)/2);
  
   var params = {xc: xc, yc: yc, af: af};
   return params;
  }
  
  function getProfile() {
  
   var shape = new THREE.Shape();
   var angle = 0, ctr = 0;
   // loop over number of flights
   for (var p=0; p&lt;P; p++){
    // tip
    shape.absarc(0, 0, D/2, angle, angle+αt);
    angle += αt; 
    // flank
    var params = getFlankParams(angle, D/2, angle+αf, Dr/2, ctr++);
    shape.absarc(params.xc, params.yc, Cl, angle+αf-params.af, angle+αf, false);
    angle += αf; 
    // root
    shape.absarc(0, 0, Dr/2, angle, angle+αr);
    angle += αr; 
    // flank
    params = getFlankParams(angle, Dr/2, angle+αf, D/2, ctr++);
    shape.absarc(params.xc, params.yc, Cl, angle, angle+αf-params.af, false);
    angle += αf;
   }
   return shape;
  
  }
  
  function GFAElement(params) {
   //
   var p = params.split("-");
   var userData = {
    type:   "GFA",
    flights: parseInt(p[0]),
    pitch:   parseInt(p[1]),
    length:  parseInt(p[2])
   };
  
   var shape = getProfile();
  
   var extrudeSettings = {
    steps: 5*userData.length,
    depth: userData.length,
    bevelEnabled: false
   };
  
   var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
   var material = new THREE.MeshStandardMaterial( {
    color: 0xffffff,
    metalness: 0.5,
    roughness: 0.5,
   } );
  
   THREE.Mesh.call( this, geometry, material );
  
   this.geometry.vertices.forEach( vertex =&gt; {
    var angle = 2*Math.PI/userData.flights*vertex.z/userData.pitch;
    var updateX = vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle);
    var updateY = vertex.y * Math.cos(angle) + vertex.x * Math.sin(angle);
    vertex.x = updateX;
    vertex.y = updateY;
   });
   this.geometry.computeFaceNormals();
   this.geometry.computeVertexNormals();
  
   this.type = 'GFAElement';
   this.userData = userData;
    
  }
  
  GFAElement.prototype = Object.create( THREE.Mesh.prototype );
  GFAElement.prototype.constructor = GFAElement;
  //GFAElement.prototype.getMesh = function() {return this.mesh;}
  
  class NewGFAElement extends THREE.Mesh {
  
   constructor(params){
    super();
    
    this.type = 'GFAElement';
    
    var p = params.split("-");
    this.userData.type = "GFA",
    this.userData.flights = parseInt(p[0]);
    this.userData.pitch   = parseInt(p[1]);
    this.userData.length  = parseInt(p[2]);
    
    this.generate();
    
   }
  
   generate(){
   
    var shape = getProfile();
  
    var extrudeSettings = {
     steps: 5*this.userData.length,
     depth: this.userData.length,
     bevelEnabled: false
    };
  
    var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
    var material = new THREE.MeshStandardMaterial( {
     color: 0xffffff,
     metalness: 0.5,
     roughness: 0.5,
    } );
  
    THREE.Mesh.call( this, geometry, material );
    
    this.geometry.vertices.forEach( vertex =&gt; {
     var angle = 2*Math.PI/this.userData.flights*vertex.z/this.userData.pitch;
     var updateX = vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle);
     var updateY = vertex.y * Math.cos(angle) + vertex.x * Math.sin(angle);
     vertex.x = updateX;
     vertex.y = updateY;
    });
    this.geometry.computeFaceNormals();
    this.geometry.computeVertexNormals();
    
   }
  
  }
  
  class KBElement extends THREE.Group {
   //
   constructor(params){
    super();
     
    this.type = 'KBElement';
  
    var p = params.split("-");
    this.userData.type = "KB";
    this.userData.thickness = parseInt(p[0]);
    this.userData.flights   = parseInt(p[1]);
    this.userData.length    = parseInt(p[2]);
    this.userData.stagAngle = parseInt(p[3]);
  
    this.generate();
   }
   
   generate(){
   
    var shape = getProfile();
  
    var extrudeSettings = {
     depth: this.userData.thickness,
     bevelEnabled: false
    };
  
    var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
    var material = new THREE.MeshStandardMaterial( {
     color: 0xffffff,
     metalness: 0.5,
     roughness: 0.5,
    } );
  
    var mesh = new THREE.Mesh( geometry, material );
  
    super.add( mesh );
    for (var n=1, nt = this.userData.length/this.userData.thickness; n&lt;nt; n++){
     mesh = mesh.clone();
     mesh.position.z += this.userData.thickness;
     mesh.rotation.z += this.userData.stagAngle;
     super.add( mesh );
    }
    
   }
   
  }
  
  class Screw extends THREE.Group {
   //
   constructor(){
    super();
   }
  
   add(desc){
    var elem;
    var params = desc.split(" ");
    if (params[0] == "GFA") {
     elem = new NewGFAElement(params[1]);
    } else
    if (params[0] == "KB") {
     elem = new KBElement(params[1]);
    }
    super.add(elem);
   }
  
  }
  
  function init() {
   
   renderer = new THREE.WebGLRenderer();
   renderer.setPixelRatio( window.devicePixelRatio );
   renderer.setSize( window.innerWidth, window.innerHeight );
   //renderer.gammaInput = true;
   //renderer.gammaOutput = true;
   document.body.appendChild( renderer.domElement );
   
   scene = new THREE.Scene();
   scene.background = new THREE.Color( 0x222222 );
  
   camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
   camera.position.set( 200, 200, 200 );
    scene.add( camera );
   
   var light = new THREE.PointLight( 0xffffff );
   camera.add( light );
   
   controls = new THREE.TrackballControls( camera, renderer.domElement );
   controls.minDistance = 100;
   controls.maxDistance = 500;
  
   screw = new Screw();
   screw.add('GFA 2-40-90');
   //screw.add('KB 5-2-30-90');
   
   //var mirror = screw.mirror();
   //scene.add(screw, mirror);
    scene.add(screw);
   
   console.log(screw);
   
  }
  
  function animate() {
   
   //group.rotation.z  -= 2*Math.PI/100;
   //mirror.rotation.z -= 2*Math.PI/100;
  
   requestAnimationFrame( animate );
   controls.update();
   renderer.render( scene, camera );
  }
  
  init();
  animate();</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>   body {
      font-family: Monospace;
      background-color: #222;
      margin: 0px;
      overflow: hidden;
     }
     a {
      color: #f80;
     }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="https://rawgit.com/mrdoob/three.js/master/examples/js/controls/TrackballControls.js"&gt;&lt;/script&gt;
  &lt;script src="https://rawgit.com/mrdoob/three.js/master/build/three.min.js"&gt;&lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>