JavaScript 凸形墙壁碰撞,卡在角落

JavaScript wall collision on convex shapes, getting stuck at corner

这是对另一个问题的跟进:How do I handle player collision with corners of a wall

its answer中给出的代码的启发下,我尝试编写一些新代码。

基本上,在原来的情况下,墙壁滑动在墙壁内部效果很好,但我想让它在墙壁外部也能正常工作,所以我根据他的技术制作了一个新的基本代码引擎:

var aD =[]
var r
function start() {
 r = new CanvasRenderer(can),
  my = new scene();
 window.my = my
 eventHandler();
 my.add(new mesh({
  verts: [
   0,   0,
   100, 15,
   115, 60,
   50, 100,
   20, 75,2,8
  ],
  position: {
   x: 100,
   y:100
  },
  scale: {

   x:4,y:5
  },
  color:"orange",
  onupdate(me) {
  // me.position.x++
  }
 }));
 var g = false
 my.add(new mesh({
  primitive:"rect",
  name: "player",
  scale: {
   x: 50,
   y:50
  },
  position: {
   x: 311,
   y:75
  },
  origin: {
   x:0.5,
   y:0.5
  },
  onupdate(me) {
   var upKey = keys[38],
    downKey = keys[40],
    rightKey = keys[39],
    leftKey = keys[37],
    drx  = 0,
    dx = 0,
    speed = 5,
    turningSpeed = 3
   
   drx = leftKey ? -1 : rightKey ? 1 : 0
   forward = upKey ? 1 : downKey ? -1 : 0

   me.rotation.x += (
    (drx * Math.PI / 180 * turningSpeed )
   )
   me.rotation.y = 1;

   var xDir = Math.cos(me.rotation.x)
   var yDir = Math.sin(me.rotation.x)
   
   me.position.x += xDir  * forward * speed
   me.position.y += yDir * forward * speed

   for(var i = 0; i < my.objects.length; i++) {
    let cur = my.objects[i];
    if(cur.name !== me.name) {
     cur.lineSegments.forEach(l => {
      var col = checkCollision(
       me.position.x,
       me.position.y,
       me.scale.x/2,
       l
      )
      
      if(col) {
  
       me.position.y=col.y
       me.position.x = col.x
      }
      });
    }
   }


  
   
  }

 }));
 
 let i = setInterval(() => render(r, my), 16);
 r.on("resize", () => render(r, my));

}

function checkCollision(x1, y1, rad,l) {
  var dist = distance2(
       l.start[0],
       l.start[1],
       
       l.end[0],
       l.end[1]
      ),
       vec1 = [
        x1 - l.start[0],
        y1 - l.start[1]
       ],

       vec2 = [
        l.end[0] - l.start[0],
        l.end[1] - l.start[1]
       ],

       percentOfWall = (
        Math.max(
         0,
         Math.min(
          1, 
          dot(
           vec1[0],
           vec1[1],

           vec2[0],
           vec2[1]
          ) / dist
         )
        )
       ),
       projection = [
        l.start[0] + percentOfWall * vec2[0],
        l.start[1] + percentOfWall * vec2[1],
       ],
       acDist = Math.sqrt(distance2(
        x1, y1,
        projection[0], projection[1]
       ))
aD.push( () => {
      r.ctx.beginPath()
      r.ctx.fillStyle="green"
      r.ctx.arc(projection[0], projection[1], 5, 0, Math.PI*2);
      r.ctx.fill()
      r.ctx.closePath();
      })

     
     if(acDist < rad) {
      var mag = Math.sqrt(dist),
       delt = [
       l.end[0] - l.start[0],
       l.end[1] - l.start[1]
      ],
       normal = [
       delt[0] / mag,
       delt[1] / mag
      ]
      
      return {
      
       x: projection[0] + 

       rad * (normal[1] ),
      
        y:projection[1] + 
       rad* (-normal[0] ),
       projection,
       normal
      }
     }

     
}


function dot(x1, y1, x2, y2) {
 return (
  x1 * x2 + y1 * y2
 )
}

