Dropping balls MVP
This commit is contained in:
parent
33fde737ba
commit
99a306c10b
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef VEC2_H
|
||||
#define VEC2_H
|
||||
|
||||
typedef struct vec2_t {
|
||||
float x, y;
|
||||
} vec2;
|
||||
|
||||
vec2 vec2_add(vec2 a, vec2 b);
|
||||
vec2 vec2_sub(vec2 a, vec2 b);
|
||||
float vec2_dot(vec2 a, vec2 b);
|
||||
vec2 vec2_mul(vec2 x, float f);
|
||||
vec2 vec2_div(vec2 x, float f);
|
||||
float vec2_mag(vec2 x);
|
||||
vec2 vec2_normalize(vec2 x);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#include "vec2.h"
|
||||
#include <math.h>
|
||||
|
||||
vec2 vec2_add(vec2 a, vec2 b) {
|
||||
return (vec2){
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
};
|
||||
}
|
||||
|
||||
vec2 vec2_sub(vec2 a, vec2 b) {
|
||||
return (vec2){
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
};
|
||||
}
|
||||
|
||||
float vec2_dot(vec2 a, vec2 b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
vec2 vec2_mul(vec2 x, float f) {
|
||||
return (vec2){
|
||||
.x = x.x * f,
|
||||
.y = x.y * f,
|
||||
};
|
||||
}
|
||||
|
||||
vec2 vec2_div(vec2 x, float f) {
|
||||
return (vec2){
|
||||
.x = x.x / f,
|
||||
.y = x.y / f,
|
||||
};
|
||||
}
|
||||
|
||||
float vec2_mag(vec2 x) {
|
||||
return sqrtf(x.x * x.x + x.y * x.y);
|
||||
}
|
||||
|
||||
vec2 vec2_normalize(vec2 x) {
|
||||
float len = vec2_mag(x);
|
||||
return vec2_div(x, len);
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
#include <js.h>
|
||||
#include <math.h>
|
||||
#include <memory.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <vec2.h>
|
||||
|
||||
#define TYPE_CIRCLE 1
|
||||
#define TYPE_PLANE 2
|
||||
|
||||
#define MAX_BODIES 1000
|
||||
|
||||
////////// Structs
|
||||
|
||||
typedef struct rigid_body_t {
|
||||
// Public
|
||||
uint8_t type;
|
||||
uint8_t reserved[3];
|
||||
vec2 pos;
|
||||
vec2 vel;
|
||||
float mass;
|
||||
union {
|
||||
struct {
|
||||
float radius;
|
||||
} circle;
|
||||
struct {
|
||||
vec2 normal;
|
||||
} plane;
|
||||
};
|
||||
// Private
|
||||
vec2 force;
|
||||
struct rigid_body_t* prev;
|
||||
struct rigid_body_t* next;
|
||||
} rigid_body;
|
||||
|
||||
////////// Prototypes
|
||||
|
||||
void rigid_body_resolve_collision(rigid_body* rb);
|
||||
int rigid_body_collide(rigid_body* a, rigid_body* b);
|
||||
void rigid_body_handle_collision(rigid_body* a, rigid_body* b);
|
||||
|
||||
////////// Globals
|
||||
|
||||
static rigid_body* rigid_body_head = NULL;
|
||||
static rigid_body* rigid_body_tail = NULL;
|
||||
|
||||
////////// Functions
|
||||
|
||||
rigid_body* rigid_body_new(float x, float y, float vx, float vy, float mass) {
|
||||
rigid_body* rb = malloc(sizeof(rigid_body));
|
||||
memset(rb, 0, sizeof(rigid_body));
|
||||
|
||||
rb->pos.x = x;
|
||||
rb->pos.y = y;
|
||||
|
||||
rb->vel.x = vx;
|
||||
rb->vel.y = vy;
|
||||
|
||||
rb->force.x = 0;
|
||||
rb->force.y = 0;
|
||||
|
||||
rb->mass = mass;
|
||||
rb->prev = NULL;
|
||||
rb->next = NULL;
|
||||
|
||||
if (!rigid_body_head) {
|
||||
rigid_body_head = rb;
|
||||
rigid_body_tail = rb;
|
||||
} else {
|
||||
rigid_body_tail->next = rb;
|
||||
rb->prev = rigid_body_tail;
|
||||
rigid_body_tail = rb;
|
||||
}
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
EXPORT(rigid_body_new_circle) rigid_body* rigid_body_new_circle(float x, float y, float vx, float vy, float mass, float radius) {
|
||||
rigid_body* rb = rigid_body_new(x, y, vx, vy, mass);
|
||||
|
||||
rb->type = TYPE_CIRCLE;
|
||||
rb->circle.radius = radius;
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
EXPORT(rigid_body_free) void rigid_body_free(rigid_body* rb) {
|
||||
if (rb == rigid_body_head) {
|
||||
rigid_body_head = rb->next;
|
||||
}
|
||||
if (rb == rigid_body_tail) {
|
||||
rigid_body_tail = rb->prev;
|
||||
}
|
||||
if (rb->prev) {
|
||||
rb->prev->next = rb->next;
|
||||
}
|
||||
if (rb->next) {
|
||||
rb->next->prev = rb->prev;
|
||||
}
|
||||
|
||||
free(rb);
|
||||
}
|
||||
|
||||
EXPORT(rigid_body_update) void rigid_body_update(rigid_body* rb, float dt) {
|
||||
if (!isinf(rb->mass)) {
|
||||
rigid_body_resolve_collision(rb);
|
||||
|
||||
rb->vel.x += rb->force.x * dt / rb->mass;
|
||||
rb->vel.y += rb->force.y * dt / rb->mass;
|
||||
|
||||
rb->pos.x += rb->vel.x * dt;
|
||||
rb->pos.y += rb->vel.y * dt;
|
||||
}
|
||||
|
||||
rb->force.x = 0;
|
||||
rb->force.y = 0;
|
||||
}
|
||||
|
||||
EXPORT(rigid_body_update_all) void rigid_body_update_all(float dt) {
|
||||
rigid_body* current = rigid_body_head;
|
||||
|
||||
while (current) {
|
||||
rigid_body_update(current, dt);
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT(rigid_body_add_force) void rigid_body_add_force(rigid_body* rb, float fx, float fy) {
|
||||
rb->force.x += fx;
|
||||
rb->force.y += fy;
|
||||
}
|
||||
|
||||
void rigid_body_resolve_collision(rigid_body* rb) {
|
||||
rigid_body* current = rigid_body_head;
|
||||
|
||||
while (current) {
|
||||
if (current != rb && rigid_body_collide(rb, current)) {
|
||||
rigid_body_handle_collision(rb, current);
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
int rigid_body_collide(rigid_body* rb1, rigid_body* rb2) {
|
||||
if (rb1->type == TYPE_CIRCLE && rb2->type == TYPE_CIRCLE) {
|
||||
float dx = rb2->pos.x - rb1->pos.x;
|
||||
float dy = rb2->pos.y - rb1->pos.y;
|
||||
float distance = sqrtf(dx * dx + dy * dy);
|
||||
return distance < rb1->circle.radius + rb2->circle.radius;
|
||||
}
|
||||
|
||||
return 0; // Handle other types later
|
||||
}
|
||||
|
||||
void rigid_body_handle_collision(rigid_body* rb1, rigid_body* rb2) {
|
||||
if (rb1->type == TYPE_CIRCLE && rb2->type == TYPE_CIRCLE) {
|
||||
vec2 d = vec2_sub(rb2->pos, rb1->pos);
|
||||
vec2 n = vec2_normalize(d);
|
||||
float distance = vec2_mag(d);
|
||||
|
||||
float overlap = rb1->circle.radius + rb2->circle.radius - distance;
|
||||
vec2 n_overlap = vec2_mul(n, overlap / 2);
|
||||
|
||||
if (!isinf(rb1->mass)) {
|
||||
rb1->pos = vec2_sub(rb1->pos, n_overlap);
|
||||
}
|
||||
if (!isinf(rb2->mass)) {
|
||||
rb2->pos = vec2_add(rb2->pos, n_overlap);
|
||||
}
|
||||
|
||||
float v1n = vec2_dot(rb1->vel, n);
|
||||
float v2n = vec2_dot(rb2->vel, n);
|
||||
|
||||
float v1n_new = v1n;
|
||||
float v2n_new = v2n;
|
||||
|
||||
if (!isinf(rb1->mass) && !isinf(rb2->mass)) {
|
||||
v1n_new = v1n - 2.0f * (v1n - v2n) * rb1->mass / (rb1->mass + rb2->mass);
|
||||
v2n_new = v2n + 2.0f * (v1n - v2n) * rb2->mass / (rb1->mass + rb2->mass);
|
||||
} else if (isinf(rb1->mass)) {
|
||||
v2n_new = -v2n;
|
||||
} else if (isinf(rb2->mass)) {
|
||||
v1n_new = -v1n;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 v1t = vec2_sub(rb1->vel, vec2_mul(n, v1n));
|
||||
vec2 v2t = vec2_sub(rb2->vel, vec2_mul(n, v2n));
|
||||
|
||||
if (!isinf(rb1->mass)) {
|
||||
rb1->vel = vec2_add(v1t, vec2_mul(n, v1n_new));
|
||||
}
|
||||
if (!isinf(rb2->mass)) {
|
||||
rb2->vel = vec2_add(v2t, vec2_mul(n, v2n_new));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import E from './engine.c';
|
||||
|
||||
namespace Physics {
|
||||
const TYPE_CIRCLE = 1;
|
||||
const TYPE_PLANE = 2;
|
||||
const bodies = new Set<number>();
|
||||
|
||||
export function newCircle(x: number, y: number, radius: number, mass: number = 1.0): number {
|
||||
const circlePtr = E.rigid_body_new_circle(x, y, 0, 0, mass, radius);
|
||||
bodies.add(circlePtr);
|
||||
return circlePtr;
|
||||
}
|
||||
|
||||
export function deleteBody(body: number) {
|
||||
bodies.delete(body);
|
||||
E.rigid_body_free(body);
|
||||
}
|
||||
|
||||
export function addForce(body: number, fx: number, fy: number) {
|
||||
E.rigid_body_add_force(body, fx, fy);
|
||||
}
|
||||
|
||||
export function addGlobalForce(fx: number, fy: number) {
|
||||
for (const body of bodies) {
|
||||
E.rigid_body_add_force(body, fx, fy);
|
||||
}
|
||||
}
|
||||
|
||||
export function update(dt: number) {
|
||||
E.rigid_body_update_all(dt);
|
||||
}
|
||||
|
||||
export function getBody(body: number) {
|
||||
const type = E.data.getUint8(body);
|
||||
|
||||
const x = E.data.getFloat32(body + 4, true);
|
||||
const y = E.data.getFloat32(body + 8, true);
|
||||
|
||||
const vx = E.data.getFloat32(body + 12, true);
|
||||
const vy = E.data.getFloat32(body + 16, true);
|
||||
const mass = E.data.getFloat32(body + 20, true);
|
||||
|
||||
if (type === TYPE_CIRCLE) {
|
||||
const radius = E.data.getFloat32(body + 24, true);
|
||||
return { id: body, x, y, vx, vy, mass, radius };
|
||||
} else if (type === TYPE_PLANE) {
|
||||
const nx = E.data.getFloat32(body + 24, true);
|
||||
const ny = E.data.getFloat32(body + 28, true);
|
||||
return { id: body, x, y, vx, vy, mass, nx, ny };
|
||||
}
|
||||
|
||||
return { id: body, x, y, vx, vy, mass };
|
||||
}
|
||||
|
||||
export function getBodies() {
|
||||
return Array.from(bodies).map(getBody);
|
||||
}
|
||||
}
|
||||
|
||||
export default Physics;
|
||||
|
|
@ -99,7 +99,7 @@ export async function commandInt16(char: BluetoothRemoteGATTCharacteristic, comm
|
|||
await char.writeValue(formatInt16(command, data));
|
||||
}
|
||||
|
||||
async function startPrint(char: BluetoothRemoteGATTCharacteristic, energy = 0xFF00) {
|
||||
async function startPrint(char: BluetoothRemoteGATTCharacteristic) {
|
||||
await commandInt16(char, Command.SetEnergy, 0xFF00);
|
||||
await commandInt8(char, Command.SetQuality, 50);
|
||||
await commandInt16(char, Command.SetSpeed, 32);
|
||||
|
|
@ -113,14 +113,15 @@ async function endPrint(char: BluetoothRemoteGATTCharacteristic) {
|
|||
await commandInt8(char, Command.GetState, 0);
|
||||
}
|
||||
|
||||
export async function printBitmap(char: BluetoothRemoteGATTCharacteristic, bitmap: number[] | Uint8Array, energy = 0xFF00) {
|
||||
await startPrint(char, energy);
|
||||
export async function printBitmap(char: BluetoothRemoteGATTCharacteristic, bitmap: number[] | Uint8Array) {
|
||||
await startPrint(char);
|
||||
for (let i = 0; i < bitmap.length; i += MTU) {
|
||||
const chunk = bitmap.slice(i, i + MTU);
|
||||
await command(char, Command.DrawBitmap, chunk);
|
||||
}
|
||||
await endPrint(char);
|
||||
|
||||
// Wait for print to finish
|
||||
const notify = await char.service.getCharacteristic(NOTIFY_CHARACTERISTIC);
|
||||
await notify.startNotifications();
|
||||
|
||||
|
|
@ -137,4 +138,19 @@ export async function printBitmap(char: BluetoothRemoteGATTCharacteristic, bitma
|
|||
await promise;
|
||||
clearInterval(interval);
|
||||
notify.removeEventListener('characteristicvaluechanged', handler);
|
||||
}
|
||||
|
||||
export async function testPrint() {
|
||||
const device = await navigator.bluetooth.requestDevice({
|
||||
filters: [{ namePrefix: "MX" }],
|
||||
optionalServices: [PRINTER_SERVICE],
|
||||
});
|
||||
|
||||
if (!device?.gatt) return;
|
||||
|
||||
await device.gatt.connect();
|
||||
const srv = await device.gatt.getPrimaryService(PRINTER_SERVICE);
|
||||
const char = await srv.getCharacteristic(PRINT_CHARACTERISTIC);
|
||||
|
||||
await printBitmap(char, Array(PRINTER_WIDTH * 16).fill(0x55));
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { createCanvas } from "@common/display/canvas";
|
||||
import { getRealPoint } from "@common/dom";
|
||||
import { gameLoop } from "@common/game";
|
||||
import Physics from "@common/physics";
|
||||
|
||||
const setup = () => {
|
||||
const canvas = createCanvas(640, 480);
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Failed to get canvas context');
|
||||
}
|
||||
|
||||
canvas.addEventListener('contextmenu', e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
})
|
||||
canvas.addEventListener('mousedown', (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
canvas.addEventListener('mouseup', (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
const { x, y } = getRealPoint(canvas, event);
|
||||
console.log(event.button);
|
||||
const mass = event.button === 0 ? 1 : Number.POSITIVE_INFINITY;
|
||||
const r = event.button === 0 ? 16 : 4;
|
||||
Physics.newCircle(x, y, r, mass);
|
||||
return false;
|
||||
});
|
||||
|
||||
return { canvas, ctx };
|
||||
}
|
||||
|
||||
const frame = (dt: number, state: ReturnType<typeof setup>) => {
|
||||
Physics.addGlobalForce(0, 98);
|
||||
Physics.update(dt);
|
||||
|
||||
const { ctx, canvas } = state;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (const { id, x, y, radius } of Physics.getBodies()) {
|
||||
if (y >= canvas.height) {
|
||||
Physics.deleteBody(id);
|
||||
}
|
||||
ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`;
|
||||
if (radius) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default gameLoop(setup, frame);
|
||||
Loading…
Reference in New Issue