Физика в canvas. Программа плохо понимает столкновения объектов

109
11 марта 2021, 08:40

Согласно рекомендациям @Stranger in the Q, я сделал проверку на столкновения отдельным таймером, но так еще хуже. Может есть еще способы получше проверять столкновения?

Проверка во время отрисовки:

function dotInObj(dotX, dotY, objX, objY, objWidth, objHeight) { 
  if ((dotX >= objX && dotY >= objY) && (dotX <= objX + objWidth && dotY <= objY + objHeight)) return true; 
  else return false; 
} //функция проверки "точка в объекте" 
 
function MacroCollision(obj1, obj2) { 
  var XColl = false; 
  var YColl = false; 
 
  if ((obj1.X + obj1.width >= obj2.X) && (obj1.X <= obj2.X + obj2.width)) XColl = true; 
  if ((obj1.Y + obj1.height >= obj2.Y) && (obj1.Y <= obj2.Y + obj2.height)) YColl = true; 
 
  if (XColl & YColl) { 
    return true; 
  } 
  return false; 
} 
 
var cvs = document.getElementById("canvas"); 
var ctx = cvs.getContext("2d"); 
var touch = 0; 
 
var objects = []; 
 
var wall = { 
  X: 0, 
  Y: 0, 
  mass: Infinity, 
  width: 5, 
  height: cvs.height, 
  velocity: 0 
} 
 
function createObj(mass, velocity, position) { //функция создания объекта, position[0-1] 
  objects.push({ 
    X: cvs.width * position - 30, 
    Y: cvs.height - 30, 
    mass: mass, 
    width: 30, 
    height: 30, 
    velocity: velocity 
  }); 
} 
 
createObj(1000, -100, 1); 
createObj(1, 0, 0.5); //созаю объекты 
 
ctx.fillStyle = "#000"; 
let prevTime = 0; 
 
function draw(t) { //функция отрисовки 
  let dt = (t - prevTime) / 1000; 
  ctx.clearRect(0, 0, cvs.width, cvs.height); //очистка после предыдущего кадра 
  ctx.fillRect(wall.X, wall.Y, wall.width, wall.height); //стена 
  for (let i = 0; i < objects.length; i++) { //отрисовка объектов 
    ctx.fillRect(objects[i].X, objects[i].Y, objects[i].width, objects[i].height); //заполнение объекта(ов) 
    objects[i].X += objects[i].velocity * dt; //передвижение объекта(ов) согласно его(их) скорости 
  } 
  let j = 1; 
  for (let i = 0; i < objects.length; i++) { 
    if (MacroCollision(objects[i], wall)) { //проверка на стык со стеной 
      objects[i].velocity = Math.abs(objects[i].velocity) * Math.sign(-objects[i].velocity); 
      touch++; 
    } 
    if (MacroCollision(objects[i], objects[j])) { //проверка столкновения 2 объектов 
 
      if (j == i) continue; //от бага 
      let x1 = objects[j].X + objects[j].width; 
      let x2 = objects[i].X; 
      let dx = x1 - x2; 
      if (dx > 0) 
        dx *= -1; 
      objects[j].X += dx; 
      let tx1 = objects[i].velocity; 
      let tx2 = objects[j].velocity; 
      let m2 = objects[i].mass + objects[j].mass; 
      objects[i].velocity = ((objects[i].mass - objects[j].mass) * tx1 + 2.0 * 
        objects[j].mass * tx2) / m2; 
      objects[j].velocity = (2.0 * objects[i].mass * tx1 + 
        (objects[j].mass - objects[i].mass) * tx2) / m2; 
      touch++; 
    } 
  } 
  ctx.font = "16px Arial"; 
  ctx.fillText(touch, 10, 20); 
  requestAnimationFrame(draw); 
  prevTime = t; 
} 
 
/* var timer = setInterval(function() { 
   
}, 1); 
 */ 
requestAnimationFrame(draw);
canvas { 
  border: 1px solid #999; 
}
<div class="canvasBG"></div> 
<canvas id="canvas" width="500" height="250"></canvas> 
<input type="text" id="console">

Проверка отдельным таймером:

function dotInObj(dotX, dotY, objX, objY, objWidth, objHeight) { 
  if ((dotX >= objX && dotY >= objY) && (dotX <= objX + objWidth && dotY <= objY + objHeight)) return true; 
  else return false; 
} //функция проверки "точка в объекте" 
 