function distance2(x1, y1, x2, y2) {
 let dx = (x1 - x2), dy = (y1 - y2);
 return (
  dx * dx + dy * dy
 );
}

function render(r,s) {
//r.ctx.clearRect(0,0,r.ctx.canvas.width,r.ctx.canvas.height)
 s.update();
 r.render(s)
 aD.forEach(x=>x());
 aD = []
}

onload = start;

function eventHandler() {
 window.keys = {};
 addEventListener("keyup" , e=> {
  keys[e.keyCode] = false;
   
 });

 addEventListener("keydown" , e=> {
  keys[e.keyCode] = true;
 });
}

function CanvasRenderer(dom) {
 if(!dom) dom = document.createElement("canvas");
 
 var events = {}, self = this;
 function rsz() {
  dom.width = dom.clientWidth;
  dom.height = dom.clientHeight;
  self.dispatchEvent("resize");
 }
 
 window.addEventListener("resize", rsz); 

 let ctx = dom.getContext("2d");

 function render(scene) {
  ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
  for(let i = 0; i < scene.objects.length; i++) {
   let o = scene.objects[i],
    verts = o.realVerts;

   
   ctx.beginPath();
   ctx.moveTo(
    verts[0] , 

    verts[1]
   );
   verts.forEach((v, i, ar) => {
    let y = i;
    


    ctx.lineTo(
     v[0] , 

     v[1]
    );
    
   });
   ctx.lineTo(
    verts[0],
    verts[1] 
   );
   ctx.fillStyle = o.color || "blue";
   ctx.lineWidth = 1;
   ctx.fill()
   ctx.stroke();
   ctx.closePath();
  }
 }

 Object.defineProperties(this, {
  domElement: {
   get: () => dom
  }, 
  ctx: {
   get: () => ctx
  },
  render: {
   get: () => render
  },
  on: {
   get: () => (nm, cb) => {
    if(!events[nm]) {
     events[nm] = [];
    }
    events[nm].push(data => {
     if(typeof cb == "function") {
      cb(data);
     }
    });
   }  
  },
  dispatchEvent: {
   get: () => (name, data) => {
    if(events[name]) {
     events[name].forEach(x => {
      x(data);
     });
    }
   }
  }
 });
 
 rsz();

}

function scene() {
 let objects = [];
 Object.defineProperties(this, {
  add: {
   get: () => obj => {
    objects.push(obj);
   }
  },
  objects: {
   get: () => objects
  },
  update: {
   get: () => () => {    
    objects.forEach(x => {
     if(typeof x.update == "function") {
      x.update();
     }
    });
    
   }
  }
 });
}

