Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/deploy-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: deploy-examples

on:
push:
branches:
- main
paths:
- "packages/forge2d/**"
- ".github/workflows/deploy-examples.yml"
workflow_dispatch:

permissions:
contents: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- uses: bluefireteam/melos-action@v3
- name: Build the examples
working-directory: packages/forge2d/example
run: |
dart run build_runner build --release --output build
# The output's packages directory is a symlink; materialize it so
# the published branch carries real files.
cp -rL build/web site
- name: Publish to the gh-pages branch
working-directory: packages/forge2d/example/site
run: |
touch .nojekyll
git init -b gh-pages
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "Deploy forge2d examples for $GITHUB_SHA"
git push --force \
"https://x-access-token:${{ github.token }}@github.com/${{ github.repository }}.git" \
gh-pages
11 changes: 8 additions & 3 deletions packages/forge2d/example/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# Examples for Forge2D

Runnable console examples of the native-backed API:
An interactive gallery of physics demos running on the WebAssembly backend,
deployed to GitHub Pages from main. Run it locally with:

```sh
dart run build_runner serve web:8080 --release
```

The console examples of the native backend still live in `bin/`:

```sh
dart run bin/hello_world.dart
dart run bin/car.dart
dart run bin/domino_tower.dart
```

Browser examples return when the WebAssembly backend lands.
3 changes: 3 additions & 0 deletions packages/forge2d/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ environment:

dependencies:
forge2d: ^0.14.2+1
web: ^1.1.1

dev_dependencies:
build_runner: ^2.6.0
build_web_compilers: ^4.2.0
flame_lint: ^1.4.1
172 changes: 172 additions & 0 deletions packages/forge2d/example/web/canvas_debug_draw.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import 'dart:js_interop';
import 'dart:math' as math;

import 'package:forge2d/forge2d.dart';
import 'package:web/web.dart';

