Skip to main content

BH

HI I'veI've been looking through a lot of answers here:

Resolving Circle-Circle collision

Circle to Circle collision, checking each circle against all others

Nestling into contact with a group of physics objects without exerting forces on them

https://stackoverflow.com/questions/345838/ball-to-ball-collision-detection-and-handling

https://stackoverflow.com/questions/53010947/detect-collision-between-two-circles-and-sliding-them-on-each-other

Sliding character inside the circle

2D Circle detection algorithm partially working

Box2d too much for Circle/Circle collision detection?

Circle Collision Resolution and Angular Momentum

https://flatredball.com/documentation/tutorials/math/circle-collision/

https://stackoverflow.com/questions/61161303/how-do-i-simulate-ball-ball-collision-without-having-the-balls-bounce --doesnt show sliding

https://stackoverflow.com/questions/26216173/accelerometer-rolling-ball-in-ball -- not sure how to implement in my case for angle a

Couldn'tI couldn't find exactly the same thing I was looking for, which is proper top-down character sliding of circle to circle movement.

I tried implemmentingimplementing things from other answers here but whenever the character moves around circle, for some reason it just goes inside it (wasI was able to get it to spin around edges at one point but was unpredictable)

var aD = []
var r

function start() {
  r = new CanvasRenderer(can)
  my = new scene();
  eventHandler();

  my.add(new mesh({
    position: {
      x: 700,
      y: 200
    },
    radius: 70,
    color: "yellow",
    primitive: "circle",
    collision: "circle"
  }))

  my.add(new mesh({
    primitive: "circle",

    collision: "circle",
    color: "blue",
    name: "player",
    radius: 40,
    position: {
      x: 311,
      y: 75
    },
    origin: {
      x: 0.5,
      y: 0.5
    },
    onupdate(me) {


      aD.push(() => {
        r.ctx.beginPath()
        r.ctx.fillStyle = "red"
        r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI * 2);
        r.ctx.fill()
        r.ctx.closePath();
      })

      if (!window.pl) pl = me
      var upKey = keys[38],
        downKey = keys[40],
        rightKey = keys[39],
        leftKey = keys[37],
        drx = 0,
        dx = 0,
        speed = 5,
        turningSpeed = 3
      //console.log(upKey)
      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)

      var xvl = xDir * forward * speed
      var yvl = yDir * forward * speed
      me.position.y //=col.y
      me.position.x // = col.x
      me.xvl = xvl
      me.yvl = yvl
      var next = {
        x: me.position.x + xvl,
        y: me.position.y + yvl
      }
      me.next = next
      var isColliding = []
      var coll;
      var curColls = []
      me.cols = []

      me.speed = speed
      me.yDir = yDir
      for (var i = 0; i < my.objects.length; i++) {
        let cur = my.objects[i];
        if (cur.name == me.name || cur == me) {
          break;
        }
        curColls = checkCollision(
          next.x,
          next.y,
          me.radius || me.scale.x / 2,
          cur
        )


        curColls.forEach(coll => {
          if (!coll) return;
          if (coll == me) {
            //  console.log("WH")
            return
          }


          cur.isCol = true
          isColliding.push({
            coll,
            object: cur
          })
          //    console.log(2222,coll.line)

        })
      }

      Array.from(me.cols)
        .forEach(q => {
          var w = isColliding.indexOf(q)
          if (w == -1 || q.object == me) {
            me.cols.splice(me.cols.indexOf(q), 1)
          }


        })
      var isme = me.cols.findIndex(q => q.object == me)
      if (isme > -1) {
        me.cols.splice(isme, 1)
        var ok = isColliding.findIndex(q => q.object == me)
        if (ok > -1) {
          isColliding.splice(ok, 1)
        }
      }
      if (!me.obs)
        me.obs = []
      var noad = false
      if (isColliding.length > 0) {

        isColliding.forEach(q => {
          var w = me.cols.indexOf(q)
          if (w == -1) {
            me.cols.push(q)
          }
        })





        var col = me.cols[0].coll
        var ob = me.cols[0].object
        if (!me.obs)
          me.obs = []

        if (col.type == "circle") {
          let vecBetweenX = next.x - ob.position.x;
          let vecBetweenY = next.y - ob.position.y;

          let heading = Math.atan2(vecBetweenY, vecBetweenX);

          var dst = Math.sqrt(
            vecBetweenX * vecBetweenX +
            vecBetweenY * vecBetweenY
          )
          var dirX = vecBetweenX / dst
          var dirY = vecBetweenY / dst

          var rt = heading

          var ris = ob.radius + me.radius

          var ax = Math.cos(rt) * ris
          var ay = Math.sin(rt) * ris

          /*
          var nx=ob.position.x+ax
          var ny=ob.position.y+ay
          */

          var nx = ob.position.x + dirX * ris
          var ny = ob.position.y + dirY + ris

          me.position.x = nx
          me.position.y = ny
          return


        }
      }

      me.position.x += xvl

      me.position.y += yvl
    }

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

}


function render(r, s) {

  s.update();
  r.render(s)
  aD.forEach(x => x());
  aD = []
}

onload = start;


function checkCollision(x1, y1, rad, ob) {
  var colls = [];
  if (ob.collision == "circle") {
    /*
        Ball 1:  center: p1=(x1,y1)  radius: r1
        Ball 2:  center: p2=(x2,y2)  radius: r2

        collision distance: R= r1 + r2
        actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

        collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
        actual distance:  r12= sqrt( dx*dx + dy*dy )
    */

    var r1 = rad;
    var r2 = ob.radius;

    var p1 = [x1, y1]
    var p2 = [ob.position.x, ob.position.y]

    var R = r1 + r2;
    var R2 = R * R;

    var dx = p2[0] - p1[0]
    var dy = p2[1] - p1[1]

    var r12 = (
      dx * dx + dy * dy
    )

    var r12s = Math.sqrt(r12)

    var isCollision = r12s < R

    var colPoint = {
      x: (x1 * r2 + p2[0] * r1) /
        (r1 + r2),
      y: (y1 * r2 + p2[1] * r1) /
        (r1 + r2),
      type: "circle"
    };

    aD.push(() => {
      r.ctx.beginPath()
      r.ctx.fillStyle = "salmon"
      r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI * 2);
      r.ctx.fill()
      r.ctx.closePath();
    })

    if (isCollision) {
      colls.push(colPoint)
    }
  }

  return colls;


}


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


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;


      if (o.primitive == "circle") {
        var fnc = o.drawPrimitive[o.primitive]
        if (typeof(fnc) == "function")
          fnc(ctx)
      }
      ctx.fillStyle = o.color || "blue";
      ctx.lineWidth = 1;
      ctx.fill()
      ctx.stroke();
    }
  }

  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 self = this,
    holder = {
      position: {},
      scale: {

      },
      rotation: {},
      origin: {}
    },
    position = {},
    scale = {},
    rotation = {},
    collision = "mesh",
    shape = null,
    origin = {},
    color,
    radius = 0,
    name,
    primitive,

    eventNames = "update",
    events = {},
    drawPrimitive = {
      circle(ctx) {
        ctx.beginPath();
        ctx.arc(
          self.position.x,
          self.position.y,
          radius,
          0,
          360 * Math.PI / 180
        );
        ctx.closePath();
      }
    },
    width = 1,
    height = 1;

  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(this, {

    name: {
      get: () => name,
      set(v) {
        name = v
      }
    },
    primitive: {
      get: () => primitive,
      set(v) {
        primitive = v;

      }
    },

    drawPrimitive: {
      get: () => drawPrimitive
    },
    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;
      }
    },

    radius: {
      get: () => radius || (width * scale.x) / 2,
      set: v => {
        radius = v;
        this.width = radius / 2
        this.height = radius / 2
      }
    },
    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;
      }
    },
    color: {
      get: () => color,
      set: v => {
        color = v;
      }
    },
    update: {
      get: () => () => {
        if (events["update"]) {
          events.update.forEach(x => {

            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]
  }


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

.wow {
  float: right;
  z-index: 1298737198
}
BH

<br>
  game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dst
                    var dirY=vecBetweenY/dst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script><canvas id=can></canvas>

justJust arrow keys to move

relevantRelevant part for circle sliding (contains code from different examples that was used at different times but nothing works):


                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dst
                    var dirY=vecBetweenY/dst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return

 
                }
            }
            

[Edit]I tried using method suggested in answer, but player is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges

BH

HI I've been looking through a lot of answers here:

Resolving Circle-Circle collision

Circle to Circle collision, checking each circle against all others

Nestling into contact with a group of physics objects without exerting forces on them

https://stackoverflow.com/questions/345838/ball-to-ball-collision-detection-and-handling

https://stackoverflow.com/questions/53010947/detect-collision-between-two-circles-and-sliding-them-on-each-other

Sliding character inside the circle

2D Circle detection algorithm partially working

Box2d too much for Circle/Circle collision detection?

Circle Collision Resolution and Angular Momentum

https://flatredball.com/documentation/tutorials/math/circle-collision/

https://stackoverflow.com/questions/61161303/how-do-i-simulate-ball-ball-collision-without-having-the-balls-bounce --doesnt show sliding

https://stackoverflow.com/questions/26216173/accelerometer-rolling-ball-in-ball -- not sure how to implement in my case for angle a

Couldn't find exactly the same thing I was looking for, which is proper top-down character sliding of circle to circle movement.

I tried implemmenting things from other answers here but whenever the character moves around circle, for some reason it just goes inside it (was able to get it to spin around edges at one point but was unpredictable)

BH

<br>
 game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dst
                    var dirY=vecBetweenY/dst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script>

just arrow keys to move

relevant part for circle sliding (contains code from different examples that was used at different times but nothing works):


                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dst
                    var dirY=vecBetweenY/dst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return

 
                }
            }
            