function mesh(data={}) {
 let verts = [],
  self = this,
  holder = {
   position:{},
   scale: {
    
   },
   rotation: {},
   origin:{}
  },
  actual = {
 
  },
  position = {},
  scale = {},
  rotation = {},
  origin = {},
  color,
  name,
  primitive,
  eventNames = "update",
  events = {},
  drawPrimitive = {
   circle(ctx) {
    ctx.beginPath();
    ctx.arc(
     self.position.x,
     self.position.y,
     5, 
     0,
     360 * Math.PI / 180
    );
    ctx.closePath();
   },
   rect(ctx) {
    ctx.strokeRect(
     self.position.x,
     self.position.y,
     30, 30
    );
   }
  },
  width = 1,
  height = 1,
  primitiveToVerts = {
   rect: () =>  [
     0, 0,
     width , 0,
     width, height,
     0, height
   ]
  },
  realVerts = verts,
  lineSegments = [],
  o = this;
 
 function updateRealVerts() {
   
   let  actualVerts = [],
    originedVerts = [],
    adjustedVerts = [],
    rotatedVerts = [],
    stepSize = o.step || 2,
    curVerts = [];
   
   o.verts.forEach((v, i) => {
    curVerts.push(v);
    if(
     (i - 1) % stepSize === 0 &&
     i !== 0
    ) {
     actualVerts.push(curVerts);
     curVerts = [];
    }
   });
   actualVerts = actualVerts.filter(x => x.length == stepSize);
   
   originedVerts = actualVerts.map(v => [
    v[0] - o.origin.x,
    v[1] - o.origin.y,
    v[2] - o.origin.z
   ]);
 
   rotatedVerts = originedVerts.map(v => 
    [

     v[0] * Math.cos(o.rotation.x) - 
     v[1] * Math.sin(o.rotation.x),

     v[0] * Math.sin(o.rotation.x) + 
     v[1] *Math.cos(o.rotation.x),
v[2]
    ]
   );

   adjustedVerts = rotatedVerts.map(v => 
    [
     v[0] * 
     o.scale.x + 
     o.position.x,
 
     v[1] * 
     o.scale.y + 
     o.position.y,

     v[2] * 
     o.scale.z + 
     o.position.z,
    ]
   );

   realVerts = adjustedVerts;
   updateLineSegments();
 } 

 function updateLineSegments() {
    let lines = [];
    for(let i = 0, a = realVerts; i < a.length;i++) {
     let start = [], end = []
     if(i < a.length - 1) {
      start = a[i];
      end = a[i + 1];
     } else {
      start = a[i];
      end = a[0];
     }

     lines.push({
      start, end
     })
    }
    lineSegments = lines;
 }
 Object.defineProperties(position, {
  x: {
   get: () => holder.position.x || 0,
   set: v => holder.position.x = v
  },
  y: {
   get: () => holder.position.y || 0,
   set: v => holder.position.y = v
  },
  z: {
   get: () => holder.position.z || 0,
   set: v => holder.position.z = v
  }
 });

 Object.defineProperties(scale, {
  x: {
   get: () => holder.scale.x || 1,
   set: v => holder.scale.x = v
  },
  y: {
   get: () => holder.scale.y || 1,
   set: v => holder.scale.y = v
  },
  z: {
   get: () => holder.scale.z || 1,
   set: v => holder.scale.z = v
  }
 });

 Object.defineProperties(rotation, {
  x: {
   get: () => holder.rotation.x || 0,
   set: v => holder.rotation.x = v
  },
  y: {
   get: () => holder.rotation.y || 0,
   set: v => holder.rotation.y = v
  },
  z: {
   get: () => holder.rotation.z || 0,
   set: v => holder.rotation.z = v
  }
 });

 Object.defineProperties(origin, {
  x: {
   get: () => holder.origin.x || 0,
   set: v => holder.origin.x = v
  },
  y: {
   get: () => holder.origin.y || 0,
   set: v => holder.origin.y = v
  },
  z: {
   get: () => holder.origin.z || 0,
   set: v => holder.origin.z = v
  }
 });
 

 Object.defineProperties(this, {
  verts: {
   get: ()=>verts,
   set(v) {
    verts = v
   }
  },
  name: {
   get: ()=>name,
   set(v) {
    name = v
   }
  },
  primitive: {
   get: ()=>primitive,
   set(v) {
    primitive = v;
    let newVerts = primitiveToVerts[v];
    if(newVerts) {
     this.verts = newVerts();
    }
   }
  },
  width: {
   get: ()=>width,
   set(v) {
    width = v
   }
  },
  height: {
   get: ()=>height,
   set(v) {
    height = v
   }
  },
  position: {
   get: () => position,
   set: v => {
    position.x = v.x || 0;
    position.y = v.y || 0;
    position.z = v.z || 0;
   }
  },
  scale: {
   get: () => scale,
   set: v => {
    scale.x = v.x || v.x === 0 ? v.x : 1;
    scale.y = v.y  || v.y === 0 ? v.y : 1;
    scale.z = v.z  || v.z === 0 ? v.z : 1;
   }
  },
  rotation: {
   get: () => rotation,
   set: v => {
    rotation.x = v.x || 0;
    rotation.y = v.y || 0;
    rotation.z = v.z || 0;
   }
  },
  origin: {
   get: () => origin,
   set: v => {
    origin.x = v.x || 0;
    origin.y = v.y || 0;
    origin.z = v.z || 0;
   }
  },
  color: {
   get: () => color,
   set: v => {
    color = v;
   }
  },
  realVerts: {
   get: () => realVerts
  },
  lineSegments: {
   get: () => lineSegments
  },
  update: {
   get: () => () => {
    if(events["update"]) {
     events.update.forEach(x => {
      updateRealVerts();
      x(this);
     });
    }
   }
  },
  on: {
   get: () => (nm, fnc) => {
    if(!events[nm]) events[nm] = [];
    events[nm].push(stuff => {
     if(typeof fnc == "function") {
      fnc(stuff);
     }
    });
   }
  }
 });

 eventNames.split(" ").forEach(x => {
  var name = "on" + x;
  if(!this.hasOwnProperty(name)) {
   Object.defineProperty(this, name, {
    get: () => events[name],
    set(v) {
     events[x] = [
      data => {
       typeof v == "function" && v(data)
      }
     ];
    }
   });
  }
 });

 for(let k in data) {
  this[k] = data[k]
 }

 updateRealVerts();

}
canvas{
 width:100%;
 height:100%;
 position:absolute;
 top:0; 
 left:0px
}

