Я думаю, что лучшим ответом здесь будет значительный пересмотр 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.mousemove
mouse.element
mousemove()
Matter.Mouse._getRelativeMousePosition()
mouseup
mousemove
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
: противоречивое обнаружение столкновений по-прежнему разрешает проход с помощью этого метода