ref: 7ed124593ebda443fc4d0f1439d1fd8dc602ce72
parent: b57dba67153279a84a16bba40ae007c8ae710056
author: rodri <rgl@antares-labs.eu>
date: Sun Aug 1 07:56:12 EDT 2021
began building a universe for every party. get rid of the notes file. i already keep them on paper. adapted the client to use an rframe for drawing. implemented some VModel methods for testing. FPS2MS → HZ2MS. it makes more sense and is shorter.
--- a/assets/mdl/needle.vmdl
+++ b/assets/mdl/needle.vmdl
@@ -1,14 +1,13 @@
# The Needle vector model
-
v 10 0
v 8 -4
+v 8 -4
v -10 -6
+v -10 -6
v -12 0
v -10 6
+v -10 6
v 8 4
-
-l 1 2
-l 2 3
-c 3 4 5
-l 5 6
-l 6 1
+v 8 4
+v 10 0
+llcll
--- a/dat.h
+++ b/dat.h
@@ -34,6 +34,7 @@
typedef struct Derivative Derivative;
typedef struct Conn Conn;
typedef struct Player Player;
+typedef struct PInput PInput;
typedef struct Lobby Lobby;
typedef struct Party Party;
@@ -68,8 +69,8 @@
struct Particle
{
Point2 p, v;
- double yaw;
- double mass;
+ double θ, ω;
+ double mass; /* kg */
};
struct Bullet
@@ -83,6 +84,7 @@
{
Particle;
Kind kind;
+ int fuel;
Bullet rounds[10];
VModel *mdl;
Matrix mdlxform;
@@ -98,8 +100,10 @@
{
Ship ships[2];
Star star;
+ double t, timeacc;
- int (*step)(Universe*);
+ void (*step)(Universe*, double);
+ void (*reset)(Universe*);
};
struct GameState
@@ -119,12 +123,19 @@
char dir[40];
int ctl;
int data;
+ int status;
};
+struct PInput
+{
+ ulong kdown;
+};
+
struct Player
{
char *name;
Conn conn;
+ PInput oldinput, input;
};
struct Lobby
--- a/fns.h
+++ b/fns.h
@@ -1,4 +1,4 @@
-#define FPS2MS(fps) (1000/(fps))
+#define HZ2MS(hz) (1000/(hz))
/*
* alloc
@@ -37,3 +37,9 @@
void delparty(Party*);
void addparty(Party*);
+/*
+ * universe
+ */
+Universe *newuniverse(void);
+void deluniverse(Universe*);
+void inituniverse(Universe*);
--- a/mkfile
+++ b/mkfile
@@ -14,6 +14,7 @@
lobby.$O\
party.$O\
sprite.$O\
+ universe.$O\
HFILES=\
dat.h\
--- a/musw.c
+++ b/musw.c
@@ -1,5 +1,6 @@
#include <u.h>
#include <libc.h>
+#include <bio.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
@@ -27,7 +28,7 @@
{.key = 'y', .op = Ksay},
{.key = 'q', .op = Kquit}
};
-ulong kup, kdown;
+ulong kdown;
typedef struct Ball Ball;
struct Ball
@@ -35,12 +36,117 @@
Point2 p, v;
};
+RFrame screenrf;
Ball bouncer;
+Universe *universe;
+VModel *needlemdl;
char winspec[32];
int debug;
+Point
+toscreen(Point2 p)
+{
+ p = invrframexform(p, screenrf);
+ return Pt(p.x,p.y);
+}
+
+Point2
+fromscreen(Point p)
+{
+ return rframexform(Pt2(p.x,p.y,1), screenrf);
+}
+
+/*
+ * readvmodel and drawship are testing routines
+ * that will later be implemented as VModel methods.
+ */
+VModel *
+readvmodel(char *file)
+{
+ ulong lineno;
+ char *s, *args[2];
+ Biobuf *bin;
+ VModel *mdl;
+
+ bin = Bopen(file, OREAD);
+ if(bin == nil)
+ sysfatal("Bopen: %r");
+
+ mdl = emalloc(sizeof *mdl);
+ mdl->pts = nil;
+ mdl->npts = 0;
+ mdl->strokefmt = nil;
+
+ lineno = 0;
+ while(s = Brdline(bin, '\n')){
+ s[Blinelen(bin)-1] = 0;
+ lineno++;
+
+ switch(*s++){
+ case '#':
+ continue;
+ case 'v':
+ if(tokenize(s, args, nelem(args)) != nelem(args)){
+ werrstr("syntax error: %s:%lud 'v' expects %d args",
+ file, lineno, nelem(args));
+ free(mdl);
+ Bterm(bin);
+ return nil;
+ }
+ mdl->pts = erealloc(mdl->pts, ++mdl->npts*sizeof(Point2));
+ mdl->pts[mdl->npts-1].x = strtod(args[0], nil);
+ mdl->pts[mdl->npts-1].y = strtod(args[1], nil);
+ mdl->pts[mdl->npts-1].w = 1;
+ break;
+ case 'l':
+ case 'c':
+ mdl->strokefmt = strdup(s-1);
+ break;
+ }
+ }
+ Bterm(bin);
+
+ return mdl;
+}
+
void
+drawship(Ship *ship, Image *dst)
+{
+ int i;
+ char *s;
+ Point pts[3];
+ VModel *mdl;
+ Point2 *p;
+ Matrix T = {
+ 1, 0, ship->p.x,
+ 0, 1, ship->p.y,
+ 0, 0, 1
+ }, R = {
+ cos(ship->θ), -sin(ship->θ), 0,
+ sin(ship->θ), cos(ship->θ), 0,
+ 0, 0, 1
+ };
+
+ mulm(T, R);
+ mdl = ship->mdl;
+ p = mdl->pts;
+ for(s = mdl->strokefmt; s != 0 && p-mdl->pts < mdl->npts; s++)
+ switch(*s){
+ case 'l':
+ line(dst, toscreen(xform(p[0], T)), toscreen(xform(p[1], T)), 0, 0, 0, display->white, ZP);
+ p += 2;
+ break;
+ case 'c':
+ for(i = 0; i < nelem(pts); i++)
+ pts[i] = toscreen(xform(p[i], T));
+ bezspline(dst, pts, nelem(pts), 0, 0, 0, display->white, ZP);
+ p += 3;
+ break;
+ }
+}
+
+void
kbdproc(void *)
{
Rune r;
@@ -84,11 +190,10 @@
break;
}
}
- kup = ~kdown;
if(debug)
- fprint(2, "kup %.*lub\nkdown %.*lub\n",
- sizeof(kup)*8, kup, sizeof(kdown)*8, kdown);
+ fprint(2, "kdown %.*lub\n",
+ sizeof(kdown)*8, kdown);
}
}
@@ -95,7 +200,7 @@
void
threadnetrecv(void *arg)
{
- uchar buf[256];
+ uchar buf[1024];
int fd, n;
Ioproc *io;
@@ -103,10 +208,10 @@
io = ioproc();
while((n = ioread(io, fd, buf, sizeof buf)) > 0){
- unpack(buf, n, "PP", &bouncer.p, &bouncer.v);
-
- if(debug)
- fprint(2, "bouncer %v %v\n", bouncer.p, bouncer.v);
+ unpack(buf, n, "PPdPdP", &bouncer.p,
+ &universe->ships[0].p, &universe->ships[0].θ,
+ &universe->ships[1].p, &universe->ships[1].θ,
+ &universe->star.p);
}
closeioproc(io);
}
@@ -135,8 +240,12 @@
lockdisplay(display);
draw(screen, screen->r, display->black, nil, ZP);
- fillellipse(screen, addpt(screen->r.min,Pt(Dx(screen->r)/2,Dy(screen->r)/2+bouncer.p.y)), 2, 2, display->white, ZP);
+ fillellipse(screen, toscreen(bouncer.p), 2, 2, display->white, ZP);
+ drawship(&universe->ships[0], screen);
+ drawship(&universe->ships[1], screen);
+ fillellipse(screen, toscreen(universe->star.p), 4, 4, display->white, ZP);
+
flushimage(display, 1);
unlockdisplay(display);
}
@@ -154,6 +263,8 @@
sysfatal("resize failed");
unlockdisplay(display);
+ screenrf.p = Pt2(screen->r.min.x+Dx(screen->r)/2,screen->r.max.y-Dy(screen->r)/2,1);
+
/* ignore move events */
if(Dx(screen->r) != SCRW || Dy(screen->r) != SCRH){
fd = open("/dev/wctl", OWRITE);
@@ -203,6 +314,10 @@
display->locking = 1;
unlockdisplay(display);
+ screenrf.p = Pt2(screen->r.min.x+Dx(screen->r)/2,screen->r.max.y-Dy(screen->r)/2,1);
+ screenrf.bx = Vec2(1, 0);
+ screenrf.by = Vec2(0,-1);
+
proccreate(kbdproc, nil, 4096);
fd = dial(server, nil, nil, nil);
@@ -209,6 +324,13 @@
if(fd < 0)
sysfatal("dial: %r");
+ universe = newuniverse();
+ needlemdl = readvmodel("assets/mdl/needle.vmdl");
+ if(needlemdl == nil)
+ sysfatal("readvmodel: %r");
+ universe->ships[0].mdl = needlemdl;
+ universe->ships[1].mdl = needlemdl;
+
threadcreate(threadnetrecv, &fd, 4096);
threadcreate(threadresize, mc, 4096);
@@ -215,6 +337,6 @@
io = ioproc();
for(;;){
redraw();
- iosleep(io, FPS2MS(30));
+ iosleep(io, HZ2MS(30));
}
}
--- a/muswd.c
+++ b/muswd.c
@@ -66,20 +66,24 @@
broadcaststate(void)
{
int i, n;
- uchar buf[256];
+ uchar buf[1024];
Player *player;
- Party *p, *np;
+ Party *p;
for(p = theparty.next; p != &theparty; p = p->next){
- n = pack(buf, sizeof buf, "PP", p->state.p, p->state.v);
+ n = pack(buf, sizeof buf, "PPdPdP",
+ p->state.p,
+ p->u->ships[0].p, p->u->ships[0].θ,
+ p->u->ships[1].p, p->u->ships[1].θ,
+ p->u->star.p);
for(i = 0; i < nelem(p->players); i++){
if(write(p->players[i].conn.data, buf, n) != n){
player = &p->players[i^1];
lobby->takeseat(lobby, player->conn.dir, player->conn.ctl, player->conn.data);
- np = p->prev;
- delparty(p);
- p = np;
+ /* step back and delete the spoiled party */
+ p = p->prev;
+ delparty(p->next);
break;
}
}
@@ -112,7 +116,8 @@
if(lobby->getcouple(lobby, couple) != -1){
newparty(couple);
- resetsim(theparty.prev); /* reset the new party */
+ resetsim(theparty.prev);
+ theparty.prev->u->reset(theparty.prev->u);
}
now = nanosec();
@@ -120,8 +125,15 @@
then = now;
for(p = theparty.next; p != &theparty; p = p->next){
+ p->u->timeacc += frametime/1e9;
p->state.timeacc += frametime/1e9;
+ while(p->u->timeacc >= Δt){
+ p->u->step(p->u, Δt);
+ p->u->timeacc -= Δt;
+ p->u->t += Δt;
+ }
+
while(p->state.timeacc >= Δt){
integrate(&p->state, p->state.t, Δt);
p->state.timeacc -= Δt;
@@ -131,7 +143,7 @@
broadcaststate();
- iosleep(io, FPS2MS(70));
+ iosleep(io, HZ2MS(70));
}
}
@@ -158,10 +170,17 @@
{
ulong i = 0;
Party *p;
+ Ship *s;
- for(p = theparty.next; p != &theparty; p = p->next, i++)
- fprint(fd, "%lud [p %v v %v]\n",
+ for(p = theparty.next; p != &theparty; p = p->next, i++){
+ fprint(fd, "%lud p %v v %v\n",
i, p->state.p, p->state.v);
+ for(s = &p->u->ships[0]; s-p->u->ships < nelem(p->u->ships); s++){
+ fprint(fd, "%lud s%lld k%d p %v v %v θ %g ω %g m %g f %d\n",
+ i, s-p->u->ships, s->kind, s->p, s->v, s->θ, s->ω, s->mass, s->fuel);
+ }
+ fprint(fd, "%lud S p %v m %g\n", i, p->u->star.p, p->u->star.mass);
+ }
}
--- a/notes
+++ /dev/null
@@ -1,17 +1,0 @@
-• there's, at most, two players waiting in the lobby at a given time.
-it makes no sense to allocate more than two seats since the threadsim
-will consume them whenever they are ready to join the party. i'm
-thinking of using channels to synchronize the two threads, so
-threadsim doesn't loop doing nothing (but sleeping) until at least a
-couple of players join.
-
-• the integrator has to operate with vectors and the different objects
-in the universe, some of which may require their own governing laws.
-
-• think of a way to pack the bullets efficiently. will they be part
-of the global state broadcast? what does the client need to know to
-render and manage them?
-
-• it could be beneficial to do dynamics in the client as well, which
-means sending more data, and probably require a tighter sync, but a
-smoother user experience.
--- a/party.c
+++ b/party.c
@@ -23,6 +23,8 @@
p->players[0] = players[0];
p->players[1] = players[1];
+ p->u = newuniverse();
+
addparty(p);
return p;
@@ -33,6 +35,7 @@
{
p->next->prev = p->prev;
p->prev->next = p->next;
+ deluniverse(p->u);
free(p);
}
--- a/physics.c
+++ b/physics.c
@@ -18,6 +18,25 @@
return Vec2(0, -k*s->p.y - b*s->v.y);
}
+/*
+ * XXX: remember to take thrust into account, based on user input.
+ */
+static Point2
+accelship(Universe *u, Ship *s, double)
+{
+ double g, d;
+
+ d = vec2len(subpt2(u->star.p, s->p));
+ g = G*u->star.mass/(d*d);
+ return mulpt2(normvec2(subpt2(u->star.p, s->p)), g);
+}
+
+static Point2
+accelbullet(Universe *, Bullet *, double)
+{
+ return Vec2(0,0);
+}
+
static Derivative
eval(GameState *s0, double t, double Δt, Derivative *d)
{
--- /dev/null
+++ b/universe.c
@@ -1,0 +1,63 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "libgeometry/geometry.h"
+#include "dat.h"
+#include "fns.h"
+
+static void
+universe_step(Universe *u, double Δt)
+{
+ //integrate(u, u->t, Δt);
+}
+
+static void
+universe_reset(Universe *u)
+{
+ int i, j;
+
+ for(i = 0; i < nelem(u->ships); i++){
+ for(j = 0; j < nelem(u->ships[i].rounds); j++)
+ memset(&u->ships[i].rounds[j], 0, sizeof(Bullet));
+ memset(&u->ships[i].Particle, 0, sizeof(Particle));
+ }
+ memset(&u->star.Particle, 0, sizeof(Particle));
+ inituniverse(u);
+}
+
+void
+inituniverse(Universe *u)
+{
+ u->ships[0].p = Pt2(SCRW/2-50,SCRH/2-50,1);
+ u->ships[0].θ = (180+45)*DEG;
+ u->ships[0].mass = 10e3; /* 10 tons */
+ u->ships[0].kind = NEEDLE;
+ u->ships[0].fuel = 100;
+
+ u->ships[1].p = Pt2(-SCRW/2+50,-SCRH/2+50,1);
+ u->ships[1].θ = 45*DEG;
+ u->ships[1].mass = 40e3; /* 40 tons */
+ u->ships[1].kind = WEDGE;
+ u->ships[1].fuel = 200;
+
+ u->star.p = Pt2(0,0,1);
+ u->star.mass = 5.97e24; /* earth's mass */
+}
+
+Universe *
+newuniverse(void)
+{
+ Universe *u;
+
+ u = emalloc(sizeof(Universe));
+ memset(u, 0, sizeof *u);
+ u->step = universe_step;
+ u->reset = universe_reset;
+ return u;
+}
+
+void
+deluniverse(Universe *u)
+{
+ free(u);
+}