.wow{
 float:right;
 z-index:1298737198
}
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>

请参阅第 71 行以了解碰撞检测实现调用(以及那里函数的 return 值)。

问题是,正如您所希望看到的(只是全屏显示并使用箭头键移动,尝试与角落处的橙色网格碰撞)它滑动得很好,但是当它到达角落时,它变得卡在他们身上。

关于如何解决此问题的任何想法 - 无需使用任何类型的外部库等(仅片段中可用的内容)?

我想说的是检查你不在角落里 - 当 percentOfWall 恰好是 0 或恰好是 1

时丢弃这些情况

编辑:为了解决评论中提到的问题,我必须解释为什么您的实施卡住了。它计算了所有墙壁的渗透,并减少了该渗透量的位置变化。角落里,物体一下子撞上了两边,一下子被两边弹开,不动了。

正如您正确注意到的那样,在拐角处,您的正方形进入障碍物内部一帧,然后在随后的帧中被推出。

然而,替代解决方案更复杂且更难调试,但这里是几个选项的草图:

  • 将碰撞限制为一次仅从一堵墙击退,并尽早退出 forEach 循环。这是一个简单的解决方案,适用于这个特定示例,但不适用于一般情况,例如在拐角处,当您需要与 2 堵墙碰撞以防止您向两个方向移动时。
  • 在每个角落添加一个小圆圈并与其碰撞以避免进入内部。法线沿着圆心和接触点之间的线。这平滑了你在角落里的法线不连续性,并且总是有一个单一的法线,物体被排斥,从一个部分连续变化到另一个部分。

将 "push" 指向障碍物的每个部分之外(这就是您所要求的)不会像在拐角处那样阻止停车(这正是您所关心的点),两堵墙将与您的对象发生碰撞,并且 "outside" 将处于相反的方向。所以它会像以前一样卡住,并且出于完全相同的原因 - 法线将不连续。

希望对您有所帮助