/// Renders the world onto a canvas through the [DebugDraw] interface, with
/// a soft glow-on-dark styling.
class CanvasDebugDraw extends DebugDraw {
CanvasDebugDraw(this._context);

final CanvasRenderingContext2D _context;

/// The world point at the center of the view.
Vector2 center = Vector2.zero();

/// How many world meters are visible vertically.
double viewHeight = 40;

double _scale = 20;
double _canvasWidth = 0;
double _canvasHeight = 0;

/// Prepares the transform for a frame and clears the canvas.
void beginFrame(double canvasWidth, double canvasHeight) {
_canvasWidth = canvasWidth;
_canvasHeight = canvasHeight;
_scale = canvasHeight / viewHeight;
_context.clearRect(0, 0, canvasWidth, canvasHeight);
}

double _x(double worldX) => _canvasWidth / 2 + (worldX - center.x) * _scale;
double _y(double worldY) => _canvasHeight / 2 - (worldY - center.y) * _scale;

/// Converts a canvas point to world coordinates.
Vector2 toWorld(double canvasX, double canvasY) => Vector2(
center.x + (canvasX - _canvasWidth / 2) / _scale,
center.y - (canvasY - _canvasHeight / 2) / _scale,
);

String _rgba(int color, double alpha) {
final r = (color >> 16) & 0xFF;
final g = (color >> 8) & 0xFF;
final b = color & 0xFF;
return 'rgba($r, $g, $b, $alpha)';
}

void _stroke(int color) {
_context
..strokeStyle = _rgba(color, 0.95).toJS
..lineWidth = 1.6
..stroke();
}

void _fill(int color) {
_context
..fillStyle = _rgba(color, 0.28).toJS
..fill();
}

void _polygonPath(List<Vector2> vertices) {
_context.beginPath();
for (var i = 0; i < vertices.length; i++) {
final vertex = vertices[i];
if (i == 0) {
_context.moveTo(_x(vertex.x), _y(vertex.y));
} else {
_context.lineTo(_x(vertex.x), _y(vertex.y));
}
}
_context.closePath();
}

@override
void drawPolygon(List<Vector2> vertices, int color) {
_polygonPath(vertices);
_stroke(color);
}

@override
void drawSolidPolygon(
Transform transform,
List<Vector2> vertices,
double radius,
int color,
) {
_polygonPath([for (final vertex in vertices) transform.apply(vertex)]);
_fill(color);
_stroke(color);
}

@override
void drawCircle(Vector2 center, double radius, int color) {
_context.beginPath();
_context.arc(_x(center.x), _y(center.y), radius * _scale, 0, 2 * math.pi);
_stroke(color);
}

@override
void drawSolidCircle(Transform transform, double radius, int color) {
final position = transform.position;
_context.beginPath();
_context.arc(
_x(position.x),
_y(position.y),
radius * _scale,
0,
2 * math.pi,
);
_fill(color);
_stroke(color);
// A radius line makes rotation visible.
final edge = transform.apply(Vector2(radius, 0));
_context.beginPath();
_context.moveTo(_x(position.x), _y(position.y));
_context.lineTo(_x(edge.x), _y(edge.y));
_stroke(color);
}

@override
void drawSolidCapsule(
Vector2 point1,
Vector2 point2,
double radius,
int color,
) {
final axis = point2 - point1;
final angle = math.atan2(axis.y, axis.x);
_context.beginPath();
_context.arc(
_x(point1.x),
_y(point1.y),
radius * _scale,
-angle + math.pi / 2,
-angle + 3 * math.pi / 2,
);
_context.arc(
_x(point2.x),
_y(point2.y),
radius * _scale,
-angle - math.pi / 2,
-angle + math.pi / 2,
);
_context.closePath();
_fill(color);
_stroke(color);
}

@override
void drawSegment(Vector2 point1, Vector2 point2, int color) {
_context.beginPath();
_context.moveTo(_x(point1.x), _y(point1.y));
_context.lineTo(_x(point2.x), _y(point2.y));
_stroke(color);
}

@override
void drawPoint(Vector2 point, double size, int color) {
_context.beginPath();
_context.arc(_x(point.x), _y(point.y), size / 2, 0, 2 * math.pi);
_context.fillStyle = _rgba(color, 0.9).toJS;
_context.fill();
}

@override
void drawString(Vector2 point, String text, int color) {
_context
..fillStyle = _rgba(0xE5E9F5, 0.8).toJS
..font = '12px ui-sans-serif, system-ui'
..fillText(text, _x(point.x), _y(point.y));
}
}
117 changes: 117 additions & 0 deletions packages/forge2d/example/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Forge2D examples</title>
<link rel="icon" type="image/svg+xml" href="logo.svg">
<script defer src="main.dart.js"></script>
<style>
:root {
--bg: #0b1020;
--panel: #151b30;
--text: #e5e9f5;
--muted: #8b93ad;
--accent: #7c9cff;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
height: 100%;
background: var(--bg);
color: var(--text);
font-family: ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
overflow: hidden;
}
header {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.6rem 1rem;
background: var(--panel);
border-bottom: 1px solid #232b47;
}
header h1 {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1rem;
font-weight: 600;
margin: 0;
letter-spacing: 0.02em;
}
header h1 img { height: 1.7rem; width: 1.7rem; }
header h1 span { color: var(--accent); }
nav {
display: flex;
gap: 0.4rem;
flex-wrap: wrap;
}
nav button {
background: transparent;
color: var(--muted);
border: 1px solid #2a3355;
border-radius: 999px;
padding: 0.3rem 0.9rem;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.15s ease;
}
nav button:hover { color: var(--text); border-color: var(--accent); }
nav button.active {
background: var(--accent);
border-color: var(--accent);
color: #0b1020;
font-weight: 600;
}
#reset {
background: transparent;
color: var(--text);
border: 1px solid #2a3355;
border-radius: 999px;
padding: 0.3rem 0.9rem;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.15s ease;
}
#reset:hover { border-color: var(--accent); color: var(--accent); }
#canvas { display: block; width: 100vw; height: calc(100vh - 53px); }
#hint {
position: fixed;
bottom: 0.8rem;
left: 50%;
transform: translateX(-50%);
color: var(--muted);
font-size: 0.8rem;
background: color-mix(in srgb, var(--panel) 80%, transparent);
padding: 0.35rem 0.9rem;
border-radius: 999px;
pointer-events: none;
white-space: nowrap;
}
#loading {
position: fixed;
inset: 0;
display: grid;
place-items: center;
color: var(--muted);
font-size: 0.95rem;
}
a { color: var(--accent); text-decoration: none; }
.spacer { flex: 1; }
.gh { color: var(--muted); font-size: 0.85rem; }
</style>
</head>
<body>
<header>
<h1><img src="logo.svg" alt="Forge2D logo">Forge2D <span>examples</span></h1>
<nav id="nav"></nav>
<div class="spacer"></div>
<button id="reset" title="Restart the scene">&#8635; Reset</button>
<a class="gh" href="https://github.com/flame-engine/forge2d">GitHub</a>
</header>
<canvas id="canvas"></canvas>
<div id="hint"></div>
<div id="loading">Loading Box2D…</div>
</body>
</html>
15 changes: 15 additions & 0 deletions packages/forge2d/example/web/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading