Published
Edited
Jun 3, 2021
Insert cell
Insert cell
Insert cell
Insert cell
rapier2d = {
const rapier = await require('https://cdn.jsdelivr.net/npm/@dimforge/rapier2d-compat');
await rapier.init(); // Note that this call needs to be awaited.
return rapier;
}
Insert cell
Insert cell
world1 = {
let gravity = new rapier2d.Vector2(0.0, -9.81);
let world = new rapier2d.World(gravity);

invalidation.then(() => world.free());
return world;
}
Insert cell
Insert cell
Insert cell
Insert cell
world2 = {
let gravity = new rapier2d.Vector2(0.0, -9.81);
let world = new rapier2d.World(gravity);
invalidation.then(() => world.free());

// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Dynamic
).setTranslation(1.0, 1.0);
let rigidBody = world.createRigidBody(rigidBodyDesc);

invalidation.then(() => world.free());
return world;
}
Insert cell
worldInspector(world2)
Insert cell
Insert cell
Insert cell
Insert cell
world3 = {
let gravity = new rapier2d.Vector2(0.0, -9.81);
let world = new rapier2d.World(gravity);
invalidation.then(() => world.free());

// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Dynamic
).setTranslation(2.0, 4.0);
let rigidBody = world.createRigidBody(rigidBodyDesc);

// Create a cuboid collider attached to rigidBody.
let colliderDesc = rapier2d.ColliderDesc.ball(1).setDensity(1.0); // The default density is 1.0.
let collider = world.createCollider(colliderDesc, rigidBody.handle);

invalidation.then(() => world.free());
return world;
}
Insert cell
worldInspector(world3)
Insert cell
Insert cell
world4 = {
let gravity = new rapier2d.Vector2(0.0, -9.81);
let world = new rapier2d.World(gravity);
invalidation.then(() => world.free());

// Create the ground
let groundRigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Static
).setTranslation(4, 1);
let groundRigidBody = world.createRigidBody(groundRigidBodyDesc);
let groundColliderDesc = rapier2d.ColliderDesc.cuboid(3.0, 0.1);
world.createCollider(groundColliderDesc, groundRigidBody.handle);

// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Dynamic
).setTranslation(3, 4.0);
let rigidBody = world.createRigidBody(rigidBodyDesc);

// Create a cuboid collider attached to rigidBody.
let colliderDesc = rapier2d.ColliderDesc.cuboid(0.1, 0.1).setDensity(2.0); // The default density is 1.0.
let collider = world.createCollider(colliderDesc, rigidBody.handle);

return world;
}
Insert cell
worldInspector(world4)
Insert cell
Insert cell
simulation1 = {
let gravity = new rapier2d.Vector2(0.0, -9.81);
let world = new rapier2d.World(gravity);
invalidation.then(() => world.free());

// Create the ground
let groundRigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Static
).setTranslation(4, 1.0);
let groundRigidBody = world.createRigidBody(groundRigidBodyDesc);
let groundColliderDesc = rapier2d.ColliderDesc.cuboid(3.0, 0.1);
world.createCollider(groundColliderDesc, groundRigidBody.handle);

// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Dynamic
).setTranslation(3.0, 4.0);
let rigidBody = world.createRigidBody(rigidBodyDesc);

// Create a cuboid collider attached to rigidBody.
let colliderDesc = rapier2d.ColliderDesc.cuboid(0.2, 0.2).setDensity(2.0); // The default density is 1.0.
let collider = world.createCollider(colliderDesc, rigidBody.handle);

let torqueImpulse = 0.3;
let impulse = new rapier2d.Vector2(0, 2);
let force = new rapier2d.Vector2(-0.35, 0);
let torque = -0.4;

rigidBody.applyTorqueImpulse(torqueImpulse);
rigidBody.applyImpulse(impulse);

const forces = () => {
rigidBody.applyForce(force);
rigidBody.applyTorque(torque);
};

invalidation.then(() => world.free());
return { world, forces };
}
Insert cell
worldInspector(simulation1.world, simulation1.forces)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
let gravity = new rapier2d.Vector2(0.0, -9.81);
let world = new rapier2d.World(gravity);
let eventQueue = new rapier2d.EventQueue(true);
invalidation.then(() => world.free());

// Create the ground
let groundRigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Static
);
let groundRigidBody = world.createRigidBody(groundRigidBodyDesc);
let groundColliderDesc = rapier2d.ColliderDesc.cuboid(10.0, 0.1);
world.createCollider(groundColliderDesc, groundRigidBody.handle);

// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new rapier2d.RigidBodyDesc(
rapier2d.BodyStatus.Dynamic
).setTranslation(0.0, 10.0);
let rigidBody = world.createRigidBody(rigidBodyDesc);

// Create a cuboid collider attached to rigidBody.
let colliderDesc = rapier2d.ColliderDesc.cuboid(0.5, 0.5).setDensity(2.0); // The default density is 1.0.
let collider = world.createCollider(colliderDesc, rigidBody.handle);

let force = new rapier2d.Vector2(1.0, 2.0);
let impulse = new rapier2d.Vector2(0.1, 0.2);
let torque = 3.0;
let torqueImpulse = 0.3;

rigidBody.applyForce(force, true);
rigidBody.applyImpulse(impulse, true);
rigidBody.applyTorque(torque, true);
rigidBody.applyTorqueImpulse(torqueImpulse, true);