var aD =[]
var r
function start() {
 r = new CanvasRenderer(can),
  my = new scene();
 window.my = my
 eventHandler();
 my.add(new mesh({
  verts: [
   0,   0,
   100, 15,
   115, 60,
   50, 100,
   20, 75,2,8
  ],
  position: {
   x: 100,
   y:100
  },
  scale: {

   x:4,y:5
  },
  color:"orange",
  onupdate(me) {
  // me.position.x++
  }
 }));
 var g = false
 my.add(new mesh({
  primitive:"rect",
  name: "player",
  scale: {
   x: 50,
   y:50
  },
  position: {
   x: 311,
   y:75
  },
  origin: {
   x:0.5,
   y:0.5
  },
  onupdate(me) {
   var upKey = keys[38],
    downKey = keys[40],
    rightKey = keys[39],
    leftKey = keys[37],
    drx  = 0,
    dx = 0,
    speed = 5,
    turningSpeed = 3
   
   drx = leftKey ? -1 : rightKey ? 1 : 0
   forward = upKey ? 1 : downKey ? -1 : 0

   me.rotation.x += (
    (drx * Math.PI / 180 * turningSpeed )
   )
   me.rotation.y = 1;

   var xDir = Math.cos(me.rotation.x)
   var yDir = Math.sin(me.rotation.x)
   
   me.position.x += xDir  * forward * speed
   me.position.y += yDir * forward * speed

   for(var i = 0; i < my.objects.length; i++) {
    let cur = my.objects[i];
    if(cur.name !== me.name) {
     cur.lineSegments.forEach(l => {
      var col = checkCollision(
       me.position.x,
       me.position.y,
       me.scale.x/2,
       l
      )
      
      if(col) {
       me.position.y=col.y
       me.position.x = col.x
      }
      });
    }
   }


  
   
  }

 }));
 
 let i = setInterval(() => render(r, my), 16);
 r.on("resize", () => render(r, my));

}

function checkCollision(x1, y1, rad,l) {
  var dist = distance2(
       l.start[0],
       l.start[1],
       
       l.end[0],
       l.end[1]
      ),
       vec1 = [
        x1 - l.start[0],
        y1 - l.start[1]
       ],

       vec2 = [
        l.end[0] - l.start[0],
        l.end[1] - l.start[1]
       ],

       percentOfWall = (
        Math.max(
         0,
         Math.min(
          1, 
          dot(
           vec1[0],
           vec1[1],

           vec2[0],
           vec2[1]
          ) / dist
         )
        )
       ),
       projection = [
        l.start[0] + percentOfWall * vec2[0],
        l.start[1] + percentOfWall * vec2[1],
       ],
       acDist = Math.sqrt(distance2(
        x1, y1,
        projection[0], projection[1]
       ))
aD.push( () => {
      r.ctx.beginPath()
      r.ctx.fillStyle="green"
      r.ctx.arc(projection[0], projection[1], 5, 0, Math.PI*2);
      r.ctx.fill()
      r.ctx.closePath();
      })

     
     if(acDist < rad && percentOfWall > 0 && percentOfWall < 1) {
      var mag = Math.sqrt(dist),
       delt = [
       l.end[0] - l.start[0],
       l.end[1] - l.start[1]
      ],
       normal = [
       delt[0] / mag,
       delt[1] / mag
      ]
      
      return {
      
       x: projection[0] + 

       rad * (normal[1] ),
      
        y:projection[1] + 
       rad* (-normal[0] ),
       projection,
       normal
      }
     }

     
}


function dot(x1, y1, x2, y2) {
 return (
  x1 * x2 + y1 * y2
 )
}

function distance2(x1, y1, x2, y2) {
 let dx = (x1 - x2), dy = (y1 - y2);
 return (
  dx * dx + dy * dy
 );
}

function render(r,s) {
//r.ctx.clearRect(0,0,r.ctx.canvas.width,r.ctx.canvas.height)
 s.update();
 r.render(s)
 aD.forEach(x=>x());
 aD = []
}

onload = start;

function eventHandler() {
 window.keys = {};
 addEventListener("keyup" , e=> {
  keys[e.keyCode] = false;
   
 });

 addEventListener("keydown" , e=> {
  keys[e.keyCode] = true;
 });
}

