Я думаю, что лучшим ответом здесь будет значительный пересмотр Matter.Resolverмодуля для реализации прогностического предотвращения физических конфликтов между любыми телами. Все, кроме этого, гарантированно потерпит неудачу при определенных обстоятельствах. При этом здесь говорится о двух «решениях», которые на самом деле являются лишь частичными решениями. Они изложены ниже.
Решение 1 (обновление)
Это решение имеет несколько преимуществ:
- Это более сжато, чем Решение 2
- Это создает меньшую вычислительную площадь, чем Решение 2
- Режим перетаскивания не прерывается так, как в решении 2
- Это может быть неразрушающим образом сочетается с решением 2
Идея этого подхода состоит в том, чтобы разрешить парадокс того, что происходит, « когда непреодолимая сила встречает неподвижный объект », делая силу остановить. Это обеспечивается функцией Matter.Event beforeUpdate, которая позволяет positionImpulseограничить абсолютную скорость и импульс (или, точнее , не являющийся физическим импульсом) в каждом направлении в пределах определенных пользователем границ.
window.addEventListener('load', function() {
var canvas = document.getElementById('world')
var mouseNull = document.getElementById('mouseNull')
var engine = Matter.Engine.create();
var world = engine.world;
var render = Matter.Render.create({ element: document.body, canvas: canvas,
engine: engine, options: { width: 800, height: 800,
background: 'transparent',showVelocity: true }});
var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}),
size = 50, counter = -1;
var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6,
0, 0, function(x, y) {
return Matter.Bodies.rectangle(x, y, size * 2, size, {
slop: 0, friction: 1, frictionStatic: Infinity });
});
Matter.World.add(world, [ body, stack,
Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
]);
Matter.Events.on(engine, 'beforeUpdate', function(event) {
counter += 0.014;
if (counter < 0) { return; }
var px = 400 + 100 * Math.sin(counter);
Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
Matter.Body.setPosition(body, { x: px, y: body.position.y });
if (dragBody != null) {
if (dragBody.velocity.x > 25.0) {
Matter.Body.setVelocity(dragBody, {x: 25, y: dragBody.velocity.y });
}
if (dragBody.velocity.y > 25.0) {
Matter.Body.setVelocity(dragBody, {x: dragBody.velocity.x, y: 25 });
}
if (dragBody.positionImpulse.x > 25.0) {
dragBody.positionImpulse.x = 25.0;
}
if (dragBody.positionImpulse.y > 25.0) {
dragBody.positionImpulse.y = 25.0;
}
}
});
var mouse = Matter.Mouse.create(render.canvas),
mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
constraint: { stiffness: 0.1, render: { visible: false }}});
var dragBody = null
Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
dragBody = event.body;
});
Matter.World.add(world, mouseConstraint);
render.mouse = mouse;
Matter.Engine.run(engine);
Matter.Render.run(render);
});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>
В примере я ограничивая velocityи positionImpulseв xи yдо максимальной величины 25.0. Результат показан ниже