function MacroCollision(obj1, obj2) { 
  var XColl = false; 
  var YColl = false; 
 
  if ((obj1.X + obj1.width >= obj2.X) && (obj1.X <= obj2.X + obj2.width)) XColl = true; 
  if ((obj1.Y + obj1.height >= obj2.Y) && (obj1.Y <= obj2.Y + obj2.height)) YColl = true; 
 
  if (XColl & YColl) { 
    return true; 
  } 
  return false; 
} 
 
var cvs = document.getElementById("canvas"); 
var ctx = cvs.getContext("2d"); 
var touch = 0; 
 
var objects = []; 
 
var wall = { 
  X: 0, 
  Y: 0, 
  mass: Infinity, 
  width: 5, 
  height: cvs.height, 
  velocity: 0 
} 
 
function createObj(mass, velocity, position) { //функция создания объекта, position[0-1] 
  objects.push({ 
    X: cvs.width * position - 30, 
    Y: cvs.height - 30, 
    mass: mass, 
    width: 30, 
    height: 30, 
    velocity: velocity 
  }); 
} 
 
createObj(1000, -100, 1); 
createObj(1, 0, 0.5); //созаю объекты 
 
ctx.fillStyle = "#000"; 
let prevTime = 0; 
 
function draw(t) { //функция отрисовки 
  let dt = (t - prevTime) / 1000; 
  ctx.clearRect(0, 0, cvs.width, cvs.height); //очистка после предыдущего кадра 
  ctx.fillRect(wall.X, wall.Y, wall.width, wall.height); //стена 
  for (let i = 0; i < objects.length; i++) { //отрисовка объектов 
    ctx.fillRect(objects[i].X, objects[i].Y, objects[i].width, objects[i].height); //заполнение объекта(ов) 
    objects[i].X += objects[i].velocity * dt; //передвижение объекта(ов) согласно его(их) скорости 
  } 
  ctx.font = "16px Arial"; 
  ctx.fillText(touch, 10, 20); 
  requestAnimationFrame(draw); 
  prevTime = t; 
} 
 
var timer = setInterval(function() { 
  let j = 1; 
  for (let i = 0; i < objects.length; i++) { 
    if (MacroCollision(objects[i], wall)) { //проверка на стык со стеной 
      objects[i].velocity = Math.abs(objects[i].velocity) * Math.sign(-objects[i].velocity); 
      touch++; 
    } 
    if (MacroCollision(objects[i], objects[j])) { //проверка столкновения 2 объектов 
 
      if (j == i) continue; //от бага 
      let x1 = objects[j].X + objects[j].width; 
      let x2 = objects[i].X; 
      let dx = x1 - x2; 
      if (dx > 0) 
        dx *= -1; 
      objects[j].X += dx; 
      let tx1 = objects[i].velocity; 
      let tx2 = objects[j].velocity; 
      let m2 = objects[i].mass + objects[j].mass; 
      objects[i].velocity = ((objects[i].mass - objects[j].mass) * tx1 + 2.0 * 
        objects[j].mass * tx2) / m2; 
      objects[j].velocity = (2.0 * objects[i].mass * tx1 + 
        (objects[j].mass - objects[i].mass) * tx2) / m2; 
      touch++; 
    } 
  } 
}, 1); 
 
requestAnimationFrame(draw);
canvas { 
  border: 1px solid #999; 
}
<div class="canvasBG"></div> 
<canvas id="canvas" width="500" height="250"></canvas> 
<input type="text" id="console">

Проверка через сетинтервал:

function dotInObj(dotX, dotY, objX, objY, objWidth, objHeight) { 
  if ((dotX >= objX && dotY >= objY) && (dotX <= objX + objWidth && dotY <= objY + objHeight)) return true; 
  else return false; 
} //функция проверки "точка в объекте" 
 
function MacroCollision(obj1, obj2) { 
  var XColl = false; 
  var YColl = false; 
 
  if ((obj1.X + obj1.width >= obj2.X) && (obj1.X <= obj2.X + obj2.width)) XColl = true; 
  if ((obj1.Y + obj1.height >= obj2.Y) && (obj1.Y <= obj2.Y + obj2.height)) YColl = true; 
 
  if (XColl & YColl) { 
    return true; 
  } 
  return false; 
} 
 
var cvs = document.getElementById("canvas"); 
var ctx = cvs.getContext("2d"); 
var touch = 0; 
 
var objects = []; 
 
var wall = { 
  X: 0, 
  Y: 0, 
  mass: Infinity, 
  width: 5, 
  height: cvs.height, 
  velocity: 0 
} 
 
function createObj(mass, velocity, position) { //функция создания объекта, position[0-1] 
  objects.push({ 
    X: cvs.width * position - 30, 
    Y: cvs.height - 30, 
    mass: mass, 
    width: 30, 
    height: 30, 
    velocity: velocity 
  }); 
} 
 
createObj(1000, -100, 1); 
createObj(1, 0, 0.5); //созаю объекты 
 
ctx.fillStyle = "#000"; 
let prevTime = 0; 
 
function draw(t) { //функция отрисовки 
  let dt = (t - prevTime) / 1000; 
  ctx.clearRect(0, 0, cvs.width, cvs.height); //очистка после предыдущего кадра 
  ctx.fillRect(wall.X, wall.Y, wall.width, wall.height); //стена 
  for (let i = 0; i < objects.length; i++) { //отрисовка объектов 
    ctx.fillRect(objects[i].X, objects[i].Y, objects[i].width, objects[i].height); //заполнение объекта(ов) 
    objects[i].X += objects[i].velocity * dt; //передвижение объекта(ов) согласно его(их) скорости 
  } 
  ctx.font = "16px Arial"; 
  ctx.fillText(touch, 10, 20); 
  requestAnimationFrame(draw); 
  prevTime = t; 
} 
 
var timer = setInterval(function() { 
  let j = 1; 
  for (let i = 0; i < objects.length; i++) { 
    if (MacroCollision(objects[i], wall)) { //проверка на стык со стеной 
      objects[i].velocity = Math.abs(objects[i].velocity) * Math.sign(-objects[i].velocity); 
      touch++; 
    } 
    if (MacroCollision(objects[i], objects[j])) { //проверка столкновения 2 объектов 
 
      if (j == i) continue; //от бага 
      let x1 = objects[j].X + objects[j].width; 
      let x2 = objects[i].X; 
      let dx = x1 - x2; 
      if (dx > 0) 
        dx *= -1; 
      objects[j].X += dx; 
      let tx1 = objects[i].velocity; 
      let tx2 = objects[j].velocity; 
      let m2 = objects[i].mass + objects[j].mass; 
      objects[i].velocity = ((objects[i].mass - objects[j].mass) * tx1 + 2.0 * 
        objects[j].mass * tx2) / m2; 
      objects[j].velocity = (2.0 * objects[i].mass * tx1 + 
        (objects[j].mass - objects[i].mass) * tx2) / m2; 
      touch++; 
    } 
  } 
}, 1); 
 
requestAnimationFrame(draw);
canvas { 
  border: 1px solid #999; 
}
<div class="canvasBG"></div> 
<canvas id="canvas" width="500" height="250"></canvas> 
<input type="text" id="console">

Answer 1

Видео про это задачу

Вот реализация которая считает правильно в плоть до пары 1e1 1e15.

Тут фокус в том, чтобы сильно уменьшить шаг между проверками коллизий, когда тяжелый блок прижал легкий к стене.

console.log(Math.PI*1e7); 
setTimeout(e => test(1), 100); 
 