function CanvasRenderer(dom) {
 if(!dom) dom = document.createElement("canvas");
 
 var events = {}, self = this;
 function rsz() {
  dom.width = dom.clientWidth;
  dom.height = dom.clientHeight;
  self.dispatchEvent("resize");
 }
 
 window.addEventListener("resize", rsz); 

 let ctx = dom.getContext("2d");

 function render(scene) {
  ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
  for(let i = 0; i < scene.objects.length; i++) {
   let o = scene.objects[i],
    verts = o.realVerts;

   
   ctx.beginPath();
   ctx.moveTo(
    verts[0] , 

    verts[1]
   );
   verts.forEach((v, i, ar) => {
    let y = i;
    


    ctx.lineTo(
     v[0] , 

     v[1]
    );
    
   });
   ctx.lineTo(
    verts[0],
    verts[1] 
   );
   ctx.fillStyle = o.color || "blue";
   ctx.lineWidth = 1;
   ctx.fill()
   ctx.stroke();
   ctx.closePath();
  }
 }

 Object.defineProperties(this, {
  domElement: {
   get: () => dom
  }, 
  ctx: {
   get: () => ctx
  },
  render: {
   get: () => render
  },
  on: {
   get: () => (nm, cb) => {
    if(!events[nm]) {
     events[nm] = [];
    }
    events[nm].push(data => {
     if(typeof cb == "function") {
      cb(data);
     }
    });
   }  
  },
  dispatchEvent: {
   get: () => (name, data) => {
    if(events[name]) {
     events[name].forEach(x => {
      x(data);
     });
    }
   }
  }
 });
 
 rsz();

}

function scene() {
 let objects = [];
 Object.defineProperties(this, {
  add: {
   get: () => obj => {
    objects.push(obj);
   }
  },
  objects: {
   get: () => objects
  },
  update: {
   get: () => () => {    
    objects.forEach(x => {
     if(typeof x.update == "function") {
      x.update();
     }
    });
    
   }
  }
 });
}

