Verhindern Sie mit MatterJS das Ziehen von Körpern durch andere Körper

14

Ich verwende MatterJs für ein physikbasiertes Spiel und habe keine Lösung für das Problem gefunden, zu verhindern, dass Körper von der Maus gewaltsam durch andere Körper gezogen werden. Wenn Sie einen Körper in einen anderen Körper ziehen, kann sich der gezogene Körper in und durch den anderen Körper zwingen. Ich suche nach einem zuverlässigen Weg, um zu verhindern, dass sie sich überschneiden. Sie können diesen Effekt in jeder MatterJS-Demo beobachten, indem Sie einen Körper mit der Maus auswählen und versuchen, ihn durch einen anderen Körper zu zwingen. Hier ist ein typisches Beispiel:

Geben Sie hier die Bildbeschreibung ein

https://brm.io/matter-js/demo/#staticFriction

Leider werden dadurch Spiele oder Simulationen je nach Drag & Drop unterbrochen. Ich habe zahlreiche Lösungen versucht, z. B. das Aufheben der Mausbeschränkung bei einer Kollision oder das Verringern der Beschränkungssteifigkeit, aber nichts, was zuverlässig funktioniert.

Anregungen willkommen!

d13
quelle
Ich verstehe die zwangsweise Formulierung nicht. Meinst du, dein gezogener Körper sollte durch andere Körper gehen?
Grodzi
Nein, es bedeutet, dass verhindert werden sollte, dass der gezogene Körper durch andere Körper geht.
13.
1
@ d13 Könnten Sie eine Animation hinzufügen, die das Problem zeigt? Da scheint es einige Verwirrung aufgrund des Wortlauts zu geben ...
Ghost
2
@ Ghost hinzugefügt ...
d13
@ d13 Das macht die Dinge klarer ..... das ist eine schwierige Frage
Ghost

Antworten:

6

Ich denke, dass die beste Antwort hier eine umfassende Überarbeitung des Matter.ResolverModuls wäre, um eine vorausschauende Vermeidung physischer Konflikte zwischen Körpern zu implementieren. Alles andere scheitert unter bestimmten Umständen garantiert . Davon abgesehen gibt es zwei "Lösungen", die in Wirklichkeit nur Teillösungen sind. Sie sind unten aufgeführt.


Lösung 1 (Update)

Diese Lösung hat mehrere Vorteile:

  • Es ist prägnanter als Lösung 2
  • Es erzeugt einen geringeren Rechenaufwand als Lösung 2
  • Das Ziehverhalten wird nicht wie in Lösung 2 unterbrochen
  • Es kann zerstörungsfrei mit Lösung 2 kombiniert werden

Die Idee hinter diesem Ansatz ist es, das Paradoxon dessen zu lösen, was passiert, " wenn eine unaufhaltsame Kraft auf ein unbewegliches Objekt trifft ", indem die Kraft stoppbar gemacht wird. Dies wird durch das ermöglicht Matter.Event beforeUpdate, wodurch die absolute Geschwindigkeit und der absolute Impuls (oder besser gesagt positionImpulse, der nicht wirklich physikalische Impuls) in jeder Richtung auf benutzerdefinierte Grenzen beschränkt werden können.

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>

Im Beispiel beschränke ich das velocityund positionImpulsein xund yauf eine maximale Größe von 25.0. Das Ergebnis ist unten dargestellt

Geben Sie hier die Bildbeschreibung ein

Wie Sie sehen können, ist es möglich, die Körper ziemlich heftig zu ziehen, und sie werden sich nicht gegenseitig durchdringen. Dies unterscheidet diesen Ansatz von anderen: Die meisten anderen potenziellen Lösungen schlagen fehl, wenn der Benutzer beim Ziehen ausreichend gewalttätig ist.

Das einzige Manko, auf das ich bei dieser Methode gestoßen bin, ist, dass es möglich ist, einen nicht statischen Körper zu verwenden, um einen anderen nicht statischen Körper so hart zu treffen, dass er eine ausreichende Geschwindigkeit erreicht, bis das ResolverModul die Kollision nicht erkennt und die Kollision zulässt zweiter Körper, der durch andere Körper geht. (Im Beispiel für statische Reibung ist die erforderliche Geschwindigkeit ungefähr vorhanden 50.0. Ich habe dies nur einmal erfolgreich durchgeführt, und folglich habe ich keine Animation, die dies darstellt.)


Lösung 2

Dies ist eine zusätzliche Lösung, eine faire Warnung: Es ist nicht einfach.

Im Großen und Ganzen funktioniert dies, um zu überprüfen, ob der gezogene Körper dragBodymit einem statischen Körper kollidiert ist und ob sich die Maus seitdem zu weit bewegt hat, ohne zu dragBodyfolgen. Wenn festgestellt wird, dass der Abstand zwischen der Maus und der Maus dragBodyzu groß geworden ist, wird der Ereignis-Listener entfernt und durch eine andere Mausbewegungsfunktion ersetzt . Diese Funktion prüft, ob die Maus in eine bestimmte Nähe des Körperzentrums zurückgekehrt ist. Leider konnte ich die eingebaute Methode nicht richtig zum Laufen bringen, so dass ich sie direkt einbinden musste (jemand, der mehr über Kenntnisse in Javascript verfügt als ich, muss dies herausfinden). Wenn ein Ereignis erkannt wird, wechselt es zurück zum normalen Listener.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>