Как вы можете видеть, можно перетаскивать тела, и они не будут проходить друг через друга. Это то, что отличает этот подход от других: большинство других потенциальных решений терпят неудачу, когда пользователь достаточно жесток с их перетаскиванием.
Единственный недостаток, с которым я столкнулся при использовании этого метода, заключается в том, что можно использовать нестатическое тело, чтобы ударить другое нестатическое тело достаточно сильно, чтобы дать ему достаточную скорость до точки, где Resolverмодуль не сможет обнаружить столкновение и позволить второе тело, чтобы пройти через другие тела. (В примере со статическим трением требуемая скорость равна примерно 50.0, мне удалось сделать это успешно только один раз, и, следовательно, у меня нет анимации, изображающей это).
Решение 2
Это дополнительное решение, хотя справедливое предупреждение: оно не простое.
В общих чертах, способ, которым это работает, состоит в том, чтобы проверить dragBody, столкнулось ли тело с статическим телом, и с тех пор ли мышь зашла слишком далеко без dragBodyследования. Если он обнаруживает , что разделение между мышью и dragBodyстановится слишком большим , что снимает слушатель событий от и заменяет его на другую функцию MouseMove, . Эта функция проверяет, вернулась ли мышь в заданную близость к центру тела. К сожалению, я не мог заставить встроенный метод работать должным образом, поэтому мне пришлось включить его напрямую (кто-то, более опытный, чем я, в Javascript должен будет это выяснить). Наконец, если событие обнаружено, оно переключается обратно к обычному слушателю.Matter.js mouse.mousemovemouse.elementmousemove()Matter.Mouse._getRelativeMousePosition()mouseupmousemove
window.addEventListener('load', function() {
var canvas = document.getElementById('world')
var mouseNull = document.getElementById('mouseNull')
var engine = Matter.Engine.create();
var world = engine.world;
var render = Matter.Render.create({ element: document.body, canvas: canvas,
engine: engine, options: { width: 800, height: 800,
background: 'transparent',showVelocity: true }});
var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}),
size = 50, counter = -1;
var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6,
0, 0, function(x, y) {
return Matter.Bodies.rectangle(x, y, size * 2, size, {
slop: 0.5, friction: 1, frictionStatic: Infinity });
});
Matter.World.add(world, [ body, stack,
Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
]);
Matter.Events.on(engine, 'beforeUpdate', function(event) {
counter += 0.014;
if (counter < 0) { return; }
var px = 400 + 100 * Math.sin(counter);
Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
Matter.Body.setPosition(body, { x: px, y: body.position.y });
});
var mouse = Matter.Mouse.create(render.canvas),
mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
constraint: { stiffness: 0.2, render: { visible: false }}});
var dragBody, overshoot = 0.0, threshold = 50.0, loc, dloc, offset,
bodies = Matter.Composite.allBodies(world), moveOn = true;
getMousePosition = function(event) {
var element = mouse.element, pixelRatio = mouse.pixelRatio,
elementBounds = element.getBoundingClientRect(),
rootNode = (document.documentElement || document.body.parentNode ||
document.body),
scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset :
rootNode.scrollLeft,
scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset :
rootNode.scrollTop,
touches = event.changedTouches, x, y;
if (touches) {
x = touches[0].pageX - elementBounds.left - scrollX;
y = touches[0].pageY - elementBounds.top - scrollY;
} else {
x = event.pageX - elementBounds.left - scrollX;
y = event.pageY - elementBounds.top - scrollY;
}
return {
x: x / (element.clientWidth / (element.width || element.clientWidth) *
pixelRatio) * mouse.scale.x + mouse.offset.x,
y: y / (element.clientHeight / (element.height || element.clientHeight) *
pixelRatio) * mouse.scale.y + mouse.offset.y
};
};
mousemove = function() {
loc = getMousePosition(event);
dloc = dragBody.position;
overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
if (overshoot < threshold) {
mouse.element.removeEventListener("mousemove", mousemove);
mouse.element.addEventListener("mousemove", mouse.mousemove);
moveOn = true;
}
}
Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
dragBody = event.body;
loc = mouse.position;
dloc = dragBody.position;
offset = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5;
Matter.Events.on(mouseConstraint, 'mousemove', function(event) {
loc = mouse.position;
dloc = dragBody.position;
for (var i = 0; i < bodies.length; i++) {
overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
if (bodies[i] != dragBody &&
Matter.SAT.collides(bodies[i], dragBody).collided == true) {
if (overshoot > threshold) {
if (moveOn == true) {
mouse.element.removeEventListener("mousemove", mouse.mousemove);
mouse.element.addEventListener("mousemove", mousemove);
moveOn = false;
}
}
}
}
});
});
Matter.Events.on(mouseConstraint, 'mouseup', function(event) {
if (moveOn == false){
mouse.element.removeEventListener("mousemove", mousemove);
mouse.element.addEventListener("mousemove", mouse.mousemove);
moveOn = true;
}
});
Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
overshoot = 0.0;
Matter.Events.off(mouseConstraint, 'mousemove');
});
Matter.World.add(world, mouseConstraint);
render.mouse = mouse;
Matter.Engine.run(engine);
Matter.Render.run(render);
});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>
После применения схемы переключения слушателя событий тела теперь ведут себя примерно так

Я проверил это довольно тщательно, но я не могу гарантировать, что это будет работать в каждом случае. Следует также отметить, что mouseupсобытие не обнаружено, если только мышь не находится внутри холста, когда это происходит, но это верно для любого Matter.jsmouseup обнаружения поэтому я не пытался это исправить.
Если скорость достаточно велика, Resolverне удастся обнаружить какое-либо столкновение, и поскольку ей не хватает прогностического предотвращения этой разновидности физического конфликта, это позволит телу пройти, как показано здесь.

Это может быть решено путем объединения с решением 1 .
Последнее замечание: возможно применить это только к определенным взаимодействиям (например, между статическим и нестатическим телом). Это достигается путем изменения
if (bodies[i] != dragBody && Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
(например, для статических тел)
if (bodies[i].isStatic == true && bodies[i] != dragBody &&
Matter.SAT.collides(bodies[i], dragBody).collided == true) {
//...
}
Неудачные решения
В случае, если какие-либо будущие пользователи столкнутся с этим вопросом и сочтут оба решения недостаточными для своего варианта использования, вот некоторые из решений, которые я пробовал, которые не работали. Путеводитель по тому, что не надо делать.
- Вызов
mouse.mouseupнапрямую: объект удален немедленно.
- Вызов
mouse.mouseupчерез Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse}): переопределено Engine.update, поведение без изменений.
- Делаем перетаскиваемый объект временно статичным: объект возвращается при возврате в нестатический (через
Matter.Body.setStatic(body, false)или body.isStatic = false).
- Установка силы на
(0,0)via setForceпри приближении к конфликту: объект по-прежнему может проходить, необходимо реализовать вResolver для фактической работы.
- Переключение
mouse.elementна другой холст с помощью setElement()или путем mouse.elementпрямого изменения: объект немедленно удаляется.
- Возврат объекта в последнюю «действительную» позицию: все еще позволяет пройти,
- Изменить поведение с помощью
collisionStart: противоречивое обнаружение столкновений по-прежнему разрешает проход с помощью этого метода