function mesh(data={}) {
 let verts = [],
  self = this,
  holder = {
   position:{},
   scale: {
    
   },
   rotation: {},
   origin:{}
  },
  actual = {
 
  },
  position = {},
  scale = {},
  rotation = {},
  origin = {},
  color,
  name,
  primitive,
  eventNames = "update",
  events = {},
  drawPrimitive = {
   circle(ctx) {
    ctx.beginPath();
    ctx.arc(
     self.position.x,
     self.position.y,
     5, 
     0,
     360 * Math.PI / 180
    );
    ctx.closePath();
   },
   rect(ctx) {
    ctx.strokeRect(
     self.position.x,
     self.position.y,
     30, 30
    );
   }
  },
  width = 1,
  height = 1,
  primitiveToVerts = {
   rect: () =>  [
     0, 0,
     width , 0,
     width, height,
     0, height
   ]
  },
  realVerts = verts,
  lineSegments = [],
  o = this;
 
 function updateRealVerts() {
   
   let  actualVerts = [],
    originedVerts = [],
    adjustedVerts = [],
    rotatedVerts = [],
    stepSize = o.step || 2,
    curVerts = [];
   
   o.verts.forEach((v, i) => {
    curVerts.push(v);
    if(
     (i - 1) % stepSize === 0 &&
     i !== 0
    ) {
     actualVerts.push(curVerts);
     curVerts = [];
    }
   });
   actualVerts = actualVerts.filter(x => x.length == stepSize);
   
   originedVerts = actualVerts.map(v => [
    v[0] - o.origin.x,
    v[1] - o.origin.y,
    v[2] - o.origin.z
   ]);
 
   rotatedVerts = originedVerts.map(v => 
    [

     v[0] * Math.cos(o.rotation.x) - 
     v[1] * Math.sin(o.rotation.x),

     v[0] * Math.sin(o.rotation.x) + 
     v[1] *Math.cos(o.rotation.x),
v[2]
    ]
   );

   adjustedVerts = rotatedVerts.map(v => 
    [
     v[0] * 
     o.scale.x + 
     o.position.x,
 
     v[1] * 
     o.scale.y + 
     o.position.y,

     v[2] * 
     o.scale.z + 
     o.position.z,
    ]
   );

   realVerts = adjustedVerts;
   updateLineSegments();
 } 

 function updateLineSegments() {
    let lines = [];
    for(let i = 0, a = realVerts; i < a.length;i++) {
     let start = [], end = []
     if(i < a.length - 1) {
      start = a[i];
      end = a[i + 1];
     } else {
      start = a[i];
      end = a[0];
     }

     lines.push({
      start, end
     })
    }
    lineSegments = lines;
 }
 Object.defineProperties(position, {
  x: {
   get: () => holder.position.x || 0,
   set: v => holder.position.x = v
  },
  y: {
   get: () => holder.position.y || 0,
   set: v => holder.position.y = v
  },
  z: {
   get: () => holder.position.z || 0,
   set: v => holder.position.z = v
  }
 });

 Object.defineProperties(scale, {
  x: {
   get: () => holder.scale.x || 1,
   set: v => holder.scale.x = v
  },
  y: {
   get: () => holder.scale.y || 1,
   set: v => holder.scale.y = v
  },
  z: {
   get: () => holder.scale.z || 1,
   set: v => holder.scale.z = v
  }
 });

 Object.defineProperties(rotation, {
  x: {
   get: () => holder.rotation.x || 0,
   set: v => holder.rotation.x = v
  },
  y: {
   get: () => holder.rotation.y || 0,
   set: v => holder.rotation.y = v
  },
  z: {
   get: () => holder.rotation.z || 0,
   set: v => holder.rotation.z = v
  }
 });

 Object.defineProperties(origin, {
  x: {
   get: () => holder.origin.x || 0,
   set: v => holder.origin.x = v
  },
  y: {
   get: () => holder.origin.y || 0,
   set: v => holder.origin.y = v
  },
  z: {
   get: () => holder.origin.z || 0,
   set: v => holder.origin.z = v
  }
 });
 

 Object.defineProperties(this, {
  verts: {
   get: ()=>verts,
   set(v) {
    verts = v
   }
  },
  name: {
   get: ()=>name,
   set(v) {
    name = v
   }
  },
  primitive: {
   get: ()=>primitive,
   set(v) {
    primitive = v;
    let newVerts = primitiveToVerts[v];
    if(newVerts) {
     this.verts = newVerts();
    }
   }
  },
  width: {
   get: ()=>width,
   set(v) {
    width = v
   }
  },
  height: {
   get: ()=>height,
   set(v) {
    height = v
   }
  },
  position: {
   get: () => position,
   set: v => {
    position.x = v.x || 0;
    position.y = v.y || 0;
    position.z = v.z || 0;
   }
  },
  scale: {
   get: () => scale,
   set: v => {
    scale.x = v.x || v.x === 0 ? v.x : 1;
    scale.y = v.y  || v.y === 0 ? v.y : 1;
    scale.z = v.z  || v.z === 0 ? v.z : 1;
   }
  },
  rotation: {
   get: () => rotation,
   set: v => {
    rotation.x = v.x || 0;
    rotation.y = v.y || 0;
    rotation.z = v.z || 0;
   }
  },
  origin: {
   get: () => origin,
   set: v => {
    origin.x = v.x || 0;
    origin.y = v.y || 0;
    origin.z = v.z || 0;
   }
  },
  color: {
   get: () => color,
   set: v => {
    color = v;
   }
  },
  realVerts: {
   get: () => realVerts
  },
  lineSegments: {
   get: () => lineSegments
  },
  update: {
   get: () => () => {
    if(events["update"]) {
     events.update.forEach(x => {
      updateRealVerts();
      x(this);
     });
    }
   }
  },
  on: {
   get: () => (nm, fnc) => {
    if(!events[nm]) events[nm] = [];
    events[nm].push(stuff => {
     if(typeof fnc == "function") {
      fnc(stuff);
     }
    });
   }
  }
 });

 eventNames.split(" ").forEach(x => {
  var name = "on" + x;
  if(!this.hasOwnProperty(name)) {
   Object.defineProperty(this, name, {
    get: () => events[name],
    set(v) {
     events[x] = [
      data => {
       typeof v == "function" && v(data)
      }
     ];
    }
   });
  }
 });

 for(let k in data) {
  this[k] = data[k]
 }

 updateRealVerts();

}
canvas{
 width:100%;
 height:100%;
 position:absolute;
 top:0; 
 left:0px
}

.wow{
 float:right;
 z-index:1298737198
}
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>