[Edit] tried using method suggested in answer, but player is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges

I've been looking through a lot of answers here:

I couldn't find exactly the same thing I was looking for, which is proper top-down character sliding of circle to circle movement.

I tried implementing things from other answers here but whenever the character moves around circle, for some reason it just goes inside it (I was able to get it to spin around edges at one point but was unpredictable)

var aD = []
var r

function start() {
  r = new CanvasRenderer(can)
  my = new scene();
  eventHandler();

  my.add(new mesh({
    position: {
      x: 700,
      y: 200
    },
    radius: 70,
    color: "yellow",
    primitive: "circle",
    collision: "circle"
  }))

  my.add(new mesh({
    primitive: "circle",

    collision: "circle",
    color: "blue",
    name: "player",
    radius: 40,
    position: {
      x: 311,
      y: 75
    },
    origin: {
      x: 0.5,
      y: 0.5
    },
    onupdate(me) {


      aD.push(() => {
        r.ctx.beginPath()
        r.ctx.fillStyle = "red"
        r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI * 2);
        r.ctx.fill()
        r.ctx.closePath();
      })

      if (!window.pl) pl = me
      var upKey = keys[38],
        downKey = keys[40],
        rightKey = keys[39],
        leftKey = keys[37],
        drx = 0,
        dx = 0,
        speed = 5,
        turningSpeed = 3
      //console.log(upKey)
      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)

      var xvl = xDir * forward * speed
      var yvl = yDir * forward * speed
      me.position.y //=col.y
      me.position.x // = col.x
      me.xvl = xvl
      me.yvl = yvl
      var next = {
        x: me.position.x + xvl,
        y: me.position.y + yvl
      }
      me.next = next
      var isColliding = []
      var coll;
      var curColls = []
      me.cols = []

      me.speed = speed
      me.yDir = yDir
      for (var i = 0; i < my.objects.length; i++) {
        let cur = my.objects[i];
        if (cur.name == me.name || cur == me) {
          break;
        }
        curColls = checkCollision(
          next.x,
          next.y,
          me.radius || me.scale.x / 2,
          cur
        )


        curColls.forEach(coll => {
          if (!coll) return;
          if (coll == me) {
            //  console.log("WH")
            return
          }


          cur.isCol = true
          isColliding.push({
            coll,
            object: cur
          })
          //    console.log(2222,coll.line)

        })
      }

      Array.from(me.cols)
        .forEach(q => {
          var w = isColliding.indexOf(q)
          if (w == -1 || q.object == me) {
            me.cols.splice(me.cols.indexOf(q), 1)
          }


        })
      var isme = me.cols.findIndex(q => q.object == me)
      if (isme > -1) {
        me.cols.splice(isme, 1)
        var ok = isColliding.findIndex(q => q.object == me)
        if (ok > -1) {
          isColliding.splice(ok, 1)
        }
      }
      if (!me.obs)
        me.obs = []
      var noad = false
      if (isColliding.length > 0) {

        isColliding.forEach(q => {
          var w = me.cols.indexOf(q)
          if (w == -1) {
            me.cols.push(q)
          }
        })





        var col = me.cols[0].coll
        var ob = me.cols[0].object
        if (!me.obs)
          me.obs = []

        if (col.type == "circle") {
          let vecBetweenX = next.x - ob.position.x;
          let vecBetweenY = next.y - ob.position.y;

          let heading = Math.atan2(vecBetweenY, vecBetweenX);

          var dst = Math.sqrt(
            vecBetweenX * vecBetweenX +
            vecBetweenY * vecBetweenY
          )
          var dirX = vecBetweenX / dst
          var dirY = vecBetweenY / dst

          var rt = heading

          var ris = ob.radius + me.radius

          var ax = Math.cos(rt) * ris
          var ay = Math.sin(rt) * ris

          /*
          var nx=ob.position.x+ax
          var ny=ob.position.y+ay
          */

          var nx = ob.position.x + dirX * ris
          var ny = ob.position.y + dirY + ris

          me.position.x = nx
          me.position.y = ny
          return


        }
      }

      me.position.x += xvl

      me.position.y += yvl
    }

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

}


function render(r, s) {

  s.update();
  r.render(s)
  aD.forEach(x => x());
  aD = []
}

onload = start;


function checkCollision(x1, y1, rad, ob) {
  var colls = [];
  if (ob.collision == "circle") {
    /*
        Ball 1:  center: p1=(x1,y1)  radius: r1
        Ball 2:  center: p2=(x2,y2)  radius: r2

        collision distance: R= r1 + r2
        actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

        collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
        actual distance:  r12= sqrt( dx*dx + dy*dy )
    */

    var r1 = rad;
    var r2 = ob.radius;

    var p1 = [x1, y1]
    var p2 = [ob.position.x, ob.position.y]

    var R = r1 + r2;
    var R2 = R * R;

    var dx = p2[0] - p1[0]
    var dy = p2[1] - p1[1]

    var r12 = (
      dx * dx + dy * dy
    )

    var r12s = Math.sqrt(r12)

    var isCollision = r12s < R

    var colPoint = {
      x: (x1 * r2 + p2[0] * r1) /
        (r1 + r2),
      y: (y1 * r2 + p2[1] * r1) /
        (r1 + r2),
      type: "circle"
    };

    aD.push(() => {
      r.ctx.beginPath()
      r.ctx.fillStyle = "salmon"
      r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI * 2);
      r.ctx.fill()
      r.ctx.closePath();
    })

    if (isCollision) {
      colls.push(colPoint)
    }
  }

  return colls;


}


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


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;


      if (o.primitive == "circle") {
        var fnc = o.drawPrimitive[o.primitive]
        if (typeof(fnc) == "function")
          fnc(ctx)
      }
      ctx.fillStyle = o.color || "blue";
      ctx.lineWidth = 1;
      ctx.fill()
      ctx.stroke();
    }
  }

  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 self = this,
    holder = {
      position: {},
      scale: {

      },
      rotation: {},
      origin: {}
    },
    position = {},
    scale = {},
    rotation = {},
    collision = "mesh",
    shape = null,
    origin = {},
    color,
    radius = 0,
    name,
    primitive,

    eventNames = "update",
    events = {},
    drawPrimitive = {
      circle(ctx) {
        ctx.beginPath();
        ctx.arc(
          self.position.x,
          self.position.y,
          radius,
          0,
          360 * Math.PI / 180
        );
        ctx.closePath();
      }
    },
    width = 1,
    height = 1;

  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(this, {

    name: {
      get: () => name,
      set(v) {
        name = v
      }
    },
    primitive: {
      get: () => primitive,
      set(v) {
        primitive = v;

      }
    },

    drawPrimitive: {
      get: () => drawPrimitive
    },
    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;
      }
    },

    radius: {
      get: () => radius || (width * scale.x) / 2,
      set: v => {
        radius = v;
        this.width = radius / 2
        this.height = radius / 2
      }
    },
    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;
      }
    },
    color: {
      get: () => color,
      set: v => {
        color = v;
      }
    },
    update: {
      get: () => () => {
        if (events["update"]) {
          events.update.forEach(x => {

            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]
  }


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

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

Just arrow keys to move

Relevant part for circle sliding (contains code from different examples that was used at different times but nothing works):

if(col.type=="circle"){
    let vecBetweenX = next.x - ob.position.x;
    let vecBetweenY = next.y - ob.position.y;

    let heading = Math.atan2(vecBetweenY, vecBetweenX);

    var dst = Math.sqrt(
        vecBetweenX*vecBetweenX
        +
        vecBetweenY*vecBetweenY
    )
    var dirX=vecBetweenX/dst
    var dirY=vecBetweenY/dst

    var rt= heading
    
    var ris = ob.radius + me.radius

    var ax = Math.cos(rt)*ris
    var ay = Math.sin(rt)*ris

    /*
    var nx=ob.position.x+ax
    var ny=ob.position.y+ay
    */
    
    var nx=ob.position.x+dirX*ris
    var ny=ob.position.y+dirY+ris
    
    me.position.x = nx
    me.position.y = ny
    return
}

I tried using method suggested in answer, but player is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges

Notice removed Draw attention by B''H Bi'ezras -- Boruch Hashem
Bounty Ended with John Haggerty's answer chosen by B''H Bi'ezras -- Boruch Hashem
added 1 character in body
Source Link
BH

<br>
game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/distdst
                    var dirY=vecBetweenY/distdst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script>

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/distdst
                    var dirY=vecBetweenY/distdst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
BH

<br>
game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dist
                    var dirY=vecBetweenY/dist

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script>

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dist
                    var dirY=vecBetweenY/dist

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
BH

<br>
game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dst
                    var dirY=vecBetweenY/dst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script>

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dst
                    var dirY=vecBetweenY/dst

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
added 495 characters in body
Source Link
BH

<br>
game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = me.positionnext.x - ob.position.x;
                    let vecBetweenY = me.positionnext.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);




                    var rt= me.rotation.x//+heading


                    var axdst = Math.cossqrt(rt)*speed*forward
                    var ay = Math.sin(rt)*speed*forward vecBetweenX*vecBetweenX
                        +
                    difx = -ax  vecBetweenY*vecBetweenY
                    dify)
 = -ay                  var dirX=vecBetweenX/dist
                    var dirY=vecBetweenY/dist

                    var rrrt= heading
                    
                    var ris = ob.radius- + me.radius

                    var nx=ob.positionax = Math.x+rr*axcos(rt)*ris
                    var ny=ob.positionay = Math.y+rr*aysin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script>

                if(col.type=="circle"){
                    let vecBetweenX = me.positionnext.x - ob.position.x;
                    let vecBetweenY = me.positionnext.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dist
                    var dirY=vecBetweenY/dist

                    var rt= meheading
                    
                    var ris = ob.rotationradius + me.x//+heading
radius

                    var ax = Math.cos(rt)*speed*forward*ris
                    var ay = Math.sin(rt)*speed*forward*ris

                    difx = -ax/*
                    dify =var -ay
nx=ob.position.x+ax
                    var rr = obny=ob.radius-meposition.radiusy+ay
                    */
                    
                    var nx=ob.position.x+rr*axx+dirX*ris
                    var ny=ob.position.y+rr*ayy+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }

ive[Edit] tried following the complex math questions hereusing method suggested in answer, but my mind can only understand codeplayer is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges

BH

<br>
game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = me.position.x - ob.position.x;
                    let vecBetweenY = me.position.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);




                    var rt= me.rotation.x//+heading


                    var ax = Math.cos(rt)*speed*forward
                    var ay = Math.sin(rt)*speed*forward

                    difx = -ax
                    dify = -ay

                    var rr = ob.radius-me.radius

                    var nx=ob.position.x+rr*ax
                    var ny=ob.position.y+rr*ay

                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script>

                if(col.type=="circle"){
                    let vecBetweenX = me.position.x - ob.position.x;
                    let vecBetweenY = me.position.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);




                    var rt= me.rotation.x//+heading


                    var ax = Math.cos(rt)*speed*forward
                    var ay = Math.sin(rt)*speed*forward

                    difx = -ax
                    dify = -ay

                    var rr = ob.radius-me.radius

                    var nx=ob.position.x+rr*ax
                    var ny=ob.position.y+rr*ay

                    me.position.x = nx
                    me.position.y = ny
                    return


                }

