Я думаю, що найкращою відповіддю тут буде суттєвий перегляд 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
подія не виявляється, якщо миша не знаходиться в полотні, коли вона відбувається, але це справедливо для будь-якого mouseup
виявлення Matter.js, тому я не намагався це виправити.
Якщо швидкість буде достатньо великою, 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)
через, setForce
коли наближається до конфлікту: об'єкт все ще може пройти через, потрібно було б реалізувати, Resolver
щоб реально працювати.
- Змінення
mouse.element
на інше полотно через setElement()
або mouse.element
безпосередньо за допомогою мутації : об’єкт видаляється негайно
- Повернення об'єкта до останнього "дійсного" положення: все ще дозволяє пройти через,
- Змініть поведінку через
collisionStart
: непослідовне виявлення зіткнення все ще дозволяє пройти через цей метод