Closed 8Observer8 closed 2 months ago
Box2D-WASM has a better solution. It just pass vertices directly and I can draw the lines:
drawLines(vertices, color) {
const c = new PIXI.Color([color.r, color.g, color.b]).toHex();
this.lines.lineStyle(1, c, 1, 0.5, true);
this.lines.moveTo(vertices[0].x * this.pixelsPerMeter, vertices[0].y * this.pixelsPerMeter);
this.lines.lineTo(vertices[1].x * this.pixelsPerMeter, vertices[1].y * this.pixelsPerMeter);
this.lines.lineTo(vertices[2].x * this.pixelsPerMeter, vertices[2].y * this.pixelsPerMeter);
this.lines.lineTo(vertices[3].x * this.pixelsPerMeter, vertices[3].y * this.pixelsPerMeter);
this.lines.lineTo(vertices[0].x * this.pixelsPerMeter, vertices[0].y * this.pixelsPerMeter);
}
This is the full code of the example above:
The example is small: 62 lines in index.js
and 50 lines in debug-drawer.js
:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Example</title>
<link rel="stylesheet" type="text/css" href="./css/style.css">
</head>
<body>
<!-- Since importmap is not yet supported by all browsers, it is
necessary to add the polyfill es-module-shims.min.js -->
<script async src="https://unpkg.com/es-module-shims@0.1.7/dist/es-module-shims.min.js"></script>
<script type="importmap">
{
"imports": {
"@box2d/core": "https://cdn.skypack.dev/@box2d/core@0.10.0",
"pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@7.2.4/+esm"
}
}
</script>
<script type="module" src="./js/index.js"></script>
</body>
</html>
index.js
import { b2BodyType, b2PolygonShape, b2Vec2, b2World, DrawShapes } from "@box2d/core";
import * as PIXI from "pixi.js";
import DebugDrawer from "./debug-drawer.js";
async function init() {
const renderer = PIXI.autoDetectRenderer(300, 300, {
backgroundColor: 0x000000,
antialias: true,
resolution: 1
});
renderer.view.width = 300;
renderer.view.height = 300;
document.body.appendChild(renderer.view);
// Create the main stage for your display objects
const stage = new PIXI.Container();
const world = b2World.Create({ x: 0, y: 9.8 });
const pixelsPerMeter = 30;
const debugDrawer = new DebugDrawer(stage, pixelsPerMeter);
// Box
const boxShape = new b2PolygonShape();
boxShape.SetAsBox(30 / pixelsPerMeter, 30 / pixelsPerMeter);
const boxBody = world.CreateBody({
type: b2BodyType.b2_dynamicBody,
position: { x: 100 / pixelsPerMeter, y: 30 / pixelsPerMeter },
angle: 30 * Math.PI / 180
});
boxBody.CreateFixture({ shape: boxShape, density: 1 });
// Ground
const groundShape = new b2PolygonShape();
groundShape.SetAsBox(130 / pixelsPerMeter, 20 / pixelsPerMeter);
const groundBody = world.CreateBody({
type: b2BodyType.b2_staticBody,
position: { x: 150 / pixelsPerMeter, y: 270 / pixelsPerMeter }
});
groundBody.CreateFixture({ shape: groundShape });
let currentTime, lastTime, dt;
function render() {
requestAnimationFrame(render);
currentTime = Date.now();
dt = (currentTime - lastTime) / 1000;
lastTime = currentTime;
world.Step(dt, { velocityIterations: 3, positionIterations: 2 });
DrawShapes(debugDrawer, world);
// Render the stage
renderer.render(stage);
debugDrawer.clear();
}
lastTime = Date.now();
render();
}
init();
debug-drawer.js
import * as PIXI from "pixi.js";
export default class DebugDrawer {
constructor(stage, pixelsPerMeter) {
this.lines = new PIXI.Graphics();
stage.addChild(this.lines);
this.pixelsPerMeter = pixelsPerMeter;
this.translationX = 0;
this.translationY = 0;
this.angle = 0;
}
DrawSolidPolygon(vertices, vertexCount, color) {
const c = new PIXI.Color([color.r, color.g, color.b]).toHex();
this.lines.lineStyle(1, c, 1, 0.5, true);
this.lines.moveTo((vertices[0].x + this.translationX) * this.pixelsPerMeter,
(vertices[0].y + this.translationY) * this.pixelsPerMeter);
this.lines.lineTo((vertices[1].x + this.translationX) * this.pixelsPerMeter,
(vertices[1].y + this.translationY) * this.pixelsPerMeter);
this.lines.lineTo((vertices[2].x + this.translationX) * this.pixelsPerMeter,
(vertices[2].y + this.translationY) * this.pixelsPerMeter);
this.lines.lineTo((vertices[3].x + this.translationX) * this.pixelsPerMeter,
(vertices[3].y + this.translationY) * this.pixelsPerMeter);
this.lines.lineTo((vertices[0].x + this.translationX) * this.pixelsPerMeter,
(vertices[0].y + this.translationY) * this.pixelsPerMeter);
this.lines.angle = this.angle;
}
clear() {
this.lines.clear();
}
PushTransform(xf) {
this.translationX = xf.p.x;
this.translationY = xf.p.y;
this.angle = xf.q.s * 180 / Math.PI;
}
PopTransform(xf) {}
DrawPolygon(vertices, vertexCount, color) {}
DrawCircle(center, radius, color) {}
DrawSolidCircle(center, radius, axis, color) {}
DrawSegment(p1, p2, color) {}
DrawTransform(xf) {}
DrawPoint(p, size, color) {}
}
I got the idea with drawing colliders with Pixi.js from this rapier2d topic: https://github.com/dimforge/rapier.js/pull/119 (Add basic debug-render support). It shows how to draw colliders for rapier2d/3d with Pixi.js and Three.js:
if (!this.lines) {
this.lines = new PIXI.Graphics();
this.viewport.addChild(this.lines);
}
let buffers = world.debugRender();
let vtx = buffers.vertices;
let cls = buffers.colors;
this.lines.clear();
for (let i = 0; i < vtx.length / 4; i += 1) {
let color = PIXI.utils.rgb2hex([cls[i * 8], cls[i * 8 + 1], cls[i * 8 + 2]]);
this.lines.lineStyle(1.0, color, cls[i * 8 + 3], 0.5, true);
this.lines.moveTo(vtx[i * 4], -vtx[i * 4 + 1]);
this.lines.lineTo(vtx[i * 4 + 2], -vtx[i * 4 + 3]);
}
I created another example using the Melon.js game engine: https://plnkr.co/edit/QVRPb8spIyVwLvtF?preview I draw a ground like this;
export default class DebugDrawer {
constructor(renderer, pixelsPerMeter) {
this.renderer = renderer;
this.pixelsPerMeter = pixelsPerMeter;
this.translationX = 0;
this.translationY = 0;
this.angle = 0;
}
DrawSolidPolygon(vertices, vertexCount, color) {
this.renderer.setLineWidth(3);
this.renderer.beginPath();
const c = new me.Color().setFloat(color.r, color.g, color.b, 1);
this.renderer.setColor(c);
this.renderer.moveTo((vertices[0].x + this.translationX) * this.pixelsPerMeter,
(vertices[0].y + this.translationY) * this.pixelsPerMeter);
this.renderer.lineTo((vertices[1].x + this.translationX) * this.pixelsPerMeter,
(vertices[1].y + this.translationY) * this.pixelsPerMeter);
this.renderer.lineTo((vertices[2].x + this.translationX) * this.pixelsPerMeter,
(vertices[2].y + this.translationY) * this.pixelsPerMeter);
this.renderer.lineTo((vertices[3].x + this.translationX) * this.pixelsPerMeter,
(vertices[3].y + this.translationY) * this.pixelsPerMeter);
this.renderer.lineTo((vertices[0].x + this.translationX) * this.pixelsPerMeter,
(vertices[0].y + this.translationY) * this.pixelsPerMeter);
this.renderer.stroke();
}
PushTransform(xf) {
this.translationX = xf.p.x;
this.translationY = xf.p.y;
this.angle = xf.q.s * 180 / Math.PI;
}
I want to draw a rotated platform:
// Platform
const platformBodyDef = new b2BodyDef();
platformBodyDef.set_position(new b2Vec2(220 / this.pixelsPerMeter,
200 / this.pixelsPerMeter));
platformBodyDef.angle = -20 * Math.PI / 180;
const platformBody = this.world.CreateBody(platformBodyDef);
const platformShape = new b2PolygonShape();
platformShape.SetAsBox(50 / this.pixelsPerMeter, 5 / this.pixelsPerMeter);
platformBody.CreateFixture(platformShape, 0);
For a while it looks like this because I don't apply a rotation:
I see these two methods:
PushTransform(xf) {
this.translationX = xf.p.x;
this.translationY = xf.p.y;
this.angle = xf.q.s * 180 / Math.PI;
}
PopTransform(xf) {}
What is the difference between them? It looks like they have translations and angles. But how to apply this angle to the lines?
I solved a problem above with Melon.js using this example from this message.
PushTransform(xf) {
this.renderer.save();
this.renderer.translate(xf.p.x * this.pixelsPerMeter, xf.p.y * this.pixelsPerMeter);
this.renderer.rotate(xf.q.GetAngle());
}
PopTransform(xf) {
this.renderer.restore();
}
Phaser 3 and Melon.js have the save()
and restore()
methods even in WEBGL mode:
Pixi.js 8 has save()
and restore
methods in the GraphicsContext class. How to solve the rotation problem: https://plnkr.co/edit/1RizUv5yknLrb9w4?preview
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Set up Pixi.js 8 as ES6-module in JavaScript</title>
</head>
<body>
<script type="importmap">
{
"imports": {
"@box2d/core": "https://8observer8.github.io/libs/box2d-core@0.10.0/box2d-core.min.js",
"pixi.js": "https://8observer8.github.io/libs/pixi.js@8.1.6/pixi.min.mjs"
}
}
</script>
<script type="module" src="./js/index.js"></script>
</body>
</html>
index.js
import { b2BodyType, b2PolygonShape, b2World } from "@box2d/core";
import { DrawShapes } from "@box2d/core";
import { Application, Graphics } from "pixi.js";
import DebugDrawer from "./debug-drawer.js";
// Create the main application and the canvas
const app = new Application();
await app.init({ width: 400, height: 400 });
document.body.appendChild(app.canvas);
const world = b2World.Create({ x: 0, y: 10 });
const pixelsPerMeter = 30;
// Ground
const groundShape = new b2PolygonShape();
groundShape.SetAsBox(130 / pixelsPerMeter, 20 / pixelsPerMeter);
const groundBody = world.CreateBody({
type: b2BodyType.b2_staticBody,
position: { x: 150 / pixelsPerMeter, y: 270 / pixelsPerMeter }
});
groundBody.CreateFixture({ shape: groundShape });
// Box
const boxShape = new b2PolygonShape();
boxShape.SetAsBox(30 / pixelsPerMeter, 30 / pixelsPerMeter);
const boxBody = world.CreateBody({
type: b2BodyType.b2_dynamicBody,
angle: 10 * Math.PI / 180,
position: { x: 200 / pixelsPerMeter, y: 30 / pixelsPerMeter }
});
boxBody.CreateFixture({ shape: boxShape, density: 1 });
const debugGraphics = new Graphics();
const debugDrawer = new DebugDrawer(debugGraphics, pixelsPerMeter);
app.stage.addChild(debugGraphics);
draw();
function draw() {
world.Step(0.016, { velocityIterations: 3, positionIterations: 2 });
debugGraphics.clear();
DrawShapes(debugDrawer, world);
requestAnimationFrame(draw);
}
debug-drawer.js
import { Color } from "pixi.js";
export default class DebugDrawer {
constructor(graphics, pixelsPerMeter, lineWidth = 5) {
this.graphics = graphics;
this.ppm = pixelsPerMeter;
this.lineWidth = lineWidth;
}
DrawSolidPolygon(vertices, vertexCount, color) {
const c = new Color([color.r, color.g, color.b]).toHex();
this.graphics.moveTo(vertices[0].x * this.ppm, vertices[0].y * this.ppm);
this.graphics.lineTo(vertices[1].x * this.ppm, vertices[1].y * this.ppm);
this.graphics.lineTo(vertices[2].x * this.ppm, vertices[3].y * this.ppm);
this.graphics.lineTo(vertices[3].x * this.ppm, vertices[3].y * this.ppm);
this.graphics.lineTo(vertices[0].x * this.ppm, vertices[0].y * this.ppm);
this.graphics.stroke({ color: c, width: this.lineWidth });
}
DrawSolidCircle(center, radius, axis, color) {
}
PushTransform(xf) {
this.graphics.context.save();
this.graphics.context.translate(xf.p.x * this.ppm, xf.p.y * this.ppm);
this.graphics.context.rotate(xf.q.GetAngle());
}
PopTransform(xf) {
this.graphics.context.restore();
}
DrawCircle(center, radius, color) {}
DrawPolygon(vertices, vertexCount, color) {}
DrawSegment(p1, p2, color) {}
DrawTransform(xf) {}
DrawPoint(p, size, color) {}
}
I have created the issue for Pixi.js 8: https://github.com/pixijs/pixijs/issues/10632
It is very good that @box2d/core
, @box2d/particles
, and Pixi.js v8
can be loaded from Skypack:
<script type="importmap">
{
"imports": {
"@box2d/core": "https://cdn.skypack.dev/@box2d/core@0.10.0",
"@box2d/particles": "https://cdn.skypack.dev/@box2d/particles@0.10.0",
"pixi.js": "https://cdn.skypack.dev/pixi.js@8.1.6"
}
}
</script>
<script type="module" src="./js/index.js"></script>
index.js
import { b2CircleShape, b2World } from "@box2d/core";
import { b2ParticleGroupDef, b2ParticleSystemDef } from "@box2d/particles";
import { Application, Graphics } from "pixi.js";
I have swapped the order of the rotation and translation and it works now: https://plnkr.co/edit/1RizUv5yknLrb9w4?preview
PushTransform(xf) {
this.graphics.context.save();
this.graphics.context.rotate(xf.q.GetAngle());
this.graphics.context.translate(xf.p.x * this.ppm, xf.p.y * this.ppm);
}
But Phaser 3 works in the other order: https://plnkr.co/edit/QZ6ce2LwdIExLdBf?preview
PushTransform(xf) {
this.graphics.save();
this.graphics.translateCanvas(xf.p.x * this.pixelsPerMeter,
xf.p.y * this.pixelsPerMeter);
this.graphics.rotateCanvas(xf.q.GetAngle());
}
This issue is similar to this one: Rotation problem with box2d-core and WebGL
box2d-core
has a method to draw vertices without translation and rotation:DrawSolidPolygon(vertices, vertexCount, color)
and separate method to set translations and rotations:I created the lines object in the
DebugDrawer
class:I draw lines but transforms applied on whole
lines
object: