2021-11-30 21:29:18 -06:00
|
|
|
#include "2dphysics.h"
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
#include "debug.h"
|
2021-11-30 21:29:18 -06:00
|
|
|
#include "gameobject.h"
|
2023-05-12 13:22:05 -05:00
|
|
|
#include <string.h>
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2022-08-17 00:01:51 -05:00
|
|
|
#include "debugdraw.h"
|
2022-12-20 08:16:26 -06:00
|
|
|
#include "stb_ds.h"
|
2023-01-25 21:32:58 -06:00
|
|
|
#include <assert.h>
|
2023-05-12 13:22:05 -05:00
|
|
|
#include <chipmunk/chipmunk_unsafe.h>
|
|
|
|
#include <math.h>
|
2022-12-20 08:16:26 -06:00
|
|
|
|
2023-04-28 20:55:24 -05:00
|
|
|
#include "2dphysics.h"
|
|
|
|
|
2023-11-03 22:01:30 -05:00
|
|
|
#include "jsffi.h"
|
2023-05-12 13:22:05 -05:00
|
|
|
#include "script.h"
|
2022-12-26 20:57:45 -06:00
|
|
|
|
2022-12-20 08:16:26 -06:00
|
|
|
#include "log.h"
|
2022-08-17 00:01:51 -05:00
|
|
|
|
2021-11-30 21:29:18 -06:00
|
|
|
cpSpace *space = NULL;
|
|
|
|
|
2023-05-16 01:31:13 -05:00
|
|
|
struct rgba color_white = {255,255,255,255};
|
|
|
|
struct rgba color_black = {0,0,0,255};
|
|
|
|
|
2023-05-24 20:45:50 -05:00
|
|
|
struct rgba disabled_color = {148,148,148,255};
|
|
|
|
struct rgba sleep_color = {255,140,228,255};
|
|
|
|
struct rgba dynamic_color = {255,70,46,255};
|
|
|
|
struct rgba kinematic_color = {255, 194, 64, 255};
|
|
|
|
struct rgba static_color = {73,209,80,255};
|
|
|
|
|
|
|
|
static const unsigned char col_alpha = 40;
|
2023-05-25 21:55:55 -05:00
|
|
|
static const float sensor_seg = 10;
|
2023-01-18 10:45:43 -06:00
|
|
|
|
2023-03-10 13:13:48 -06:00
|
|
|
unsigned int category_masks[32];
|
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
void set_cat_mask(int cat, unsigned int mask) { category_masks[cat] = mask; }
|
2023-03-10 13:13:48 -06:00
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
cpTransform m3_to_cpt(HMM_Mat3 m)
|
|
|
|
{
|
|
|
|
cpTransform t;
|
|
|
|
t.a = m.Columns[0].x;
|
2023-12-19 15:34:36 -06:00
|
|
|
t.b = m.Columns[0].y;
|
|
|
|
t.tx = m.Columns[2].x;
|
|
|
|
t.c = m.Columns[1].x;
|
2023-12-04 13:38:37 -06:00
|
|
|
t.d = m.Columns[1].y;
|
2023-12-19 15:34:36 -06:00
|
|
|
t.ty = m.Columns[2].y;
|
2023-12-04 13:38:37 -06:00
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
cpShape *phys2d_query_pos(cpVect pos) {
|
2023-02-02 17:52:15 -06:00
|
|
|
cpShapeFilter filter;
|
2023-03-17 10:25:35 -05:00
|
|
|
filter.group = CP_NO_GROUP;
|
2023-02-02 17:52:15 -06:00
|
|
|
filter.mask = CP_ALL_CATEGORIES;
|
|
|
|
filter.categories = CP_ALL_CATEGORIES;
|
|
|
|
cpShape *find = cpSpacePointQueryNearest(space, pos, 0.f, filter, NULL);
|
|
|
|
|
|
|
|
return find;
|
|
|
|
}
|
|
|
|
|
2023-12-12 08:46:27 -06:00
|
|
|
int p_compare(void *a, void *b)
|
|
|
|
{
|
|
|
|
if (a > b) return 1;
|
|
|
|
if (a < b) return -1;
|
2023-12-21 17:21:01 -06:00
|
|
|
return 0;
|
2023-12-12 08:46:27 -06:00
|
|
|
}
|
|
|
|
|
2023-12-11 16:59:59 -06:00
|
|
|
gameobject **clean_ids(gameobject **ids)
|
2023-12-04 13:38:37 -06:00
|
|
|
{
|
2023-12-12 08:46:27 -06:00
|
|
|
qsort(ids, arrlen(ids), sizeof(*ids), p_compare);
|
2023-12-11 16:59:59 -06:00
|
|
|
gameobject *curid = NULL;
|
2023-12-04 13:38:37 -06:00
|
|
|
for (int i = arrlen(ids)-1; i >= 0; i--)
|
|
|
|
if (ids[i] == curid)
|
|
|
|
arrdelswap(ids, i);
|
|
|
|
else
|
|
|
|
curid = ids[i];
|
|
|
|
|
|
|
|
return ids;
|
2023-02-05 17:42:36 -06:00
|
|
|
}
|
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
typedef struct querybox {
|
|
|
|
cpBB bb;
|
2023-12-11 08:36:45 -06:00
|
|
|
gameobject **ids;
|
2023-12-04 13:38:37 -06:00
|
|
|
} querybox;
|
2023-05-12 13:22:05 -05:00
|
|
|
|
2023-12-22 11:50:03 -06:00
|
|
|
void querylist(cpShape *shape, cpContactPointSet *points, querybox *qb) {
|
|
|
|
arrput(qb->ids, shape2go(shape));
|
|
|
|
}
|
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
void querylistbodies(cpBody *body, querybox *qb) {
|
|
|
|
if (cpBBContainsVect(qb->bb, cpBodyGetPosition(body)))
|
2023-12-11 08:36:45 -06:00
|
|
|
arrput(qb->ids,body2go(body));
|
2023-02-05 17:42:36 -06:00
|
|
|
}
|
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
/* Return all points from a list of points in the given boundingbox */
|
2023-11-14 09:20:09 -06:00
|
|
|
int *phys2d_query_box_points(HMM_Vec2 pos, HMM_Vec2 wh, HMM_Vec2 *points, int n) {
|
2023-12-04 13:38:37 -06:00
|
|
|
cpBB bbox;
|
|
|
|
bbox = cpBBExpand(bbox, cpvadd(pos.cp, cpvmult(wh.cp,0.5)));
|
|
|
|
bbox = cpBBExpand(bbox, cpvsub(pos.cp, cpvmult(wh.cp,0.5)));
|
|
|
|
int *hits = NULL;
|
2023-05-12 13:22:05 -05:00
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
for (int i = 0; i < n; i++)
|
2023-11-14 09:20:09 -06:00
|
|
|
if (cpBBContainsVect(bbox, points[i].cp))
|
2023-12-04 13:38:37 -06:00
|
|
|
arrpush(hits, i);
|
2023-10-03 17:16:38 -05:00
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
return hits;
|
2023-03-19 20:33:05 -05:00
|
|
|
}
|
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
/* Return all gameobjects within the given box */
|
2023-12-21 17:21:01 -06:00
|
|
|
gameobject **phys2d_query_box(HMM_Vec2 pos, HMM_Vec2 wh) {
|
2023-02-06 16:41:47 -06:00
|
|
|
cpShape *box = cpBoxShapeNew(NULL, wh.x, wh.y, 0.f);
|
|
|
|
cpTransform T = {0};
|
|
|
|
T.a = 1;
|
|
|
|
T.d = 1;
|
|
|
|
T.tx = pos.x;
|
|
|
|
T.ty = pos.y;
|
|
|
|
cpShapeUpdate(box, T);
|
2023-05-12 13:22:05 -05:00
|
|
|
|
2023-02-06 16:41:47 -06:00
|
|
|
cpBB bbox = cpShapeGetBB(box);
|
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
querybox qb;
|
|
|
|
qb.bb = bbox;
|
2023-12-12 08:46:27 -06:00
|
|
|
qb.ids = NULL;
|
2023-05-12 13:22:05 -05:00
|
|
|
|
2023-12-22 11:50:03 -06:00
|
|
|
cpSpaceShapeQuery(space, box, querylist, &qb);
|
2023-12-04 13:38:37 -06:00
|
|
|
cpSpaceEachBody(space, querylistbodies, &qb);
|
2023-05-12 13:22:05 -05:00
|
|
|
|
2023-02-05 17:42:36 -06:00
|
|
|
cpShapeFree(box);
|
2023-05-12 13:22:05 -05:00
|
|
|
|
2023-12-12 08:46:27 -06:00
|
|
|
return clean_ids(qb.ids);
|
2023-02-05 17:42:36 -06:00
|
|
|
}
|
|
|
|
|
2023-12-21 17:21:01 -06:00
|
|
|
gameobject **phys2d_query_shape(struct phys2d_shape *shape)
|
2023-12-12 08:46:27 -06:00
|
|
|
{
|
2023-12-11 08:36:45 -06:00
|
|
|
gameobject **ids = NULL;
|
2023-12-04 13:38:37 -06:00
|
|
|
cpSpaceShapeQuery(space, shape->shape, querylist, ids);
|
|
|
|
return clean_ids(ids);
|
2023-03-13 09:27:32 -05:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
int cpshape_enabled(cpShape *c) {
|
|
|
|
cpShapeFilter filter = cpShapeGetFilter(c);
|
|
|
|
if (filter.categories == ~CP_ALL_CATEGORIES && filter.mask == ~CP_ALL_CATEGORIES)
|
|
|
|
return 0;
|
2023-01-18 08:45:42 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
return 1;
|
2023-01-18 10:45:43 -06:00
|
|
|
}
|
2023-01-18 08:45:42 -06:00
|
|
|
|
2023-05-24 20:45:50 -05:00
|
|
|
struct rgba shape_color(cpShape *shape) {
|
2023-10-31 12:38:23 -05:00
|
|
|
if (!cpshape_enabled(shape)) return disabled_color;
|
2023-05-12 13:22:05 -05:00
|
|
|
switch (cpBodyGetType(cpShapeGetBody(shape))) {
|
|
|
|
case CP_BODY_TYPE_DYNAMIC:
|
2023-05-24 20:45:50 -05:00
|
|
|
// cpBodySleep(cpShapeGetBody(shape));
|
|
|
|
if (cpBodyIsSleeping(cpShapeGetBody(shape)))
|
|
|
|
return sleep_color;
|
2023-05-12 13:22:05 -05:00
|
|
|
return dynamic_color;
|
2023-01-18 08:45:42 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
case CP_BODY_TYPE_KINEMATIC:
|
|
|
|
return kinematic_color;
|
2023-01-18 08:45:42 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
case CP_BODY_TYPE_STATIC:
|
2023-01-18 10:45:43 -06:00
|
|
|
return static_color;
|
2023-05-12 13:22:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return static_color;
|
2023-01-18 08:45:42 -06:00
|
|
|
}
|
|
|
|
|
2023-05-24 20:45:50 -05:00
|
|
|
void phys2d_init()
|
|
|
|
{
|
2023-05-12 13:22:05 -05:00
|
|
|
space = cpSpaceNew();
|
2023-05-24 20:45:50 -05:00
|
|
|
cpSpaceSetSleepTimeThreshold(space, 1);
|
2023-05-29 10:47:30 -05:00
|
|
|
cpSpaceSetCollisionSlop(space, 0.01);
|
|
|
|
cpSpaceSetCollisionBias(space, cpfpow(1.0-0.5, 165.f));
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-01-10 17:23:11 -06:00
|
|
|
void phys2d_set_gravity(cpVect v) {
|
2023-05-12 13:22:05 -05:00
|
|
|
cpSpaceSetGravity(space, v);
|
2022-12-22 03:50:40 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_update(float deltaT) {
|
|
|
|
cpSpaceStep(space, deltaT);
|
2023-05-27 10:13:20 -05:00
|
|
|
flush_collide_cbs();
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
void init_phys2dshape(struct phys2d_shape *shape, gameobject *go, void *data) {
|
2023-05-12 13:22:05 -05:00
|
|
|
shape->go = go;
|
|
|
|
shape->data = data;
|
2023-12-19 15:34:36 -06:00
|
|
|
shape->t.scale = (HMM_Vec2){1.0,1.0};
|
2023-12-11 08:36:45 -06:00
|
|
|
go_shape_apply(go->body, shape->shape, go);
|
2023-12-21 17:21:01 -06:00
|
|
|
cpShapeSetCollisionType(shape->shape, (cpCollisionType)go);
|
2023-05-12 13:22:05 -05:00
|
|
|
cpShapeSetUserData(shape->shape, shape);
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_shape_del(struct phys2d_shape *shape) {
|
|
|
|
if (!shape->shape) return;
|
|
|
|
cpSpaceRemoveShape(space, shape->shape);
|
|
|
|
cpShapeFree(shape->shape);
|
2022-08-28 22:34:33 -05:00
|
|
|
}
|
|
|
|
|
2023-01-16 02:16:39 -06:00
|
|
|
/***************** CIRCLE2D *****************/
|
2023-12-11 08:36:45 -06:00
|
|
|
struct phys2d_circle *Make2DCircle(gameobject *go) {
|
2023-05-12 13:22:05 -05:00
|
|
|
struct phys2d_circle *new = malloc(sizeof(struct phys2d_circle));
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
new->radius = 10.f;
|
2023-11-11 20:01:42 -06:00
|
|
|
new->offset = v2zero;
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
new->shape.shape = cpSpaceAddShape(space, cpCircleShapeNew(go->body, new->radius, cpvzero));
|
2023-05-12 13:22:05 -05:00
|
|
|
new->shape.debugdraw = phys2d_dbgdrawcircle;
|
|
|
|
new->shape.moi = phys2d_circle_moi;
|
2023-09-26 17:07:51 -05:00
|
|
|
new->shape.apply = phys2d_applycircle;
|
2023-12-22 11:50:03 -06:00
|
|
|
new->shape.free = NULL;
|
2023-05-12 13:22:05 -05:00
|
|
|
init_phys2dshape(&new->shape, go, new);
|
2023-09-27 09:37:20 -05:00
|
|
|
phys2d_applycircle(new);
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
return new;
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
float phys2d_circle_moi(struct phys2d_circle *c, float m) {
|
2023-11-11 20:01:42 -06:00
|
|
|
return 1;
|
|
|
|
//TODO: Calculate correctly
|
|
|
|
//return cpMomentForCircle(m, 0, c->radius, c->offset);
|
2023-02-24 12:11:36 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_circledel(struct phys2d_circle *c) {
|
|
|
|
phys2d_shape_del(&c->shape);
|
2022-08-28 22:34:33 -05:00
|
|
|
}
|
|
|
|
|
2023-12-21 17:21:01 -06:00
|
|
|
void phys2d_dbgdrawcpcirc(cpShape *c) {
|
2023-11-30 10:47:59 -06:00
|
|
|
HMM_Vec2 pos = mat_t_pos(t_go2world(shape2go(c)), (HMM_Vec2)cpCircleShapeGetOffset(c));
|
2023-05-12 13:22:05 -05:00
|
|
|
float radius = cpCircleShapeGetRadius(c);
|
2023-05-24 20:45:50 -05:00
|
|
|
struct rgba color = shape_color(c);
|
|
|
|
float seglen = cpShapeGetSensor(c) ? 5 : -1;
|
2023-11-14 09:20:09 -06:00
|
|
|
draw_circle(pos, radius, 1, color, seglen);
|
2023-05-24 20:45:50 -05:00
|
|
|
color.a = col_alpha;
|
2023-11-14 09:20:09 -06:00
|
|
|
draw_circle(pos,radius,radius,color,-1);
|
2023-01-03 09:06:36 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_dbgdrawcircle(struct phys2d_circle *circle) {
|
2023-12-21 17:21:01 -06:00
|
|
|
phys2d_dbgdrawcpcirc(circle->shape.shape);
|
2022-08-17 00:01:51 -05:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_applycircle(struct phys2d_circle *circle) {
|
2023-12-11 08:36:45 -06:00
|
|
|
gameobject *go = circle->shape.go;
|
2023-11-14 09:20:09 -06:00
|
|
|
float radius = circle->radius * HMM_MAX(HMM_ABS(go->scale.X), HMM_ABS(go->scale.Y));
|
2023-05-12 13:22:05 -05:00
|
|
|
cpCircleShapeSetRadius(circle->shape.shape, radius);
|
2023-12-24 11:50:01 -06:00
|
|
|
cpCircleShapeSetOffset(circle->shape.shape, circle->offset.cp);
|
2022-08-12 14:03:56 -05:00
|
|
|
}
|
|
|
|
|
2023-01-12 17:41:54 -06:00
|
|
|
/************** POLYGON ************/
|
2022-08-12 14:03:56 -05:00
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
struct phys2d_poly *Make2DPoly(gameobject *go) {
|
2023-05-12 13:22:05 -05:00
|
|
|
struct phys2d_poly *new = malloc(sizeof(struct phys2d_poly));
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
new->points = NULL;
|
|
|
|
arrsetlen(new->points, 0);
|
|
|
|
new->radius = 0.f;
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-12-21 17:21:01 -06:00
|
|
|
new->shape.shape = cpSpaceAddShape(space, cpPolyShapeNewRaw(go->body, 0, (cpVect*)new->points, new->radius));
|
2023-05-12 13:22:05 -05:00
|
|
|
new->shape.debugdraw = phys2d_dbgdrawpoly;
|
|
|
|
new->shape.moi = phys2d_poly_moi;
|
2023-12-22 11:50:03 -06:00
|
|
|
new->shape.free = phys2d_poly_free;
|
2023-09-27 12:36:32 -05:00
|
|
|
new->shape.apply = phys2d_applypoly;
|
2023-05-12 13:22:05 -05:00
|
|
|
init_phys2dshape(&new->shape, go, new);
|
|
|
|
return new;
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-12-22 11:50:03 -06:00
|
|
|
void phys2d_poly_free(struct phys2d_poly *poly)
|
|
|
|
{
|
|
|
|
arrfree(poly->points);
|
|
|
|
free(poly);
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
float phys2d_poly_moi(struct phys2d_poly *poly, float m) {
|
2023-12-21 17:21:01 -06:00
|
|
|
float moi = cpMomentForPoly(m, arrlen(poly->points), (cpVect*)poly->points, cpvzero, poly->radius);
|
2023-05-24 20:45:50 -05:00
|
|
|
if (isnan(moi)) {
|
|
|
|
// YughError("Polygon MOI returned an error. Returning 0.");
|
|
|
|
return 0;
|
2023-02-27 08:50:36 -06:00
|
|
|
}
|
2023-05-12 13:22:05 -05:00
|
|
|
|
2023-02-27 08:50:36 -06:00
|
|
|
return moi;
|
2023-02-24 12:11:36 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_polydel(struct phys2d_poly *poly) {
|
|
|
|
arrfree(poly->points);
|
|
|
|
phys2d_shape_del(&poly->shape);
|
2022-08-28 22:34:33 -05:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_polyaddvert(struct phys2d_poly *poly) {
|
2023-11-11 20:01:42 -06:00
|
|
|
arrput(poly->points, v2zero);
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-12-21 17:21:01 -06:00
|
|
|
void phys2d_poly_setverts(struct phys2d_poly *poly, HMM_Vec2 *verts) {
|
2023-05-12 13:22:05 -05:00
|
|
|
if (!verts) return;
|
2023-10-05 17:30:17 -05:00
|
|
|
if (poly->points)
|
|
|
|
arrfree(poly->points);
|
2023-12-22 11:50:03 -06:00
|
|
|
|
2023-10-05 17:30:17 -05:00
|
|
|
arrsetlen(poly->points, arrlen(verts));
|
2023-12-19 15:34:36 -06:00
|
|
|
|
2023-12-21 17:21:01 -06:00
|
|
|
for (int i = 0; i < arrlen(verts); i++)
|
|
|
|
poly->points[i] = verts[i];
|
2023-10-05 17:30:17 -05:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
phys2d_applypoly(poly);
|
2023-01-25 21:32:58 -06:00
|
|
|
}
|
2022-08-12 14:03:56 -05:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_applypoly(struct phys2d_poly *poly) {
|
|
|
|
if (arrlen(poly->points) <= 0) return;
|
2023-12-19 15:34:36 -06:00
|
|
|
assert(sizeof(poly->points[0]) == sizeof(cpVect));
|
2023-12-11 08:36:45 -06:00
|
|
|
struct gameobject *go = poly->shape.go;
|
2023-12-27 07:04:18 -06:00
|
|
|
transform2d t = go2t(shape2go(poly->shape.shape));
|
|
|
|
t.pos.cp = cpvzero;
|
|
|
|
t.angle = 0;
|
|
|
|
cpTransform T = m3_to_cpt(transform2d2mat(t));
|
2023-12-26 15:39:46 -06:00
|
|
|
cpPolyShapeSetVerts(poly->shape.shape, arrlen(poly->points), (cpVect*)poly->points, T);
|
2023-05-12 13:22:05 -05:00
|
|
|
cpPolyShapeSetRadius(poly->shape.shape, poly->radius);
|
|
|
|
cpSpaceReindexShapesForBody(space, cpShapeGetBody(poly->shape.shape));
|
|
|
|
}
|
2023-12-27 07:04:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_dbgdrawpoly(struct phys2d_poly *poly) {
|
2023-05-16 01:31:13 -05:00
|
|
|
struct rgba color = shape_color(poly->shape.shape);
|
2023-05-24 20:45:50 -05:00
|
|
|
struct rgba line_color = color;
|
|
|
|
color.a = col_alpha;
|
2023-01-12 17:41:54 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
if (arrlen(poly->points) >= 3) {
|
|
|
|
int n = cpPolyShapeGetCount(poly->shape.shape);
|
2023-12-19 17:28:45 -06:00
|
|
|
HMM_Vec2 points[n+1];
|
2023-12-27 07:04:18 -06:00
|
|
|
transform2d t = go2t(shape2go(poly->shape.shape));
|
|
|
|
t.scale = (HMM_Vec2){1,1};
|
|
|
|
HMM_Mat3 rt = transform2d2mat(t);
|
2023-05-12 13:22:05 -05:00
|
|
|
for (int i = 0; i < n; i++)
|
2023-12-27 07:04:18 -06:00
|
|
|
points[i] = mat_t_pos(rt, (HMM_Vec2)cpPolyShapeGetVert(poly->shape.shape, i));
|
2023-01-25 21:32:58 -06:00
|
|
|
|
2023-12-19 17:28:45 -06:00
|
|
|
points[n] = points[0];
|
|
|
|
|
2023-05-25 21:55:55 -05:00
|
|
|
draw_poly(points, n, color);
|
|
|
|
float seglen = cpShapeGetSensor(poly->shape.shape) ? sensor_seg : 0;
|
2023-12-19 17:28:45 -06:00
|
|
|
draw_line(points, n, line_color, seglen, 0);
|
2023-05-12 13:22:05 -05:00
|
|
|
}
|
2023-01-25 21:32:58 -06:00
|
|
|
}
|
2023-01-12 17:41:54 -06:00
|
|
|
/****************** EDGE 2D**************/
|
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
struct phys2d_edge *Make2DEdge(gameobject *go) {
|
2023-05-12 13:22:05 -05:00
|
|
|
struct phys2d_edge *new = malloc(sizeof(struct phys2d_edge));
|
|
|
|
new->points = NULL;
|
|
|
|
arrsetlen(new->points, 0);
|
|
|
|
new->thickness = 0.f;
|
|
|
|
new->shapes = NULL;
|
|
|
|
arrsetlen(new->shapes, 0);
|
|
|
|
new->shape.go = go;
|
|
|
|
new->shape.data = new;
|
|
|
|
new->shape.debugdraw = phys2d_dbgdrawedge;
|
|
|
|
new->shape.moi = phys2d_edge_moi;
|
|
|
|
new->shape.shape = NULL;
|
2023-09-27 17:40:04 -05:00
|
|
|
new->shape.apply = NULL;
|
2023-12-22 11:50:03 -06:00
|
|
|
new->shape.free = phys2d_edge_free;
|
2023-05-24 20:45:50 -05:00
|
|
|
new->draws = 0;
|
2023-05-12 13:22:05 -05:00
|
|
|
phys2d_applyedge(new);
|
|
|
|
|
|
|
|
return new;
|
|
|
|
}
|
|
|
|
|
2023-12-22 11:50:03 -06:00
|
|
|
void phys2d_edge_free(struct phys2d_edge *edge)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < arrlen(edge->shapes); i++)
|
|
|
|
cpShapeSetUserData(edge->shapes[i], NULL);
|
|
|
|
arrfree(edge->points);
|
|
|
|
arrfree(edge->shapes);
|
|
|
|
free(edge);
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
float phys2d_edge_moi(struct phys2d_edge *edge, float m) {
|
2023-02-28 09:40:53 -06:00
|
|
|
float moi = 0;
|
2023-05-12 13:22:05 -05:00
|
|
|
for (int i = 0; i < arrlen(edge->points) - 1; i++)
|
2023-11-11 20:01:42 -06:00
|
|
|
moi += cpMomentForSegment(m, edge->points[i].cp, edge->points[i + 1].cp, edge->thickness);
|
2023-02-28 09:40:53 -06:00
|
|
|
|
|
|
|
return moi;
|
2023-02-24 12:11:36 -06:00
|
|
|
}
|
|
|
|
|
2023-12-19 15:34:36 -06:00
|
|
|
void phys2d_edgedel(struct phys2d_edge *edge) { phys2d_shape_del(&edge->shape); }
|
2022-08-28 22:34:33 -05:00
|
|
|
|
2023-12-12 19:35:34 -06:00
|
|
|
void phys2d_edgeaddvert(struct phys2d_edge *edge, HMM_Vec2 v) {
|
|
|
|
arrput(edge->points, v);
|
2023-05-12 13:22:05 -05:00
|
|
|
if (arrlen(edge->points) > 1)
|
2023-12-11 08:36:45 -06:00
|
|
|
arrput(edge->shapes, cpSpaceAddShape(space, cpSegmentShapeNew(edge->shape.go->body, cpvzero, cpvzero, edge->thickness)));
|
2022-08-12 14:03:56 -05:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_edge_rmvert(struct phys2d_edge *edge, int index) {
|
2023-12-12 19:35:34 -06:00
|
|
|
if (index>arrlen(edge->points) || index < 0) return;
|
2023-02-08 15:30:12 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
arrdel(edge->points, index);
|
2023-02-08 15:30:12 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
if (arrlen(edge->points) == 0) return;
|
2023-02-08 15:30:12 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
if (index == 0) {
|
|
|
|
cpSpaceRemoveShape(space, edge->shapes[index]);
|
|
|
|
cpShapeFree(edge->shapes[index]);
|
|
|
|
arrdel(edge->shapes, index);
|
|
|
|
return;
|
|
|
|
}
|
2023-02-08 15:30:12 -06:00
|
|
|
|
2023-12-12 19:35:34 -06:00
|
|
|
if (index != arrlen(edge->points))
|
2023-11-11 20:01:42 -06:00
|
|
|
cpSegmentShapeSetEndpoints(edge->shapes[index - 1], edge->points[index - 1].cp, edge->points[index].cp);
|
2023-02-08 15:30:12 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
cpSpaceRemoveShape(space, edge->shapes[index - 1]);
|
|
|
|
cpShapeFree(edge->shapes[index - 1]);
|
|
|
|
arrdel(edge->shapes, index - 1);
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_edge_setvert(struct phys2d_edge *edge, int index, cpVect val) {
|
|
|
|
assert(arrlen(edge->points) > index && index >= 0);
|
2023-11-11 20:01:42 -06:00
|
|
|
edge->points[index].cp = val;
|
2023-12-12 19:35:34 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void phys2d_edge_update_verts(struct phys2d_edge *edge, HMM_Vec2 *verts)
|
|
|
|
{
|
|
|
|
if (arrlen(edge->points) == arrlen(verts)) {
|
|
|
|
for (int i = 0; i < arrlen(verts); i++)
|
|
|
|
phys2d_edge_setvert(edge,i,verts[i].cp);
|
|
|
|
} else {
|
|
|
|
int vertchange = arrlen(verts)-arrlen(edge->points);
|
|
|
|
phys2d_edge_clearverts(edge);
|
|
|
|
phys2d_edge_addverts(edge,verts);
|
|
|
|
}
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
phys2d_applyedge(edge);
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_edge_clearverts(struct phys2d_edge *edge) {
|
2023-12-12 19:35:34 -06:00
|
|
|
for (int i = arrlen(edge->points) - 1; i >= 0; i--)
|
2023-02-08 15:30:12 -06:00
|
|
|
phys2d_edge_rmvert(edge, i);
|
|
|
|
}
|
|
|
|
|
2023-12-12 19:35:34 -06:00
|
|
|
void phys2d_edge_addverts(struct phys2d_edge *edge, HMM_Vec2 *verts) {
|
|
|
|
for (int i = 0; i < arrlen(verts); i++)
|
|
|
|
phys2d_edgeaddvert(edge, verts[i]);
|
2023-02-08 15:30:12 -06:00
|
|
|
}
|
|
|
|
|
2023-12-12 19:35:34 -06:00
|
|
|
/* Calculates all true positions of verts, links them up, and so on */
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_applyedge(struct phys2d_edge *edge) {
|
2023-12-11 08:36:45 -06:00
|
|
|
struct gameobject *go = edge->shape.go;
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
for (int i = 0; i < arrlen(edge->shapes); i++) {
|
2023-12-04 13:38:37 -06:00
|
|
|
/* Points must be scaled with gameobject, */
|
|
|
|
HMM_Vec2 a = HMM_MulV2(go->scale.xy, edge->points[i]);
|
|
|
|
HMM_Vec2 b = HMM_MulV2(go->scale.xy, edge->points[i+1]);
|
2023-11-11 20:01:42 -06:00
|
|
|
cpSegmentShapeSetEndpoints(edge->shapes[i], a.cp, b.cp);
|
2023-05-12 13:22:05 -05:00
|
|
|
cpSegmentShapeSetRadius(edge->shapes[i], edge->thickness);
|
|
|
|
if (i > 0 && i < arrlen(edge->shapes) - 1)
|
2023-12-04 13:38:37 -06:00
|
|
|
cpSegmentShapeSetNeighbors(edge->shapes[i], HMM_MulV2(go->scale.xy,edge->points[i-1]).cp, HMM_MulV2(go->scale.xy,edge->points[i+2]).cp);
|
2023-05-12 13:22:05 -05:00
|
|
|
go_shape_apply(NULL, edge->shapes[i], go);
|
|
|
|
cpShapeSetUserData(edge->shapes[i], &edge->shape);
|
|
|
|
}
|
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
cpSpaceReindexShapesForBody(space, edge->shape.go->body);
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void phys2d_dbgdrawedge(struct phys2d_edge *edge) {
|
|
|
|
edge->draws++;
|
|
|
|
if (edge->draws > 1) {
|
|
|
|
if (edge->draws >= arrlen(edge->shapes))
|
|
|
|
edge->draws = 0;
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
return;
|
|
|
|
}
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
if (arrlen(edge->shapes) < 1) return;
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-11-11 20:01:42 -06:00
|
|
|
HMM_Vec2 drawpoints[arrlen(edge->points)];
|
2023-12-11 08:36:45 -06:00
|
|
|
struct gameobject *go = edge->shape.go;
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-11-15 16:42:39 -06:00
|
|
|
HMM_Mat3 g2w = t_go2world(go);
|
|
|
|
for (int i = 0; i < arrlen(edge->points); i++)
|
|
|
|
drawpoints[i] = mat_t_pos(g2w, edge->points[i]);
|
2021-11-30 21:29:18 -06:00
|
|
|
|
2023-05-25 21:55:55 -05:00
|
|
|
float seglen = cpShapeGetSensor(edge->shapes[0]) ? sensor_seg : 0;
|
2023-05-24 20:45:50 -05:00
|
|
|
struct rgba color = shape_color(edge->shapes[0]);
|
|
|
|
struct rgba line_color = color;
|
|
|
|
color.a = col_alpha;
|
2023-12-19 17:28:45 -06:00
|
|
|
draw_edge(drawpoints, arrlen(edge->points), color, edge->thickness * 2, 0, line_color, seglen);
|
2023-05-12 13:22:05 -05:00
|
|
|
draw_points(drawpoints, arrlen(edge->points), 2, kinematic_color);
|
2021-11-30 21:29:18 -06:00
|
|
|
}
|
|
|
|
|
2023-01-25 21:32:58 -06:00
|
|
|
/************ COLLIDER ****************/
|
2023-05-12 13:22:05 -05:00
|
|
|
void shape_enabled(struct phys2d_shape *shape, int enabled) {
|
|
|
|
if (enabled)
|
2023-10-31 08:31:56 -05:00
|
|
|
cpShapeSetFilter(shape->shape, CP_SHAPE_FILTER_ALL);
|
2023-05-12 13:22:05 -05:00
|
|
|
else
|
2023-10-31 08:31:56 -05:00
|
|
|
cpShapeSetFilter(shape->shape, CP_SHAPE_FILTER_NONE);
|
2023-01-16 17:18:09 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
int shape_is_enabled(struct phys2d_shape *shape) {
|
|
|
|
if (cpshape_enabled(shape->shape))
|
|
|
|
return 1;
|
2023-01-18 10:45:43 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
return 0;
|
2023-01-18 10:45:43 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void shape_set_sensor(struct phys2d_shape *shape, int sensor) {
|
|
|
|
if (!shape->shape) {
|
|
|
|
struct phys2d_edge *edge = shape->data;
|
|
|
|
|
|
|
|
for (int i = 0; i < arrlen(edge->shapes); i++)
|
|
|
|
cpShapeSetSensor(edge->shapes[i], sensor);
|
|
|
|
} else
|
|
|
|
cpShapeSetSensor(shape->shape, sensor);
|
2023-01-17 13:04:08 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
int shape_get_sensor(struct phys2d_shape *shape) {
|
|
|
|
if (!shape->shape) {
|
2023-05-27 07:01:17 -05:00
|
|
|
struct phys2d_edge *edge = shape->data;
|
|
|
|
if (arrlen(edge->shapes) > 0) return cpShapeGetSensor(edge->shapes[0]);
|
2023-10-31 08:31:56 -05:00
|
|
|
YughInfo("Attempted to get the sensor of an edge with no shapes. It has %d points.", arrlen(edge->points));
|
2023-05-27 07:01:17 -05:00
|
|
|
return 0;
|
2023-05-12 13:22:05 -05:00
|
|
|
}
|
2023-05-27 07:01:17 -05:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
return cpShapeGetSensor(shape->shape);
|
2023-01-18 10:45:43 -06:00
|
|
|
}
|
|
|
|
|
2023-12-04 13:38:37 -06:00
|
|
|
void phys2d_reindex_body(cpBody *body) { cpSpaceReindexShapesForBody(space, body); }
|
2022-12-21 19:24:59 -06:00
|
|
|
|
2023-05-27 10:13:20 -05:00
|
|
|
struct postphys_cb {
|
|
|
|
struct callee c;
|
|
|
|
JSValue send;
|
|
|
|
};
|
|
|
|
|
2023-09-13 07:31:00 -05:00
|
|
|
static struct postphys_cb *begins = NULL;
|
2023-05-27 10:13:20 -05:00
|
|
|
|
|
|
|
void flush_collide_cbs() {
|
2023-10-11 17:22:41 -05:00
|
|
|
for (int i = 0; i < arrlen(begins); i++) {
|
2023-05-27 10:13:20 -05:00
|
|
|
script_callee(begins[i].c, 1, &begins[i].send);
|
2023-12-28 07:57:22 -06:00
|
|
|
JS_FreeValue(js, begins[i].send);
|
2023-10-11 17:22:41 -05:00
|
|
|
}
|
2023-05-27 10:13:20 -05:00
|
|
|
|
2023-09-13 07:31:00 -05:00
|
|
|
arrsetlen(begins,0);
|
2023-05-27 10:13:20 -05:00
|
|
|
}
|
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
void duk_call_phys_cb(HMM_Vec2 norm, struct callee c, gameobject *hit, cpArbiter *arb) {
|
2023-05-12 13:22:05 -05:00
|
|
|
cpShape *shape1;
|
|
|
|
cpShape *shape2;
|
|
|
|
cpArbiterGetShapes(arb, &shape1, &shape2);
|
|
|
|
|
|
|
|
JSValue obj = JS_NewObject(js);
|
|
|
|
JS_SetPropertyStr(js, obj, "normal", vec2js(norm));
|
2023-12-28 07:57:22 -06:00
|
|
|
JS_SetPropertyStr(js, obj, "obj", JS_DupValue(js,hit->ref));
|
2023-05-12 13:22:05 -05:00
|
|
|
JS_SetPropertyStr(js, obj, "sensor", JS_NewBool(js, cpShapeGetSensor(shape2)));
|
2023-11-14 09:20:09 -06:00
|
|
|
HMM_Vec2 srfv;
|
|
|
|
srfv.cp = cpArbiterGetSurfaceVelocity(arb);
|
|
|
|
JS_SetPropertyStr(js, obj, "velocity", vec2js(srfv));
|
2023-11-16 09:27:04 -06:00
|
|
|
// srfv.cp = cpArbiterGetPointA(arb,0);
|
|
|
|
// JS_SetPropertyStr(js, obj, "pos", vec2js(srfv));
|
|
|
|
// JS_SetPropertyStr(js,obj,"depth", num2js(cpArbiterGetDepth(arb,0)));
|
2023-05-27 10:13:20 -05:00
|
|
|
|
2023-09-13 07:31:00 -05:00
|
|
|
struct postphys_cb cb;
|
|
|
|
cb.c = c;
|
|
|
|
cb.send = obj;
|
2023-12-27 10:34:14 -06:00
|
|
|
arrput(begins, cb);
|
2023-02-17 01:16:52 -06:00
|
|
|
}
|
|
|
|
|
2023-03-17 10:25:35 -05:00
|
|
|
#define CTYPE_BEGIN 0
|
|
|
|
#define CTYPE_SEP 1
|
2023-03-10 13:13:48 -06:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
static cpBool handle_collision(cpArbiter *arb, int type) {
|
|
|
|
cpBody *body1;
|
|
|
|
cpBody *body2;
|
|
|
|
cpArbiterGetBodies(arb, &body1, &body2);
|
2023-12-11 08:36:45 -06:00
|
|
|
gameobject *go = cpBodyGetUserData(body1);
|
|
|
|
gameobject *go2 = cpBodyGetUserData(body2);
|
2023-05-12 13:22:05 -05:00
|
|
|
cpShape *shape1;
|
|
|
|
cpShape *shape2;
|
|
|
|
cpArbiterGetShapes(arb, &shape1, &shape2);
|
|
|
|
struct phys2d_shape *pshape1 = cpShapeGetUserData(shape1);
|
|
|
|
struct phys2d_shape *pshape2 = cpShapeGetUserData(shape2);
|
|
|
|
|
2023-11-14 09:20:09 -06:00
|
|
|
HMM_Vec2 norm1;
|
|
|
|
norm1.cp = cpArbiterGetNormal(arb);
|
2023-05-12 13:22:05 -05:00
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case CTYPE_BEGIN:
|
|
|
|
for (int i = 0; i < arrlen(go->shape_cbs); i++)
|
|
|
|
if (go->shape_cbs[i].shape == pshape1)
|
2023-12-11 08:36:45 -06:00
|
|
|
duk_call_phys_cb(norm1, go->shape_cbs[i].cbs.begin, go2, arb);
|
2023-05-12 13:22:05 -05:00
|
|
|
|
|
|
|
if (JS_IsObject(go->cbs.begin.obj))
|
2023-12-11 08:36:45 -06:00
|
|
|
duk_call_phys_cb(norm1, go->cbs.begin, go2, arb);
|
2023-05-12 13:22:05 -05:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTYPE_SEP:
|
2023-11-16 09:27:04 -06:00
|
|
|
if (JS_IsObject(go->cbs.separate.obj))
|
2023-12-11 08:36:45 -06:00
|
|
|
duk_call_phys_cb(norm1, go->cbs.separate, go2, arb);
|
2023-04-21 16:57:30 -05:00
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
2022-12-21 19:24:59 -06:00
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
static cpBool script_phys_cb_begin(cpArbiter *arb, cpSpace *space, void *data) {
|
2023-03-17 10:25:35 -05:00
|
|
|
return handle_collision(arb, CTYPE_BEGIN);
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
static cpBool script_phys_cb_separate(cpArbiter *arb, cpSpace *space, void *data) {
|
2023-03-17 10:25:35 -05:00
|
|
|
return handle_collision(arb, CTYPE_SEP);
|
|
|
|
}
|
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
void phys2d_rm_go_handlers(gameobject *go) {
|
2023-12-21 17:21:01 -06:00
|
|
|
cpCollisionHandler *handler = cpSpaceAddWildcardHandler(space, (cpCollisionType)go);
|
2023-02-24 12:11:36 -06:00
|
|
|
handler->userData = NULL;
|
|
|
|
handler->beginFunc = NULL;
|
|
|
|
handler->separateFunc = NULL;
|
|
|
|
}
|
|
|
|
|
2023-12-11 08:36:45 -06:00
|
|
|
void phys2d_setup_handlers(gameobject *go) {
|
2023-12-21 17:21:01 -06:00
|
|
|
cpCollisionHandler *handler = cpSpaceAddWildcardHandler(space, (cpCollisionType)go);
|
2023-12-11 08:36:45 -06:00
|
|
|
handler->userData = go;
|
2023-03-10 13:13:48 -06:00
|
|
|
handler->beginFunc = script_phys_cb_begin;
|
2023-03-17 10:25:35 -05:00
|
|
|
handler->separateFunc = script_phys_cb_separate;
|
2023-03-10 13:13:48 -06:00
|
|
|
}
|
2022-12-21 19:24:59 -06:00
|
|
|
|
2023-03-17 10:25:35 -05:00
|
|
|
static int airborne = 0;
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
void inair(cpBody *body, cpArbiter *arbiter, void *data) {
|
2023-03-17 10:25:35 -05:00
|
|
|
airborne = 0;
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:22:05 -05:00
|
|
|
int phys2d_in_air(cpBody *body) {
|
2023-03-17 10:25:35 -05:00
|
|
|
airborne = 1;
|
|
|
|
cpBodyEachArbiter(body, inair, NULL);
|
|
|
|
|
|
|
|
return airborne;
|
|
|
|
}
|