Nach dem Anwenden des Ereignis-Listener-Schaltschemas verhalten sich die Körper nun ähnlicher

Geben Sie hier die Bildbeschreibung ein

Ich habe dies ziemlich gründlich getestet , kann aber nicht garantieren, dass es in jedem Fall funktioniert. Es ist auch zu beachten, dass das mouseupEreignis erst erkannt wird, wenn sich die Maus zum Zeitpunkt des Auftretens innerhalb der Zeichenfläche befindet. Dies gilt jedoch für jede Matter.js- mouseupErkennung, sodass ich nicht versucht habe, dies zu beheben.

Wenn die Geschwindigkeit ausreichend groß ist, Resolverkeine Kollision erkannt werden kann und der Körper keine vorbeugende Verhinderung dieses Geschmacks von physischen Konflikten aufweist, kann er wie hier gezeigt passieren.

Geben Sie hier die Bildbeschreibung ein

Dies kann durch Kombination mit Lösung 1 behoben werden .

Ein letzter Hinweis hier: Es ist möglich, dies nur auf bestimmte Wechselwirkungen anzuwenden (z. B. zwischen einem statischen und einem nicht statischen Körper). Dies wird durch Ändern erreicht

if (bodies[i] != dragBody && Matter.SAT.collides(bodies[i], dragBody).collided == true) {
    //...
}

zu (für zB statische Körper)

if (bodies[i].isStatic == true && bodies[i] != dragBody && 
    Matter.SAT.collides(bodies[i], dragBody).collided == true) {
    //...
}

Fehlgeschlagene Lösungen

Falls zukünftige Benutzer auf diese Frage stoßen und beide Lösungen für ihren Anwendungsfall als unzureichend empfinden, sind hier einige der Lösungen aufgeführt, die ich versucht habe und die dies nicht getan haben funktioniert haben. Eine Art Leitfaden für das, was man nicht tun sollte.

  • Berufung mouse.mouseupDirekt : Objekt sofort gelöscht.
  • Anrufen mouse.mouseupüberEvent.trigger(mouseConstraint, 'mouseup', {mouse: mouse}) : überschrieben von Engine.update, Verhalten unverändert.
  • Das gezogene Objekt vorübergehend statisch machen: Objekt wird gelöscht, wenn zu nicht statisch zurückgekehrt wird (ob über Matter.Body.setStatic(body, false) oder body.isStatic = false).
  • Einstellen der Kraft auf (0,0)viasetForce bei Annäherung an einen Konflikt: Objekt kann immer noch passieren, müsste implementiert werden, Resolverum tatsächlich zu funktionieren.
  • Wechseln mouse.elementzu einer anderen Zeichenfläche über setElement()oder durch mouse.elementdirektes Mutieren : Objekt sofort gelöscht.
  • Zurücksetzen des Objekts auf die letzte 'gültige' Position: Ermöglicht weiterhin den Durchgang,
  • Verhaltensänderung über collisionStart: Inkonsistente Kollisionserkennung ermöglicht weiterhin den Durchgang mit dieser Methode

William Miller
quelle
Vielen Dank für Ihre Beiträge! Ich habe dir das Kopfgeld verliehen, denn obwohl deine Lösung nicht perfekt war, weist sie definitiv den Weg nach vorne und du hast viel Zeit und Gedanken in dieses Problem gesteckt - Danke !! Ich bin mir jetzt sicher, dass dieses Problem letztendlich eine Funktionslücke in MatterJS darstellt, und ich hoffe, dass diese Diskussion in Zukunft zu einer echten Lösung beitragen wird.
d13
@ d13 Danke, ich stimme zu, dass das Problem letztendlich im zugrunde liegenden Code liegt, aber ich bin froh, dass ich einen Anschein von Lösung (en) bekommen konnte
William Miller
0

Ich hätte das Feature auf andere Weise verwaltet:

  • Kein "Ziehen" (also keine kontinuierliche Ausrichtung des Ziehpunkts mit dem Versatz gegenüber dem gezogenen Objekt)
  • Bei mouseDown gibt die Mauszeigerposition einen orientierten Geschwindigkeitsvektor an, dem das Objekt folgen soll
  • Bei mouseUp setzen Sie Ihren Geschwindigkeitsvektor zurück
  • Lassen Sie die Materiesimulation den Rest erledigen
