MatterJS를 사용하여 다른 바디를 통한 강제 끌기 방지


14

물리 기반 게임에 MatterJs를 사용하고 있는데, 다른 바디를 통해 마우스로 바디에 힘을 가하지 못하게하는 문제에 대한 해결책을 찾지 못했습니다. 바디를 다른 바디로 드래그하면 드래그되는 바디가 다른 바디로 또는 다른 바디를 통과 할 수 있습니다. 교차하지 않도록 신뢰할 수있는 방법을 찾고 있습니다. 마우스로 바디를 선택하고 다른 바디를 통해 강제로 적용하여 MatterJS 데모에서이 효과를 관찰 할 수 있습니다. 일반적인 예는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

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

불행히도 드래그 앤 드롭에 따라 게임이나 시뮬레이션이 중단됩니다. 충돌이 발생할 때 마우스 제약 조건을 깨거나 제약 조건 강성을 줄이는 것과 같은 수많은 솔루션을 시도했지만 안정적으로 작동하는 것은 없습니다.

어떤 제안이라도 환영합니다!


나는 힘이 줄어드는 표현을 이해하지 못한다. 드래그 한 바디가 다른 바디를 통과해야합니까?
grodzi

아니요, 드래그 된 바디가 다른 바디를 통과하지 못하도록해야합니다.
d13

1
@ d13 문제를 보여주는 애니메이션을 추가 할 수 있습니까? 그 말에 근거하여 약간의 혼동이있는 것처럼 보이기 때문에 ...
Ghost

2
@Ghost 님이 추가했습니다 ...
d13

@ d13 그게 더 명확 해집니다 ..... 이것은 까다로운 것입니다
Ghost

답변:


6

여기에서 가장 좋은 대답 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>

이 예에서는 velocityand positionImpulsein x및 in y을 최대 크기로 제한하고 25.0있습니다. 결과는 아래와 같습니다.

여기에 이미지 설명을 입력하십시오

보시다시피, 시체를 끌 때 매우 폭력적 일 수 있으며 서로 통과하지 않습니다. 이것이 바로이 접근 방식을 다른 접근 방식과 차별화하는 이유입니다. 사용자가 드래그하는 데 충분히 폭력적 일 경우 대부분의 다른 잠재적 솔루션은 실패합니다.

이 방법으로 만난 유일한 단점은 비 정적 몸체를 사용하여 Resolver모듈이 충돌을 감지하지 못하고 모듈이 다른 몸을 통과하는 두 번째 몸. (정적 마찰 예제에서 필요한 속도는 약 50.0이며, 한 번만 성공적으로 수행했으며 결과적으로 그것을 묘사하는 애니메이션이 없습니다).


해결책 2

이것은 추가 솔루션이며 공정한 경고입니다. 간단하지 않습니다.

넓은 의미에서 이것이 작동하는 방식은 드래그되는 바디가 dragBody정적 바디와 충돌했는지 여부와 마우스가 dragBody따라 가지 않고 너무 멀리 이동했는지 확인하는 것 입니다. 마우스와의 거리 dragBody가 너무 멀어 지면 이벤트 리스너를 제거하고 다른 mousemove 함수로 대체합니다 . 이 기능은 마우스가 신체 중심 부근에 돌아 왔는지 확인합니다. 불행히도 내장 메소드가 제대로 작동하지 않아서 직접 포함시켜야했습니다 (자바 스크립트에서 나보다 더 지식이있는 사람이 그 사실을 알아야합니다). 마지막으로 이벤트가 감지되면 일반 리스너로 다시 전환됩니다 .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.js mouseup감지에 해당되므로 수정하려고하지 않았습니다.

속도가 충분히 크면 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: 불일치 충돌 감지는 여전히이 방법으로 통과를 허용합니다


당신의 공헌에 감사드립니다! 귀하의 솔루션이 완벽하지는 않았지만 분명히 앞으로 나아갈 길을 지적 하고이 문제에 대해 많은 생각과 시간을 투자했기 때문에 현상금을 수여했습니다. 감사합니다 !! 나는 이제이 문제가 MatterJS의 궁극적 인 갭이 될 것이라고 확신하며,이 논의가 미래의 실질적인 해결책에 기여하기를 바랍니다.
d13