const events = [];
while (true) {
world.step(eventQueue);

eventQueue.drainContactEvents((handle1, handle2, contactStarted) => {
events.push({ type: "contact", handle1, handle2, contactStarted });
});
eventQueue.drainIntersectionEvents((handle1, handle2, intersecting) => {
events.push({ type: "intersection", handle1, handle2, intersecting });
});
yield events;
}
}
Insert cell
Insert cell
worldInspector = function*(
world,
forces = () => {},
onContact = (handle1, handle2, contactStarted) => {},
onIntersection = (handle1, handle2, intersecting) => {},
height = 600
) {
const dx = 12;
const dy = 24;
const marginLeft = 40;

const cxnInitials = cxnId => cxnId.match(/^.|[A-Z]/g).join("");

let matchingCxnId = null;

const canvas = DOM.canvas(width, height);
const interactionCanvas = DOM.canvas(width, height);
const context = canvas.getContext('2d');
const interactionContext = interactionCanvas.getContext('2d');

const pixelRatio = window.devicePixelRatio;

canvas.width = width * pixelRatio;
canvas.height = height * pixelRatio;
interactionCanvas.width = width * pixelRatio;
interactionCanvas.height = height * pixelRatio;

canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.style.cursor = 'move';

interactionCanvas.style.width = `${width}px`;
interactionCanvas.style.height = `${height}px`;

const draw = () => {
const transform = d3.zoomTransform(canvas);

context.save();
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(0, canvas.height);
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);
context.scale(pixelRatio, pixelRatio);
context.scale(100, -100); // 1px = 1cm
context.lineWidth = 0.01;
context.fillStyle = context.strokeStyle = "#000";

world.colliders.forEachCollider(collider => {
const { x, y } = collider.translation();
const r = collider.rotation();
context.save();
context.translate(x, y);
context.rotate(r);
drawCollider(context, collider);
context.restore();
});
context.restore();

// Draw interaction segmentation.

interactionContext.save();
interactionContext.clearRect(0, 0, canvas.width, canvas.height);
interactionContext.translate(0, canvas.height);
interactionContext.translate(transform.x, transform.y);
interactionContext.scale(transform.k, transform.k);
interactionContext.scale(pixelRatio, pixelRatio);

interactionContext.scale(100, -100); // 1px = 1cm
interactionContext.lineWidth = 0.01;

world.colliders.forEachCollider(collider => {
const { x, y } = collider.translation();
const r = collider.rotation();
interactionContext.save();
interactionContext.fillStyle = idToColor(collider.parent());
interactionContext.translate(x, y);
interactionContext.rotate(r);
drawCollider(interactionContext, collider, true);
interactionContext.restore();
});

interactionContext.restore();
};

const zoom = d3
.zoom()
.scaleExtent([0.05, 2])
.on("zoom", e => draw());

function dragsubject(event) {
const [x, y] = [event.x * pixelRatio, event.y * pixelRatio];
const colorData = interactionContext.getImageData(x, y, 1, 1).data;
if (colorData[3] === 255) {
const transform = d3.zoomTransform(canvas);
const bodyId = colorToId(colorData);
const body = world.getRigidBody(bodyId);
body.sleep();
const { x, y } = body.translation();
return {
x: transform.applyX(x * 100),
y: transform.applyY(y * -100) + height,
bodyId
};
}
}

function dragged(event) {
const transform = d3.zoomTransform(canvas);
const body = world.getRigidBody(event.subject.bodyId);
body.sleep();
body.setTranslation(
new rapier2d.Vector2(
transform.invertX(event.x) / 100,
transform.invertY(event.y - height) / -100
)
);
}

function dragended(event) {
const body = world.getRigidBody(event.subject.bodyId);
body.setLinvel(new rapier2d.Vector2(0, 0));
body.setAngvel(0);
body.wakeUp();
}

const drag = d3
.drag()
.container(canvas)
.subject(dragsubject)
// .on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);

d3.select(canvas)
.call(drag)
.call(zoom);

let eventQueue = new rapier2d.EventQueue(true);

while (true) {
forces();
world.step(eventQueue);

eventQueue.drainContactEvents(onContact);
eventQueue.drainIntersectionEvents(onIntersection);

draw();
yield canvas;
}
}
Insert cell
drawCollider = (context, collider, interaction = false) => {
context.beginPath();
switch (collider.shapeType()) {
case rapier2d.ShapeType.Ball:
const radius = collider.radius();
context.arc(0, 0, radius, 0, 2 * Math.PI);
break;
case rapier2d.ShapeType.Cuboid:
const halfExtents = collider.halfExtents();
context.rect(
-halfExtents.x,
-halfExtents.y,
halfExtents.x * 2,
halfExtents.y * 2
);
break;
}
if (interaction) {
context.fill();
} else {
context.stroke();
}
}
Insert cell
import { warn, note } from "@somethingelseentirely/papers"
Insert cell
function idToColor(id) {
var ret = [];
if(id < 16777215){
ret.push(id & 0xff); // R
ret.push((id & 0xff00) >> 8); // G
ret.push((id & 0xff0000) >> 16); // B
}
var col = "rgb(" + ret.join(',') + ")";
return col;
}
Insert cell
function colorToId([r, g, b, a]) {
return r | (g << 8) | (b << 16);
}
Insert cell
ColorHash = require('color-hash@1.0.3/dist/color-hash.js')
Insert cell
colorHash = new ColorHash({ lightness: 1 })
Insert cell
neon = label => colorHash.hex(label)
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more