Mosè Raguzzini
quelle
1
Geht das nicht schon so matter.jsmit schleppenden Körpern um? von hier "... wie eine virtuelle Feder, die an der Maus befestigt ist. Beim Ziehen ... wird die Feder [am Körper] befestigt und zieht in Richtung der Maus ..."
Ghost
Das Einstellen nur der Geschwindigkeit verhindert das Ziehen von Überlappungen, die Geschwindigkeit zwingt den Körper durch andere.
Mosè Raguzzini
Dies könnte tatsächlich auf eine Lösung hinweisen. Wenn ich das richtig verstehe, bedeutet dies, dass die in MouseConstraint integrierte MatterJS nicht verwendet wird und die Geschwindigkeit des Körpers manuell basierend auf der Position der Maus eingestellt wird. Ich bin mir jedoch nicht sicher, wie dies genau implementiert werden soll. Wenn also jemand Details dazu veröffentlichen kann, wie der Körper an der Position der Maus ausgerichtet wird, ohne setPosition oder eine Einschränkung zu verwenden, tun Sie dies bitte.
d13
@ d13 Sie würden sich immer noch auf MatterJS verlassen, um Resolverzu entscheiden, was bei kollidierenden Körpern zu tun ist. Nachdem Sie diesen Code ein wenig durchgesehen haben, würde es sich unter vielen Umständen immer noch dafür entscheiden, das Durchziehen zuzulassen auch Ihre eigene Version implementiert solveVelocityund solvePositionaber an diesem Punkt sind Sie immer noch manuell tun , was Sie MatterJS direkt zu handhaben wollen ....
Geister
0

Um die Kollision beim Ziehen zu steuern, müssen Sie Kollisionsfilter und Ereignisse verwenden .

Erstellen Sie Körper mit Standardkollisionsfiltermaske 0x0001 . Fügen Sie Fang startdragund enddragEreignisse hinzu und legen Sie verschiedene Filterkategorien für Körperkollisionen fest , um Kollisionen vorübergehend zu vermeiden.

Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
    event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
});
Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
     event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
});

window.addEventListener('load', function () {

  //Fetch our canvas
  var canvas = document.getElementById('world');

  //Setup Matter JS
  var engine = Matter.Engine.create();
  var world = engine.world;
  var render = Matter.Render.create({
                                      canvas: canvas,
                                      engine: engine,
                                      options: {
                                        width: 800,
                                        height: 800,
                                        background: 'transparent',
                                        wireframes: false,
                                        showAngleIndicator: false
                                      }
                                    });

  //Add a ball
  const size = 50;
  const stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 0, 0, (x, y) => {
    return Matter.Bodies.rectangle(x, y, size * 2, size, {
      collisionFilter: {
            mask: 0x0001,
      },
      slop: 0.5,
      friction: 1,
      frictionStatic: Infinity,
    });
  });

  Matter.World.add(engine.world, stack);

  //Add a floor
  var floor = Matter.Bodies.rectangle(250, 520, 500, 40, {
    isStatic: true, //An immovable object
    render: {
      visible: false
    }
  });
  Matter.World.add(world, floor);

  //Make interactive
  var mouseConstraint = Matter.MouseConstraint.create(engine, { //Create Constraint
    element: canvas,

    constraint: {
      render: {
        visible: false
      },
      stiffness: 0.8
    }
  });
  Matter.World.add(world, mouseConstraint);

  // add events to listen drag
  Matter.Events.on(mouseConstraint, 'startdrag', function (event) {
    event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
  });
  Matter.Events.on(mouseConstraint, 'enddrag', function (event) {
    event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
  });

  //Start the engine
  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.min.js"></script>

Temur Tchanukvadze
quelle
1
Vielen Dank für Ihre hervorragende Demo! Ich versuche tatsächlich, den gegenteiligen Effekt zu erzielen: Ich muss verhindern, dass sich Körper schneiden, wenn einer in einen anderen gezogen wird.
13.
Entschuldigung, wenn ich das Problem falsch verstanden habe. Können Sie klarstellen, was Sie meinen, indem Sie verhindern, dass sich Körper schneiden? Versuchen Sie zu verhindern, dass Sie mit Gewalt durch andere Objekte ziehen?
Temur Tchanukvadze
1
In diesem Fall handelt es sich um ein offenes Problem, das nicht ohne Hardcodierung zur Implementierung von CCD ausgeführt werden kann.
Werfen
0

Dies scheint mit Problem 672 auf der GitHub-Seite zu tun zu haben, was darauf hindeutet, dass dies auf einen Mangel an CCD (Continuous Collision Detection) zurückzuführen ist.

Ein Versuch , hier Abhilfe zu schaffen wurde gemacht , und der Code für sie gefunden werden kann hier aber die Frage ist noch offen , so dass es aussieht wie Sie den Motor zu bauen CCD in es selbst bearbeiten benötigen.

Mweya Ruider
quelle
1
Danke für deine Antwort! Ich hatte darüber nachgedacht, aber ich glaube, es ist kein CCD-Problem, sondern ein Problem von "Was passiert, wenn eine unaufhaltsame Kraft auf ein unbewegliches Hindernis trifft?" Irgendwie muss ich herausfinden, wie ich die Kräfte neutralisieren kann, um zu verhindern, dass sich die Körper schneiden.
13.