ive tried following the complex math questions here, but my mind can only understand code

BH

<br>
game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>

</canvas>
<style>
    canvas{
        width:100%;
        height:100%;
        position:absolute;
        top:0;  
        left:0px
    }

    .wow{
        float:right;
        z-index:1298737198
    }

</style>
<script>
var aD =[]
var r
function start() {
    r = new CanvasRenderer(can)
    my = new scene();
    eventHandler();
    
    my.add(new mesh({
        position:{
            x:700,
            y:200
        },
        radius:70,
        color:"yellow",
        primitive:"circle",
        collision:"circle"
    }))
    
    my.add(new mesh({
        primitive:"circle",
        
        collision:"circle",
        color:"blue",
        name: "player",
        radius:40,
        position: {
            x: 311,
            y:75
        },
        origin: {
            x:0.5,
            y:0.5
        },
        onupdate(me) {
            

            aD.push( () => {
                r.ctx.beginPath()
                r.ctx.fillStyle="red"
                r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
                r.ctx.fill()
                r.ctx.closePath();
            })
            
            if(!window.pl)pl=me
            var upKey = keys[38],
                downKey = keys[40],
                rightKey = keys[39],
                leftKey = keys[37],
                drx  = 0,
                dx = 0,
                speed = 5,
                turningSpeed = 3
            //console.log(upKey)
            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)
            
            var xvl = xDir  * forward * speed
            var yvl = yDir * forward * speed
            me.position.y//=col.y
            me.position.x// = col.x
            me.xvl=xvl
            me.yvl=yvl
            var next = {
                x:me.position.x+xvl,
                y:me.position.y+yvl
            }
            me.next=next
            var isColliding = []
            var coll;
            var curColls = []
            me.cols=[]

            me.speed=speed
            me.yDir=yDir
            for(var i = 0; i < my.objects.length; i++) {
                let cur = my.objects[i];
                if(cur.name == me.name || cur == me) {
                    break;  
                }
                curColls = checkCollision(
                    next.x,
                    next.y,
                    me.radius||me.scale.x/2,
                    cur
                )
                
                
                curColls.forEach(coll=>{
                    if(!coll) return;
                    if(coll==me) {
                    //  console.log("WH")
                        return  
                    }
                    
                    
                    cur.isCol=true
                    isColliding.push({coll,object:cur}) 
                    //  console.log(2222,coll.line)

                })
            }
            
            Array.from(me.cols)
                .forEach(q=>{
                    var w=isColliding.indexOf(q)
                    if(w==-1||q.object==me){
                        me.cols.splice(me.cols.indexOf(q),1)
                    }
                    

            })
            var isme=me.cols.findIndex(q=>q.object==me)
            if(isme>-1){
                me.cols.splice(isme,1)
                var ok=isColliding.findIndex(q=>q.object==me)
                if(ok>-1){
                    isColliding.splice(ok,1)    
                }
            }
            if(!me.obs)
                me.obs=[]
            var noad=false
            if(isColliding.length > 0) {
            
                isColliding.forEach(q=>{
                    var w = me.cols.indexOf(q)
                    if(w==-1){
                        me.cols.push(q) 
                    }
                })
                
                
                
                
                
                var col =me.cols[0].coll
                var ob=me.cols[0].object
                if(!me.obs)
                    me.obs=[]

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dist
                    var dirY=vecBetweenY/dist

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }
            
            me.position.x += xvl
            
            me.position.y += yvl
        }

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

}
    
    
function render(r,s) {
    
    s.update();
    r.render(s)
    aD.forEach(x=>x());
    aD = []
}

onload = start;


function checkCollision(x1, y1,rad,ob) {
    var colls = [];
    if(ob.collision == "circle") {
        /*
            Ball 1:  center: p1=(x1,y1)  radius: r1
            Ball 2:  center: p2=(x2,y2)  radius: r2

            collision distance: R= r1 + r2
            actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

            collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
            actual distance:  r12= sqrt( dx*dx + dy*dy )
        */
        
        var r1 = rad;
        var r2 = ob.radius;
        
        var p1 = [x1,y1]
        var p2 = [ob.position.x,ob.position.y]
        
        var R = r1 + r2;
        var R2 = R * R;
        
        var dx = p2[0] - p1[0]
        var dy = p2[1] - p1[1]
        
        var r12 = (
            dx * dx + dy * dy
        )
        
        var r12s = Math.sqrt(r12)
        
        var isCollision = r12s < R
        
        var colPoint = {
            x:(x1*r2 + p2[0]*r1) / 
                (r1+r2),
            y:(y1*r2+p2[1]*r1) /
                (r1+r2),
            type:"circle"
        };

        aD.push( () => {
            r.ctx.beginPath()
            r.ctx.fillStyle="salmon"
            r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
            r.ctx.fill()
            r.ctx.closePath();
        })

        if(isCollision) {
            colls.push(colPoint)
        }
    }
    
    return colls;

                    
}

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

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;

            
            if(o.primitive == "circle") {
                var fnc = o.drawPrimitive[o.primitive]
                if(typeof(fnc)=="function")
                    fnc(ctx)
            }
            ctx.fillStyle = o.color || "blue";
            ctx.lineWidth = 1;
            ctx.fill()
            ctx.stroke();
        }
    }

    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 self = this,
        holder = {
            position:{},
            scale: {
                
            },
            rotation: {},
            origin:{}
        },
        position = {},
        scale = {},
        rotation = {},
        collision="mesh",
        shape = null,
        origin = {},
        color,
        radius=0,
        name,
        primitive,
        
        eventNames = "update",
        events = {},
        drawPrimitive = {
            circle(ctx) {
                ctx.beginPath();
                ctx.arc(
                    self.position.x,
                    self.position.y,
                    radius, 
                    0,
                    360 * Math.PI / 180
                );
                ctx.closePath();
            }
        },
        width = 1,
        height = 1;

    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(this, {

        name: {
            get: ()=>name,
            set(v) {
                name = v
            }
        },
        primitive: {
            get: ()=>primitive,
            set(v) {
                primitive = v;
                
            }
        },
        
        drawPrimitive: {
            get: ()=>drawPrimitive
        },
        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;
            }
        },
        
        radius: {
            get: () => radius||(width*scale.x)/2,
            set: v => {
                radius = v;
                this.width = radius/2
                this.height = radius/2
            }
        },
        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;
            }
        },
        color: {
            get: () => color,
            set: v => {
                color = v;
            }
        },
        update: {
            get: () => () => {
                if(events["update"]) {
                    events.update.forEach(x => {
                        
                        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]
    }


}
</script>

                if(col.type=="circle"){
                    let vecBetweenX = next.x - ob.position.x;
                    let vecBetweenY = next.y - ob.position.y;

                    let heading = Math.atan2(vecBetweenY, vecBetweenX);

                    var dst = Math.sqrt(
                        vecBetweenX*vecBetweenX
                        +
                        vecBetweenY*vecBetweenY
                    )
                    var dirX=vecBetweenX/dist
                    var dirY=vecBetweenY/dist

                    var rt= heading
                    
                    var ris = ob.radius + me.radius

                    var ax = Math.cos(rt)*ris
                    var ay = Math.sin(rt)*ris

                    /*
                    var nx=ob.position.x+ax
                    var ny=ob.position.y+ay
                    */
                    
                    var nx=ob.position.x+dirX*ris
                    var ny=ob.position.y+dirY+ris
                    
                    me.position.x = nx
                    me.position.y = ny
                    return


                }
            }

[Edit] tried using method suggested in answer, but player is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges

Notice added Draw attention by B''H Bi'ezras -- Boruch Hashem
Bounty Started worth 50 reputation by B''H Bi'ezras -- Boruch Hashem
deleted 10574 characters in body
Source Link
Loading
Source Link
Loading