function test(mass) { 
  var cvs = document.createElement('canvas'); 
  cvs.width = 300; 
  cvs.height = 35; 
  document.body.append(cvs); 
 
  var ctx = cvs.getContext("2d"); 
  var touch = 0; 
 
  var wall, o1, o2, objects = [ 
      wall = {X: 0, Y: 0, width: 5, height: cvs.height, mass: Infinity, velocity: 0}, 
      o1 = {X: 100, Y: cvs.height - 20, width: 20, height: 20, mass: 1, velocity: 0}, 
      o2 = {X: 200, Y: cvs.height - 30, width: 30, height: 30, mass: mass, velocity: -250} 
  ]; 
 
  let i, prevTime = Date.now(), goal = Math.floor(Math.PI*Math.sqrt(o2.mass)); 
 
  function draw() {  
    ctx.clearRect(0, 0, cvs.width, cvs.height); 
    ctx.fillStyle = 'black' 
    objects.forEach(o => ctx.fillRect(o.X, o.Y, o.width, o.height));  
    ctx.fillStyle = touch === goal ? 'green' : 'red' 
    ctx.fillText(touch, 10, 10); 
    if (o2.X > cvs.width && o1.velocity >= 0 && o1.X > 20) { 
      clearInterval(i); 
    if (mass<1e14) 
      test(mass*100) 
    } else { 
      requestAnimationFrame(draw); 
    } 
  } 
 
  i = setInterval(e => { 
    tick((Date.now() - prevTime) / 1000); 
    while (o2.X < wall.width + o1.width) tick(o2.mass>1e5 ? 0.00001 : 0.001) 
    prevTime = Date.now(); 
  }); 
 
  function tick(dt) { 
 
      objects.forEach(o => o.X += o.velocity * dt); 
 
      if (wall.X + wall.width > o1.X) {  
          o1.velocity = Math.abs(o1.velocity); 
          touch++; 
      }   
 
      if (o1.X + o1.width > o2.X) {  
          let dx = o1.X + o1.width - o2.X; 
          if (dx > 0) 
              dx *= -1; 
          o1.X += dx; 
          let tx1 = o2.velocity; 
          let tx2 = o1.velocity; 
          let m1 = o2.mass - o1.mass; 
          let m2 = o2.mass + o1.mass; 
          o2.velocity = (m1 * tx1 + 2 * o1.mass * tx2) / m2; 
          o1.velocity = (2 * o2.mass * tx1 - m1 * tx2) / m2; 
          touch++; 
      } 
 
      o1.X = Math.max(5, o1.X);   
  } 
 
  requestAnimationFrame(draw); 
}
body{margin:0}canvas{background-color:#eee;}

Answer 2

Вот версия, которая работает более менее стабильно, однако ответ не верный...

Так же я изменил секцию где определяется направление движения после столкновения со стеной, она не может быть влево, по этому я убрал второй множитель

function MacroCollision(obj1, obj2) { 
  return obj1.X + obj1.width >= obj2.X && obj1.X <= obj2.X + obj2.width; 
} 
 
var cvs = document.getElementById("canvas"); 
var ctx = cvs.getContext("2d"); 
var touch = 0; 
var objects = []; 
 
var wall = { 
  X: -1000, 
  Y: 0, 
  mass: Infinity, 
  width: 1005, 
  height: cvs.height, 
  velocity: 0 
} 
 
function createObj(mass, velocity, position) { //функция создания объекта, position[0-1] 
  objects.push({ 
    X: cvs.width * position - 30, 
    Y: cvs.height - 30, 
    mass: mass, 
    width: 30, 
    height: 30, 
    velocity: velocity 
  }); 
} 
 
createObj(10000, -50, 1); 
createObj(1, 0, 0.5); //созаю объекты 
 
ctx.fillStyle = "#000"; 
let prevTime = Date.now(); 
 
function draw(t) { //функция отрисовки 
  ctx.clearRect(0, 0, cvs.width, cvs.height); //очистка после предыдущего кадра 
  ctx.fillRect(wall.X, wall.Y, wall.width, wall.height); //стена 
  for (let i = 0; i < objects.length; i++) { //отрисовка объектов 
    ctx.fillRect(objects[i].X, objects[i].Y, objects[i].width, objects[i].height); //заполнение  
  } 
  ctx.font = "16px Arial"; 
  ctx.fillText(touch, 10, 20); 
  requestAnimationFrame(draw); 
} 
 
var timer = setInterval(function() { 
  let dt = (Date.now() - prevTime) / 1000; 
  prevTime = Date.now(); 
  let j = 1; 
  for (let i = 0; i < objects.length; i++) { 
 
    objects[i].X += objects[i].velocity * dt; //передвижение объекта(ов) согласно его(их) скорости 
     
    objects[i].X = Math.max(5, objects[i].X) 
   
    if (MacroCollision(objects[i], wall)) { //проверка на стык со стеной 
      objects[i].velocity = Math.abs(objects[i].velocity); 
      touch++; 
    } 
     
    if (MacroCollision(objects[i], objects[j])) { //проверка столкновения 2 объектов 
      if (j == i) continue; //от бага 
      let x1 = objects[j].X + objects[j].width; 
      let x2 = objects[i].X; 
      let dx = x1 - x2; 
      if (dx > 0) 
        dx *= -1; 
      objects[j].X += dx; 
      let tx1 = objects[i].velocity; 
      let tx2 = objects[j].velocity; 
      let m2 = objects[i].mass + objects[j].mass; 
      objects[i].velocity = ((objects[i].mass - objects[j].mass) * tx1 + 2.0 * 
        objects[j].mass * tx2) / m2; 
      objects[j].velocity = (2.0 * objects[i].mass * tx1 + 
        (objects[j].mass - objects[i].mass) * tx2) / m2; 
      touch++; 
    } 
  } 
}); 
 
requestAnimationFrame(draw);
<canvas id="canvas" width="500" height="150" style="border: 1px solid #999"></canvas>

READ ALSO
Получить width экрана css

Получить width экрана css

Мне необходимо настроить @viewport в CSS вот таким образом:

117
Реализация фигуры на Css

Реализация фигуры на Css

Каким образом на Css можно реализовать фигуру, которая изображена снизу на картинке

150
Стоит ли создавать header с include-ами?

Стоит ли создавать header с include-ами?

Если проект разбит на множество файлов, описывающих разных наследников некоторого абстрактного класса, то в файле, в котором нужно использовать...

135