@ d13 감사합니다, 문제는 궁극적으로 기본 코드에 있다는 것에 동의하지만 솔루션의 일부를 얻을 수있어서 기쁩니다.
William Miller

0

다른 방법으로 기능을 관리했을 것입니다.

  • "끌기"없음 (그래서 오프셋 대 끌기 객체와 끌기 지점의 연속 정렬이 없음)
  • mouseDown에서 마우스 포인터 위치는 객체가 따라갈 방향 속도 벡터를 제공합니다.
  • mouseUp에서 속도 벡터 재설정
  • 물질 시뮬레이션이 나머지를 수행하게하십시오

1
그것은 matter.js이미 시체 끌기를 처리 하는 방법 이 아닙니까? 발 여기서 "... 가상 스프링 등이 마우스의 방향으로 마우스에 부착 끌면 ... 스프링 [본체에] 부착되고 당긴. ..."
고스트

속도 만 설정하면 드래그가 겹치지 않으며, 스핑은 다른 바디를 통해 바디를 강제합니다.
Mosè Raguzzini

이것은 실제로 해결책을 가리킬 수 있습니다. 올바르게 이해하면 MatterJS의 내장 MouseConstraint를 사용하지 않고 마우스의 위치를 ​​기준으로 신체의 속도를 수동으로 설정하는 것을 의미합니다. 그러나 이것이 어떻게 구현되는지 확실하지 않으므로 누군가가 setPosition 또는 제약 조건을 사용하지 않고 신체를 마우스 위치에 정렬하는 방법에 대한 세부 정보를 게시 할 수 있다면 수행하십시오.
d13

@ d13 당신은 여전히 Resolver충돌하는 시체에 대해 무엇을 해야할지 결정하기 위해 MatterJS에 의존하고 있습니다. 또한 자신 만의 버전 구현 solveVelocitysolvePosition하지만 그 시점에서 당신은 여전히 수동으로 .... 당신이 MatterJS 직접 처리하는 일을하고 있습니다
고스트

0

드래그 할 때 충돌을 제어하려면 충돌 필터이벤트 를 사용해야 합니다 .

기본 충돌 필터 마스크로 바디를 만듭니다 0x0001. 캐치 startdragenddrag이벤트를 추가 하고 일시적으로 충돌을 피하기 위해 다른 바디 충돌 필터 범주 를 설정하십시오 .

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>


1
당신에게 훌륭한 데모를 주셔서 대단히 감사합니다! 나는 실제로 반대 효과를 얻으려고 노력하고 있습니다. 한 물체를 다른 물체로 끌 때 몸체가 교차하지 않도록해야합니다.
d13

문제를 잘못 이해하면 죄송합니다. 시체가 교차하는 것을 방지하여 의미를 명확하게 설명 할 수 있습니까? 힘을 가했을 때 다른 물체를 끌지 못하게하려고합니까?
Temur Tchanukvadze '12

1
이 경우 공개 문제이며 CCD를 구현하기 위해 하드 코딩 없이는 수행 할 수 없습니다. 보세요 : github.com/liabru/matter-js/issues/5
Temur Tchanukvadze에게

0

이것은 GitHub 페이지의 672 문제 와 관련이있는 것으로 보입니다 .CCD (Continuous Collision Detection)가 없기 때문에 발생하는 것으로 보입니다.

이를 해결하려는 시도가 있었고 여기에 대한 코드를 찾을 수 있지만 문제는 여전히 열려 있으므로 CCD를 직접 빌드하기 위해 엔진을 편집해야 할 것 같습니다.


1
답변 주셔서 감사합니다! 나는 이것을 고려했지만 CCD 문제가 아니라 "멈출 수없는 힘이 움직일 수없는 장애물을 만나면 어떻게됩니까?"의 문제라고 생각합니다. 어떻게 든 신체가 교차하지 않도록 힘을 중화하는 방법을 알아 내야합니다.
d13
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.