top of page
Search
  • cedarcantab

Verlet Physics Part 2+ Rope


Particle class



class Point {

constructor(x, y, vx, vy, radius) {

this.pos = new Phaser.Math.Vector2(x, y);

this.oldpos = new Phaser.Math.Vector2(x + (vx||0), y + (vy||0)); // velocity x, y

this.bounce = 0.99;

this.friction = 0.97;

this.groundFriction = 0.7;

this.gravity = new Phaser.Math.Vector2(0, 1);

this.pinned = false;

this.radius = radius || 5;

this.color = "0xe62a4f";

this.mass = 1;

}

update() {

if (this.pinned) return;

let vel = this.pos.clone().subtract(this.oldpos);

vel.scale(this.friction);

// if the point touches the ground set groundFriction

if (this.pos.y >= CANVAS_HEIGHT - this.radius && vel.lengthSq() > 0.000001) {

vel.scale(this.groundFriction);

}

this.oldpos.set(this.pos.x, this.pos.y);

this.pos.add(vel);

this.pos.add(this.gravity);

}

resetVelocity() {

this.oldpos.set(this.pos.x, this.pos.y);

}

setGravity(g) {

this.gravity = g;

return this;

}

setFriction(f) {

this.friction = f;

return this;

}

setGroundFriction(f) {

this.groundFriction = f;

return this;

}

setVelocity(vel) {

this.oldpos.set(vel.x, vel.y);

return this;

}

setBounce(b) {

this.bounce = b;

return this;

}

pin() {

this.pinned = true;

return this;

}

unpin() {

this.pinned = false;

return this;

}

constrain() {

if (this.pos.x > CANVAS_WIDTH - this.radius) {

this.pos.x = CANVAS_WIDTH - this.radius;

}

if (this.pos.x < this.radius) {

this.pos.x = this.radius;

}

if (this.pos.y > CANVAS_HEIGHT - this.radius) {

this.pos.y = CANVAS_HEIGHT - this.radius;

}

if (this.pos.y < this.radius) {

this.pos.y = this.radius;

}

};

render(ctx) {

ctx.fillStyle(this.color).fillCircle(this.pos.x, this.pos.y, this.radius)

}

}



Distance Constraint


class Stick {

constructor(p1, p2, length) {

this.startPoint = p1;

this.endPoint = p2;

this.stiffness = 2;

this.color = '0xf5476a';

// if the length is not given then calculate the distance based on position

if (!length) {

this.length = Phaser.Math.Distance.BetweenPoints(this.startPoint.pos, this.endPoint.pos);

} else {

this.length = length;

}

}

update() {

// calculate the distance between two dots

let dx = this.endPoint.pos.x - this.startPoint.pos.x;

let dy = this.endPoint.pos.y - this.startPoint.pos.y;

// pythagoras theorem

let dist = Math.sqrt(dx dx + dy dy);

// calculate the resting distance betwen the dots

let diff = (this.length - dist) / dist * this.stiffness;

// getting the offset of the points

let offsetx = dx diff 0.5;

let offsety = dy diff 0.5;

// calculate mass

let m1 = this.startPoint.mass + this.endPoint.mass;

let m2 = this.startPoint.mass / m1;

m1 = this.endPoint.mass / m1;

// and finally apply the offset with calculated mass

if (!this.startPoint.pinned) {

this.startPoint.pos.x -= offsetx * m1;

this.startPoint.pos.y -= offsety * m1;

}

if (!this.endPoint.pinned) {

this.endPoint.pos.x += offsetx * m2;

this.endPoint.pos.y += offsety * m2;

}

}

render(ctx) {

ctx.lineStyle(1,this.color);

ctx.lineBetween(this.startPoint.pos.x, this.startPoint.pos.y, this.endPoint.pos.x, this.endPoint.pos.y);

}

}



Entity Class


class Entity {

constructor(scene, iterations) {

this.scene = scene;

this.points = [];

this.sticks = [];

this.iterations = iterations || 16;

}

addPoint(x, y, vx, vy) {

this.points.push(new Point(x, y,vx, vy));

}

addStick(p1, p2) {

this.sticks.push(new Stick(this.points[p1], this.points[p2]));

}

setGravity(g) {

for (let i = 0; i < this.points.length; i++) {

this.points[i].setGravity(g);

}

}

pin(index) {

this.points[index].pin();

}

updatePoints() {

for (let i = 0; i < this.points.length; i++) {

this.points[i].update();

}

}

updateSticks() {

for (let i = 0; i < this.sticks.length; i++) {

this.sticks[i].update();

}

}

update() {

this.updatePoints();

for (let j = 0; j < this.iterations; j++) {

this.updateSticks();

this.updateContrains();

}

}

render() {

this.renderPoints();

this.renderSticks();

}

updateContrains() {

for (let i = 0; i < this.points.length; i++) {

this.points[i].constrain();

}

}

renderPoints() {

for (let i = 0; i < this.points.length; i++) {

this.points[i].render(this.graphics);

}

}

renderSticks() {

for (let i = 0; i < this.sticks.length; i++) {

this.sticks[i].render(this.graphics);

}

}

}



Main Simulation class



class Verly {

constructor(scene, iterations) {

this.scene = scene;

this.entities = [];

this.iterations = iterations;

this.mouse = new Mouse(this.scene, this.entities)

}

addEntity(e) {

this.entities.push(e);

}

update() {

this.graphics.clear();

for (let i = 0; i < this.entities.length; i++) {

this.entities[i].update();

}

}

render() {

for (let i = 0; i < this.entities.length; i++) {

this.entities[i].render();

}

}

createBox(x, y, w, h) {

const box = new Entity(this.scene, this.iterations, "Box");

box.addPoint(x, y, 0, 0);

box.addPoint(x + w, y, 0, 0);

box.addPoint(x + w, y + h, 0, 0);

box.addPoint(x, y + h, 0, 0);

box.addStick(0, 1);

box.addStick(1, 2);

box.addStick(2, 3);

box.addStick(3, 0);

box.addStick(3, 1);

this.addEntity(box);

return box;

}

createRope(x, y, segments = 10, gap = 15, pin) {

let rope = new Entity(this.scene, this.iterations, this, 'Rope');

for (let i = 0; i < segments; i++) {

rope.addPoint(x + i * gap, y, 0, 0);

}

for (let i = 0; i < segments - 1; i++) {

rope.addStick(i, (i + 1) % segments);

}

if (pin !== undefined) {

rope.pin(pin);

}

this.addEntity(rope);

return rope;

}

interact() {

this.mouse.drag();

}

}







class Mouse {

constructor(scene, entities) {

this.scene = scene;

this.entities = entities;

// mouse related variables

this.draggedPoint = null;

this.down = false;

this.coord = new Phaser.Math.Vector2()

this.text = this.scene.add.text(0,0,'', {color: '0x000000'})

this.scene.input.on('pointermove', pointer => this.interact(pointer));

this.scene.input.on('pointerdown', pointer => {

this.down = true;

this.drag()

});

this.scene.input.on('pointerup', pointer=>{

if (this.draggedPoint) {

this.draggedPoint.resetVelocity();

};

this.down = false;

this.draggedPoint = null;

})

}

interact(pointer) {

this.coord.copy(pointer)

this.text.setText("x="+this.coord.x+"y="+this.coord.y);

}

dragPoint() {

if (!this.down) return;

this.draggedPoint.pos.set(this.coord.x, this.coord.y);

}

drag() {

if (!this.down) {

this.draggedPoint = this.getNearestPoint();

}

if (this.draggedPoint) {

this.renderDraggedPoint(this.draggedPoint);

this.dragPoint();

}

}

renderDraggedPoint(point) {

this.graphics.lineStyle(1,'0x000000');

this.graphics.strokeCircle(point.pos.x, point.pos.y, 8);

}

getNearestPoint() {

// if (!this.down) return false;

let d = 20;

let p = null;

for (let k = 0; k < this.entities.length; k++) {

for (let i = 0; i < this.entities[k].points.length; i++) {

let dist = Phaser.Math.Distance.BetweenPoints(this.entities[k].points[i].pos, this.coord);

if (dist < d) {

p = this.entities[k].points[i];

}

}

}

return p;

}

}


Boot


class main extends Phaser.Scene {

constructor() {

super({ key: "main" });

}

preload() {}

create() {

this.world = new Verly(this);

this.world.createBox(100,100,100,100)

this.world.createRope(100,50,10,15,0)

}

update() {

this.world.update();

this.world.render();

this.world.interact();

}

}



Useful References






6 views0 comments

Recent Posts

See All

p2 naive broadphase

var Broadphase = require('../collision/Broadphase'); module.exports = NaiveBroadphase; /** * Naive broadphase implementation. Does N^2 tests. * * @class NaiveBroadphase * @constructor * @extend

Extending Box2D-Lite in Javascript: Revolute Joint

/// Revolute joint definition. This requires defining an anchor point where the /// bodies are joined. The definition uses local anchor points so that the /// initial configuration can violate the con

sopiro motor constranit

import { Matrix2, Vector2 } from "./math.js"; import { RigidBody } from "./rigidbody.js"; import { Settings } from "./settings.js"; import * as Util from "./util.js"; import { Joint } from "./joint.js

記事: Blog2_Post
bottom of page