Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - eri0o

#113
Hi, I have recently found a small piece of software I wanted to experiment with for ideas. Problem is, it's from 1986 and for Mac computer of the time.

The software is World Builder. Does anyone knows a way I could run and play with this software?

https://www.macintoshrepository.org/2984-world-builder
#114
General Discussion / Fake 3D in 2D
Thu 05/10/2023 00:51:25
Just because I love these 3D but in 2D stuff - mostly because aesthetics and to see the tricks. I can't try to implement all methods, so better to at least aggregate somewhere.

This post is recent and the technique here is incredible

https://dioragame.com/devlog/?log=3

It accomplishes 3D in 2D on PlayDate - an underpowered gaming device with a black and white display.
#115
toaster version 0.1.2

Get Latest Release toaster.scm | GitHub Repo | Download project .zip

This is a initial version of Toaster, a module to produce Toasts. These are those messages that appear on the screen and fade.

I will eventually write better docs and make the module more interesting, but here is a demo to explain what it does.



Usage

The basic usage is simply Toaster t; t.Toast("A Toast!");



Script API

Toaster

A toaster can produce toasts, but you can configure your toaster to produce different toasts as desired!

eToastColor Toaster.BackgroundColor
Background color of the toast, in AGS Color.

TextWindowGUI* Toaster.TextWindowGUI
You can set a TextWindowGUI to put the toast in a text box.

FontType Toaster.Font
The font to write the toast message.

eToastTweenEasingType Toaster.SlideInEasing
The easing to use when moving the toast into screen. These are the same possible ones in the tween module!

eToastTweenEasingType Toaster.SlideOutEasing
The easing to use when moving the toast out of existence

eToastAlignement Toaster.OriginAlignment
From where to position the toasts. It can be on the left, center and right.

float Toaster.Duration
How long in seconds should the Toast be on-screen.

float Toaster.Rotation
Rotation in degrees for produced toasts.

int Toaster.Icon
Add sprite to use as an icon in the left corner of the message.



License
This module is created by eri0o is provided with MIT License, see LICENSE for more details. It uses easing code based on Edmundo Ruiz and Robert Penner's, works, which are MIT and BSD licensed, respectively (and included in the module script).
#116
I have been thinking on the design of room CRM files.

For some reason room files uses sprites stored in itself instead of reading the spritefile, these are the masks (Walkable areas, Regions, Hotspots and Walkbehinds) and the room backgrounds (up to 5). My guess is this is made so it's easy to share them and directly use them as room templates.

Is it better for resource management in the engine side to keep these in the room file instead of the spritefile (you never need the background/mask from one room in the other room)?

Now for a more practical question.

Recently Deflate was added as a compression option for sprites. Let's say I select deflate in the general setting. Are the sprites in the room files using deflate or do they use something else? Is it possible to detect if any of these room sprites is just empty black with all zeroes and instead store a hint there?
#117
Editor Development / Small editor improvements
Sun 17/09/2023 14:03:32
Occasionally when using the Editor I see some stuff that looks "maybe not that hard" and that feels it would improve usage. Whenever I attempt something in this tiny scope I will put here just to not polute elsewhere with tiny changes.

The other reason is just if I am thinking wrong about it someone can just interject here (assuming most people don't follow things in GitHub).

OK, today my first thing in this is adding a button to open the file explorer in the compiled directory.





https://github.com/adventuregamestudio/ags/pull/2133

#118
I notice in my either script intensive games, or very lots of dialog games, there is a lot of the size of the game that is mainly due to the size of the scripts - I usually work in low resolution, so the image files get very small already.

I am not sure how to do or proceed with this. On one hand in the past games usually compressed their texts using some strategy - this is really common in very old games that had to fit in a ROM cartridge with very limited storage. This is an strategy, to simply compress only the strings, but I wonder if there is any way to directly compress the entirety of the script objects.

I don't know if this is the best approach or some other approach is best - say have the packaging format itself support compression. I also don't know if it's reasonable to just decompress on game boot and have the uncompressed script objects to work with or if it would need to be streamed from the compressed package.

Anyway, because of the unknowns around this I decided to write it here just to start to think about this.
#119
Editor Development / AGS Editor on macOS
Fri 26/05/2023 21:33:32
So I am on macOS Ventura with a Mac mini 2023 with m2 chip.

AGS Editor depends on .NET Framework 4.6 and installing it on a recent macOS requires wine 8.3 or above, which aren't stable yet, meaning one can't install them with Homebrew yet.

So if you want to run on Wine the only current way of doing outside of building wine by hand is using Crossover - I used the trial version for testing.

On Crossover, I created a bottle (the way it names a wine prefix) where I installed .NET 4.0, .NET 4.5.2 and then .NET 4.6.2 (had to use the offline installer). I already had Rosetta enabled too, not sure what happens when one doesn't have it. After this I run the AGS Editor installer from the latest 3.6.0.48 version in the bottle using crossover interface.

After the install it looks like AGS Editor runs normally, I have only built for windows and web with it. Differently from my previous Linux experience, it looks like I can press F5 and debug an AGS game, so not sure if something was fixed in Wine in meanwhile or the wine build for macOS had no issue with this.

I will update here some months from now when a new wine version is stable.
#120
Following the new pointers in managed structs PR, from CW, I decided to give a shot at something that uses lots of pointers just to see what would happen.

So, from the original box2d lite code, from the original erin catto presentation, I attempted a port to AGS Script, just to see what would happen.



b2dlite.ash
Spoiler
Code: ags
// new module header
#define FLT_MAX 340282346638528859811704183484516925440.0
#define MAX_POINTS 32
#define MAX_ARBITERS 16
#define MAX_BODIES 256
#define MAX_JOINTS 256
#define MAX_CONTACTS 2

managed struct Vec2
{
	float x, y;

  import static Vec2* New(float x, float y); // $AUTOCOMPLETESTATICONLY$
 	import void Set(float x, float y);
  import void SetV(Vec2* v);
  import Vec2* Abs();
  import Vec2* Negate();
	import Vec2* Minus(Vec2* vb);
  import Vec2* Plus(Vec2* vb);
  import Vec2* Scale(float a);
  import float Length();
  import float Dot(Vec2* b);
  import float Cross(Vec2* b);
};

managed struct Mat22
{
  float col1_x, col1_y, col2_x, col2_y;
  
  import static Mat22* New(float col1_x, float col1_y, float col2_x, float col2_y); // $AUTOCOMPLETESTATICONLY$
  import static Mat22* NewFromAngle(float angle); // $AUTOCOMPLETESTATICONLY$
  import static Mat22* NewFromVec2(Vec2* col1, Vec2* col2); // $AUTOCOMPLETESTATICONLY$
  import Mat22* Transpose();
  import Mat22* Invert();
  import Mat22* Plus(Mat22* B);
  import Mat22* Multiply(Mat22* B);
  import Mat22* Abs();
};

managed struct Body
{
  import static Body* Create();
	float position_x, position_y;
	float rotation;

	float velocity_x, velocity_y;
	float angularVelocity;

	float force_x, force_y;
	float torque;

	float width_x, width_y;

	float friction;
	float mass, invMass;
	float I, invI;
    
	import void Set(Vec2* w, float m);
	import void AddForce(const Vec2* f);
};

managed struct Joint
{
  import static Joint* Create();
	Mat22* M;
	Vec2* localAnchor1, localAnchor2;
	Vec2* r1, r2;
	Vec2* bias;
	Vec2* P;		// accumulated impulse
	Body* body1;
	Body* body2;
	float biasFactor;
	float softness;
  
	import void Set(Body* body1, Body* body2, Vec2* anchor);

	import void PreStep(float inv_dt);
	import void ApplyImpulse();
};

managed struct FeaturePair
{
  static import FeaturePair* Create();
  
  char inEdge1;
  char outEdge1;
  char inEdge2;
  char outEdge2;
  import attribute int value;
  import int get_value();
  import void set_value(int value);
};

managed struct Contact
{
  static import Contact* Create();
  
	Vec2* position;
	Vec2* normal;
	Vec2* r1, r2;
	float separation;
	float Pn;	// accumulated normal impulse
	float Pt;	// accumulated tangent impulse
	float Pnb;	// accumulated normal impulse for position bias
	float massNormal, massTangent;
	float bias;
	FeaturePair* feature;
  import Contact* CopyTo(Contact* c);
};

managed struct Arbiter
{
  static import Arbiter* Create(Body* b1, Body* b2);
  
  import void Update(Contact* contacts[],  int numContacts);
  
	import void PreStep(float inv_dt);
	import void ApplyImpulse();

	Contact* contacts[];
	int numContacts;

	Body* body1;
	Body* body2;

	// Combined friction
	float friction;
};

managed struct Arbiters
{
  import void Add(Arbiter* arb);
  import void Remove(Body* b1, Body* b2);
  import Arbiter* Get(Body* b1, Body* b2);
  import void Clear();
  
  Arbiter* a[MAX_ARBITERS];
  int a_count;
  import static Arbiters* Create();
};

struct World
{
  import void Init(float gravity_x, float gravity_y, int iterations = 10);
	import void AddBody(Body* body);
	import void AddJoint(Joint* joint);
	import void Clear();

	import void Step(float dt);

	import void BroadPhase();

	Body* bodies[MAX_BODIES];
	int body_count;
  Joint* joints[MAX_JOINTS];
  int joint_count;
	Arbiters* arbiters;
  
	Vec2* gravity;
	int iterations;
	static import attribute bool accumulateImpulses;
  static import bool get_accumulateImpulses();
  static import void set_accumulateImpulses(bool value);
	static import attribute bool warmStarting;
  static import bool get_warmStarting();
  static import void set_warmStarting(bool value);
	static import attribute bool positionCorrection;
  static import bool get_positionCorrection();
  static import void set_positionCorrection(bool value);
};
[close]

b2dlite.asc
Spoiler
Code: ags
// new module script

// assertion check utility
void assert(bool expr)
{
  if (expr == false) AbortGame("Failed assertion!");
}

float _abs(float a)
{
  if (a >= 0.0)
    return a;
  return -a;
}

#region MATH_UTILITIES
static Vec2* Vec2::New(float x, float y) {
  Vec2* v = new Vec2;
  v.x = x;
  v.y = y;
  return v;
}
void  Vec2::Set(float x, float y) { this.x = x;  this.y = y; }
void  Vec2::SetV(Vec2* v) { this.x = v.x;  this.y = v.y; }
Vec2* Vec2::Negate() { return this.New(-this.x, -this.y); }
Vec2* Vec2::Abs() { return this.New(_abs(this.x), _abs(this.y)); }
Vec2* Vec2::Minus(Vec2* v) { return this.New(this.x - v.x, this.y - v.y); }
Vec2* Vec2::Plus(Vec2* v) { return this.New(this.x + v.x, this.y + v.y); }
Vec2* Vec2::Scale(float a) { return this.New(this.x * a, this.y * a); }
float Vec2::Length(){ return Maths.Sqrt(this.x*this.x + this.y*this.y); }
float Vec2::Dot(Vec2* b) { return this.x * b.x + this.y * b.y; }
float Vec2::Cross(Vec2* b){  return this.x * b.y - this.y * b.x; }

static Mat22* Mat22::New(float col1_x, float col1_y, float col2_x, float col2_y)
{
  Mat22* m = new Mat22;
  m.col1_x = col1_x; m.col2_x = col2_x;
  m.col1_y = col1_y; m.col2_y = col2_y;
  return m;
}

static Mat22* Mat22::NewFromAngle(float angle)
{
  float c = Maths.Cos(angle), s = Maths.Sin(angle);
  return Mat22.New(c, s, -s, c);
}
  
static Mat22* Mat22::NewFromVec2(Vec2* col1, Vec2* col2)
{
  return Mat22.New(col1.x, col1.y, col2.x, col2.y);
}

Mat22* Mat22::Transpose() 
{
  return this.New(this.col1_x, this.col2_x, this.col1_y, this.col2_y);
}

Mat22* Mat22::Invert()
{
  float a = this.col1_x, b = this.col2_x, c = this.col1_y, d = this.col2_y;
  float det = a * d - b * c;
  assert(det != 0.0);
  det = 1.0 / det;
  return this.New(det * d, -det * c, -det * b, det * a);
}

Mat22* Mat22::Plus(Mat22* B)
{
  return this.New(this.col1_x + B.col1_x, this.col1_y + B.col1_y, this.col2_x + B.col2_x, this.col2_y + B.col2_y);
}

Mat22* Mat22::Abs()
{
  return this.New(_abs(this.col1_x), _abs(this.col1_y), _abs(this.col2_x), _abs(this.col2_y));
}

Vec2* CrossV2S(Vec2* a, float s)
{
  return Vec2.New(s * a.y, -s * a.x);
}

Vec2* CrossS2V(float s, Vec2* a)
{
  return Vec2.New(-s * a.y, s * a.x);
}

Vec2* MultiplyVec2(Mat22* A, Vec2* v)
{
  return Vec2.New(A.col1_x * v.x + A.col2_x * v.y, A.col1_y * v.x + A.col2_y * v.y);
}

Mat22* Mat22::Multiply(Mat22* matb)
{
   //A * B.col1, A * B.col2);
  float m11 = this.col1_x * matb.col1_x + this.col2_x * matb.col1_y;
  float m12 = this.col1_y * matb.col1_x + this.col2_y * matb.col1_y;
  
  float m21 = this.col1_x * matb.col2_x + this.col2_x * matb.col2_y;
  float m22 = this.col1_y * matb.col2_x + this.col2_y * matb.col2_y;
  return this.New(m11, m12, m21, m22);
}

float _sign(float a)
{
  if(a < 0.0) return -1.0;
  return 1.0;
}

int _maxi(int a, int b)
{
  if (a > b)
    return a;
  return b;
}

int _mini(int a, int b)
{
  if (a < b)
    return a;
  return b;
}

int _clampi(int v, int min, int max)
{
  return _mini(max, _maxi(v, min));
}

float _max(float a, float b)
{
  if (a > b)
    return a;
  return b;
}

float _min(float a, float b)
{
  if (a < b)
    return a;
  return b;
}

float _clamp(float v, float min, float max)
{
  return _min(max, _max(v, min));
}

// Random number in range [-1,1]
float _Random()
{
  float r = IntToFloat(Random(100000));
  r /= 100000.0;
  return 2.0 * r - 1.0;
}

float _RandomRange(float lo, float hi)
{
  float r = IntToFloat(Random(100000));
  r /= 100000.0;
  return (hi - lo) * r + lo;
}
#endregion //MATH_UTILITIES

bool _accumulateImpulses;
bool _warmStarting;
bool _positionCorrection;

static bool World::get_accumulateImpulses()
{
  return _accumulateImpulses;
}

static void World::set_accumulateImpulses(bool value)
{
  _accumulateImpulses = value;
}

static bool World::get_warmStarting()
{
  return _warmStarting;
}

static void World::set_warmStarting(bool value)
{
  _warmStarting = value;
}

static bool World::get_positionCorrection()
{
  return _positionCorrection;
}

static void World::set_positionCorrection(bool value)
{
  _positionCorrection = value;
}

void FeaturePair::set_value(int v)
{
  this.inEdge1 = (v >> 24) & 0xff;
  this.inEdge2 = (v >> 16) & 0xff;
  this.outEdge1 = (v >> 8) & 0xff;
  this.outEdge2 = v & 0xff;
}

int FeaturePair::get_value()
{
  return (this.inEdge1 << 24) & 0xff000000 | (this.inEdge2 << 16) & 0x00ff0000 | (this.outEdge1 << 8) & 0x0000ff00 | this.outEdge2;
}

static FeaturePair* FeaturePair::Create()
{
  FeaturePair* fp = new FeaturePair;
  fp.inEdge1 = 0;
  fp.inEdge2 = 0;
  fp.outEdge1 = 0;
  fp.outEdge2 = 0;
  return fp;
}

void World::AddBody(Body* body)
{
  this.bodies[this.body_count] = body;
  this.body_count++;
}

void World::AddJoint(Joint* joint)
{
  this.joints[this.joint_count] = joint;
  this.joint_count++;
}


void Arbiters::Add(Arbiter* arb)
{
  if(this.a_count < MAX_ARBITERS) {
    this.a[this.a_count] = arb;
    this.a_count++;
  }
}

Contact* Contact::CopyTo(Contact* c)
{
  c.bias = this.bias;
  if(c.feature == null) {
    c.feature = FeaturePair.Create();
  }
  
  c.feature.inEdge1 = this.feature.inEdge1;
  c.feature.inEdge2 = this.feature.inEdge2;
  c.feature.outEdge1 = this.feature.outEdge1;
  c.feature.outEdge2 = this.feature.outEdge2;
  
  c.massNormal = this.massNormal;
  c.massTangent = this.massTangent;
  if(c.normal == null) {
    c.normal = Vec2.New(this.normal.x, this.normal.y);
  } else {
    c.normal.Set(this.normal.x, this.normal.y);  
  }
  
  c.Pn = this.Pn;
  c.Pnb = this.Pnb;
  if(c.position == null) {
    c.position = Vec2.New(c.position.x, c.position.y);
  } else {
    c.position.Set(c.position.x, c.position.y);
  }
  c.Pt = this.Pt;
  if(c.r1 == null) {
    c.r1 = Vec2.New(this.r1.x, this.r1.y);
  } else {
    c.r1.Set(this.r1.x, this.r1.y);
  }
  if(c.r2 == null) {
    c.r2 = Vec2.New(this.r2.x, this.r2.y);
  } else {
    c.r2.Set(this.r2.x, this.r2.y);
  }
  c.separation = this.separation;
  return c;
}

void Arbiters::Remove(Body* b1, Body* b2)
{
  int j=0;
  int removed = 0;
  for(int i=0; i<this.a_count; i++)
  {
    if(this.a[i].body1 == b1 && this.a[i].body2 == b2 ||
       this.a[i].body1 == b2 && this.a[i].body2 == b1 ) {
      removed++;
      continue;
    }
    this.a[j]= this.a[i];
    j++;
  }
  this.a_count-=removed;
}

Arbiter* Arbiters::Get(Body* b1, Body* b2)
{
  for(int i=0; i<this.a_count; i++)
  {
    if(this.a[i].body1 == b1 && this.a[i].body2 == b2 || 
       this.a[i].body1 == b2 && this.a[i].body2 == b1 ) {
      return this.a[i];
    }
  }
  return null;  
}

void Arbiters::Clear()
{
  for(int i=0; i<this.a_count; i++)
  {
    this.a[i] = null;
  }
  this.a_count = 0;
}

static Arbiters* Arbiters::Create()
{
  Arbiters* a = new Arbiters;
  return a;  
}

void World::Clear()
{
  for(int i=0; i<this.body_count; i++)
  {
    this.bodies[i] = null;
  }
  this.body_count = 0;
  
  for(int i=0; i<this.joint_count; i++)
  {
    this.joints[i] = null;
  }
  this.joint_count = 0;
  if(this.arbiters != null) {
    this.arbiters.Clear();
  } else {
    this.arbiters = Arbiters.Create();
  }
}

static Body* Body::Create()
{
  Body* b = new Body;
  b.position_x = 0.0;
  b.position_y = 0.0;
  b.rotation = 0.0;
  b.velocity_x = 0.0;
  b.velocity_y = 0.0;
  b.angularVelocity = 0.0;
  b.force_x = 0.0;
  b.force_y = 0.0;
  b.torque = 0.0;
  b.friction = 0.2;
  
  b.width_x = 1.0;
  b.width_y = 1.0;
  b.mass = FLT_MAX;
  b.invMass = 0.0;
  b.I = FLT_MAX;
  b.invI = 0.0;
  
  return b;
}

void Body::Set(Vec2* w, float m)
{
  this.position_x = 0.0;
  this.position_y = 0.0;
  this.rotation = 0.0;
  this.velocity_x = 0.0;
  this.velocity_y = 0.0;
  this.angularVelocity = 0.0;
  this.force_x = 0.0;
  this.force_y = 0.0;
  this.torque = 0.0;
  this.friction = 0.2;
  
  this.width_x = w.x;
  this.width_y = w.y;
  this.mass = m;

  if (this.mass < FLT_MAX)
  {
    this.invMass = 1.0 / this.mass;
    this.I = this.mass * (this.width_x * this.width_x + this.width_y * this.width_y) / 12.0;
    this.invI = 1.0 / this.I;
  }
  else
  {
    this.invMass = 0.0;
    this.I = FLT_MAX;
    this.invI = 0.0;
  }
}

static Joint* Joint::Create()
{
  Joint* jo = new Joint;
  jo.body1 = null;
  jo.body2 = null;
  
  jo.P = Vec2.New(0.0, 0.0);
  jo.biasFactor = 0.2;
  jo.softness = 0.0;
  
  jo.M = Mat22.New(0.0, 0.0, 0.0, 0.0);
  jo.r1 = Vec2.New(0.0, 0.0);
  jo.r2 = Vec2.New(0.0, 0.0);
  jo.localAnchor1 = Vec2.New(0.0, 0.0);
  jo.localAnchor2 = Vec2.New(0.0, 0.0);
  jo.bias = Vec2.New(0.0, 0.0);
  
  return jo;
}

void Joint::Set(Body* b1, Body* b2, Vec2* anchor)
{
  this.body1 = b1;
  this.body2 = b2;

  Mat22* Rot1 = Mat22.NewFromAngle(this.body1.rotation);
  Mat22* Rot2 = Mat22.NewFromAngle(this.body2.rotation);
  Mat22* Rot1T = Rot1.Transpose();
  Mat22* Rot2T = Rot2.Transpose();

  this.localAnchor1 = MultiplyVec2(Rot1T, anchor.Minus(Vec2.New(this.body1.position_x, this.body1.position_y)));
  this.localAnchor2 = MultiplyVec2(Rot2T, anchor.Minus(Vec2.New(this.body2.position_x, this.body2.position_y)));

  if(this.P == null) {
    this.P = Vec2.New(0.0, 0.0);
  } else {
    this.P.Set(0.0, 0.0);
  }

  this.softness = 0.0;
  this.biasFactor = 0.2;
}

void Joint::PreStep(float inv_dt)
{
  // Pre-compute anchors, mass matrix, and bias.
  Mat22* Rot1 = Mat22.NewFromAngle(this.body1.rotation);
  Mat22* Rot2 = Mat22.NewFromAngle(this.body2.rotation);

  this.r1 = MultiplyVec2(Rot1, this.localAnchor1);
  this.r2 = MultiplyVec2(Rot2, this.localAnchor2);

  // deltaV = deltaV0 + K * impulse
  // invM = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)]
  //      = [1/m1+1/m2     0    ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * [r1.y*r1.y -r1.x*r1.y]
  //        [    0     1/m1+1/m2]           [-r1.x*r1.y r1.x*r1.x]           [-r1.x*r1.y r1.x*r1.x]
  Mat22* K1 = Mat22.New(
    this.body1.invMass + this.body2.invMass, 0.0,
    0.0, this.body1.invMass + this.body2.invMass);

  Mat22* K2 = Mat22.New( this.body1.invI * this.r1.y * this.r1.y, -this.body1.invI * this.r1.x * this.r1.y, 
                        -this.body1.invI * this.r1.x * this.r1.y,  this.body1.invI * this.r1.x * this.r1.x);
  
  
  Mat22* K3 = Mat22.New(this.body2.invI * this.r2.y * this.r2.y, -this.body2.invI * this.r2.x * this.r2.y,
                       -this.body2.invI * this.r2.x * this.r2.y,   this.body2.invI * this.r2.x * this.r2.x);

  Mat22* K = K1.Plus(K2);
  K = K.Plus(K3);

  K.col1_x += this.softness;
  K.col1_y += this.softness;

  this.M = K.Invert();

  Vec2* p1 = this.r1.Plus(Vec2.New(this.body1.position_x, this.body1.position_y));
  Vec2* p2 = this.r2.Plus(Vec2.New(this.body2.position_x, this.body2.position_y));
  Vec2* dp = p2.Minus(p1);
   
  if (_positionCorrection)
  {
    this.bias = dp.Scale(- this.biasFactor * inv_dt);
  }
  else
  {
    if(this.bias == null) {
      this.bias = Vec2.New(0.0, 0.0);
    } else {
      this.bias.Set(0.0, 0.0);
    }
  }

  if (_warmStarting)
  {
    // Apply accumulated impulse.
    this.body1.velocity_x -= this.body1.invMass * this.P.x;
    this.body1.velocity_y -= this.body1.invMass * this.P.y;
    this.body1.angularVelocity -= this.body1.invI * this.r1.Cross(this.P);
    
    this.body2.velocity_x += this.body2.invMass * this.P.x;
    this.body2.velocity_y += this.body2.invMass * this.P.y;
    this.body2.angularVelocity += this.body2.invI * this.r2.Cross(this.P);
  }
  else
  {
    if(this.P == null) {
      this.P = Vec2.New(0.0, 0.0);
    } else {
      this.P.Set(0.0, 0.0);
    }
  }
}

void Joint::ApplyImpulse()
{
  Vec2* tmpa = CrossS2V(this.body2.angularVelocity, this.r2);
  tmpa.x += this.body2.velocity_x;
  tmpa.y += this.body2.velocity_y;
  tmpa.x -= this.body1.velocity_x;
  tmpa.y -= this.body1.velocity_y;
  
  Vec2* tmpb = CrossS2V(this.body1.angularVelocity, this.r1);
  
  Vec2* dv = tmpa.Minus(tmpb);

  Vec2* tmp = this.bias.Minus(dv);
  tmp = tmp.Minus(this.P.Scale(this.softness));
  Vec2* impulse = MultiplyVec2(this.M, tmp);

  this.body1.velocity_x -= this.body1.invMass*impulse.x;
  this.body1.velocity_y -= this.body1.invMass*impulse.y;
  this.body1.angularVelocity -= this.body1.invI * this.r1.Cross(impulse);
  
  
  this.body2.velocity_x += this.body2.invMass*impulse.x;
  this.body2.velocity_y += this.body2.invMass*impulse.y;
  this.body2.angularVelocity += this.body2.invI * this.r2.Cross(impulse);

  this.P = this.P.Plus(impulse);
}

// Box vertex and edge numbering:
//
//        ^ y
//        |
//        e1
//   v2 ------ v1
//    |        |
// e2 |        | e4  --> x
//    |        |
//   v3 ------ v4
//        e3


enum Axis
{
  FACE_A_X,
  FACE_A_Y,
  FACE_B_X,
  FACE_B_Y
};

enum EdgeNumbers
{
  NO_EDGE = 0,
  EDGE1,
  EDGE2,
  EDGE3,
  EDGE4
};

managed struct ClipVertex
{
  static import ClipVertex* Create();

  Vec2* v;
  FeaturePair* fp;
};


static ClipVertex* ClipVertex::Create()
{
  ClipVertex* cv = new ClipVertex;
  cv.v = Vec2.New(0.0, 0.0);
  cv.fp = FeaturePair.Create();
  return cv;
}

static Contact* Contact::Create()
{
  Contact* c = new Contact;
  c.bias = 0.0;
  c.feature = FeaturePair.Create();
  c.massNormal = 0.0;
  c.massTangent = 0.0;
  c.normal = Vec2.New(0.0, 0.0);
  c.Pn = 0.0;
  c.Pnb = 0.0;
  c.position = Vec2.New(0.0, 0.0);
  c.Pt = 0.0;
  c.r1 = Vec2.New(0.0, 0.0);
  c.r2 = Vec2.New(0.0, 0.0);
  c.separation = 0.0;
  return c;
}

void Flip(FeaturePair* fp)
{
  char tmp = fp.inEdge1;
  fp.inEdge1 = fp.inEdge2;
  fp.inEdge2 = tmp;
  
  tmp = fp.outEdge1; 
  fp.outEdge1 = fp.outEdge2;
  fp.outEdge2 = tmp;
}

int ClipSegmentToLine(ClipVertex* vOut[], ClipVertex* vIn[], Vec2* normal, float offset, char clipEdge)
{
  // Start with no output points
  int numOut = 0;

  // Calculate the distance of end points to the line
  float distance0 = normal.Dot(vIn[0].v) - offset;
  float distance1 = normal.Dot(vIn[1].v) - offset;

  // If the points are behind the plane
  if (distance0 <= 0.0) {
    vOut[numOut] = vIn[0];
    numOut++;
  }
  if (distance1 <= 0.0) {
    vOut[numOut] = vIn[1];
    numOut++;
  }

  // If the points are on different sides of the plane
  if (distance0 * distance1 < 0.0)
  {
    // Find intersection point of edge and plane
    float interp = distance0 / (distance0 - distance1);
    Vec2* tmp_v = vIn[1].v.Minus(vIn[0].v);
    tmp_v = tmp_v.Scale(interp);
    vOut[numOut].v = vIn[0].v.Plus(tmp_v);
    
    if (distance0 > 0.0)
    {
      vOut[numOut].fp = vIn[0].fp;
      vOut[numOut].fp.inEdge1 = clipEdge;
      vOut[numOut].fp.inEdge2 = NO_EDGE;
    }
    else
    {
      vOut[numOut].fp = vIn[1].fp;
      vOut[numOut].fp.outEdge1 = clipEdge;
      vOut[numOut].fp.outEdge2 = NO_EDGE;
    }
    numOut++;
  }

  return numOut;
}

void ComputeIncidentEdge(ClipVertex* c[], Vec2* h, Vec2* pos, Mat22* Rot, Vec2* normal)
{
  // The normal is from the reference box. Convert it
  // to the incident boxe's frame and flip sign.
  Mat22* RotT = Rot.Transpose();
  Vec2* n = MultiplyVec2(RotT, normal);
  n = n.Negate();
  Vec2* nAbs = n.Abs();

  if (nAbs.x > nAbs.y)
  {
    if (_sign(n.x) > 0.0)
    {
      c[0].v.Set(h.x, -h.y);
      c[0].fp.inEdge2 = EDGE3;
      c[0].fp.outEdge2 = EDGE4;

      c[1].v.Set(h.x, h.y);
      c[1].fp.inEdge2 = EDGE4;
      c[1].fp.outEdge2 = EDGE1;
    }
    else
    {
      c[0].v.Set(-h.x, h.y);
      c[0].fp.inEdge2 = EDGE1;
      c[0].fp.outEdge2 = EDGE2;

      c[1].v.Set(-h.x, -h.y);
      c[1].fp.inEdge2 = EDGE2;
      c[1].fp.outEdge2 = EDGE3;
    }
  }
  else
  {
    if (_sign(n.y) > 0.0)
    {
      c[0].v.Set(h.x, h.y);
      c[0].fp.inEdge2 = EDGE4;
      c[0].fp.outEdge2 = EDGE1;

      c[1].v.Set(-h.x, h.y);
      c[1].fp.inEdge2 = EDGE1;
      c[1].fp.outEdge2 = EDGE2;
    }
    else
    {
      c[0].v.Set(-h.x, -h.y);
      c[0].fp.inEdge2 = EDGE2;
      c[0].fp.outEdge2 = EDGE3;

      c[1].v.Set(h.x, -h.y);
      c[1].fp.inEdge2 = EDGE3;
      c[1].fp.outEdge2 = EDGE4;
    }
  }

  c[0].v = pos.Plus(MultiplyVec2(Rot, c[0].v));
  c[1].v = pos.Plus(MultiplyVec2(Rot, c[1].v));
}

// The normal points from A to B
int Collide(Contact* contacts[], Body* bodyA, Body* bodyB)
{
  // Setup
  Vec2* hA = Vec2.New(bodyA.width_x, bodyA.width_y);
  hA = hA.Scale(0.5);
  Vec2* hB = Vec2.New(bodyB.width_x, bodyB.width_y);
  hB = hB.Scale(0.5);

  Vec2* posA = Vec2.New(bodyA.position_x, bodyA.position_y);
  Vec2* posB = Vec2.New(bodyB.position_x, bodyB.position_y);

  Mat22* RotA = Mat22.NewFromAngle(bodyA.rotation);
  Mat22* RotB = Mat22.NewFromAngle(bodyB.rotation);
  
  Mat22* RotAT = RotA.Transpose();
  Mat22* RotBT = RotB.Transpose();

  Vec2* dp = posB.Minus(posA);
  Vec2* dA = MultiplyVec2(RotAT, dp);
  Vec2* dB = MultiplyVec2(RotBT, dp);

  Mat22* C = RotAT.Multiply(RotB);
  Mat22* absC = C.Abs();
  Mat22* absCT = absC.Transpose();

  // Box A faces - Vec2 faceA = Abs(dA) - hA - absC * hB;
  Vec2* faceA = dA.Abs();
  faceA = faceA.Minus(hA);
  faceA = faceA.Minus(MultiplyVec2(absC, hB));
  
  if (faceA.x > 0.0 || faceA.y > 0.0)
    return 0;

  // Box B faces - Vec2 faceB = Abs(dB) - hB - absCT * hA;
  Vec2* faceB = dB.Abs();
  faceB = faceB.Minus(hB);
  faceB = faceB.Minus(MultiplyVec2(absCT, hA));
  
  if (faceB.x > 0.0 || faceB.y > 0.0)
    return 0;

  // Find best axis
  Axis axis;
  float separation;
  Vec2* normal;

  // Box A faces
  axis = FACE_A_X;
  separation = faceA.x;
  if(dA.x > 0.0) {
    normal = Vec2.New(RotA.col1_x, RotA.col1_y);
  } else {
    normal = Vec2.New(-RotA.col1_x, -RotA.col1_y);
  }
  
  float relativeTol = 0.95;
  float absoluteTol = 0.01;

  if (faceA.y > relativeTol * separation + absoluteTol * hA.y)
  {
    axis = FACE_A_Y;
    separation = faceA.y;
    if(dA.y > 0.0) {
      normal.Set(RotA.col2_x, RotA.col2_y);
    } else {
      normal.Set(-RotA.col2_x, -RotA.col2_y);
    }
  }

  // Box B faces
  if (faceB.x > relativeTol * separation + absoluteTol * hB.x)
  {
    axis = FACE_B_X;
    separation = faceB.x;
    if(dB.x > 0.0) {
      normal.Set(RotB.col1_x, RotB.col1_y);
    } else {
      normal.Set(-RotB.col1_x, -RotB.col1_y);
    }
  }

  if (faceB.y > relativeTol * separation + absoluteTol * hB.y)
  {
    axis = FACE_B_Y;
    separation = faceB.y;
    if(dB.y > 0.0) {
      normal.Set(RotB.col2_x, RotB.col2_y);
    } else {
      normal.Set(-RotB.col2_x, -RotB.col2_y);
    }
  }

  // Setup clipping plane data based on the separating axis
  Vec2* frontNormal;
  Vec2* sideNormal;
  ClipVertex* incidentEdge[] = new ClipVertex[2];
  incidentEdge[0] = ClipVertex.Create();
  incidentEdge[1] = ClipVertex.Create();
  float front, negSide, posSide;
  char negEdge, posEdge;

  // Compute the clipping lines and the line segment to be clipped.
  switch (axis)
  {
  case FACE_A_X:
    {
      frontNormal = normal;
      front = posA.Dot(frontNormal) + hA.x;
      sideNormal = Vec2.New(RotA.col2_x, RotA.col2_y);
      float side = posA.Dot(sideNormal);
      negSide = -side + hA.y;
      posSide =  side + hA.y;
      negEdge = EDGE3;
      posEdge = EDGE1;
      ComputeIncidentEdge(incidentEdge, hB, posB, RotB, frontNormal);
    }
    break;

  case FACE_A_Y:
    {
      frontNormal = normal;
      front = posA.Dot(frontNormal) + hA.y;
      sideNormal = Vec2.New(RotA.col1_x, RotA.col1_y);
      float side = posA.Dot(sideNormal);
      negSide = -side + hA.x;
      posSide =  side + hA.x;
      negEdge = EDGE2;
      posEdge = EDGE4;
      ComputeIncidentEdge(incidentEdge, hB, posB, RotB, frontNormal);
    }
    break;

  case FACE_B_X:
    {
      frontNormal = normal.Negate();
      front = posB.Dot(frontNormal) + hB.x;
      sideNormal = Vec2.New(RotB.col2_x, RotB.col2_y);
      float side = posB.Dot(sideNormal);
      negSide = -side + hB.y;
      posSide =  side + hB.y;
      negEdge = EDGE3;
      posEdge = EDGE1;
      ComputeIncidentEdge(incidentEdge, hA, posA, RotA, frontNormal);
    }
    break;

  case FACE_B_Y:
    {
      frontNormal = normal.Negate();
      front = posB.Dot(frontNormal) + hB.y;
      sideNormal = Vec2.New(RotB.col1_x, RotB.col1_y);
      float side = posB.Dot(sideNormal);
      negSide = -side + hB.x;
      posSide =  side + hB.x;
      negEdge = EDGE2;
      posEdge = EDGE4;
      ComputeIncidentEdge(incidentEdge, hA, posA, RotA, frontNormal);
    }
    break;
  }

  // clip other face with 5 box planes (1 face plane, 4 edge planes)

  ClipVertex* clipPoints1[] = new ClipVertex[2];
  clipPoints1[0] = ClipVertex.Create();
  clipPoints1[1] = ClipVertex.Create();
  ClipVertex* clipPoints2[] = new ClipVertex[2];
  clipPoints2[0] = ClipVertex.Create();
  clipPoints2[1] = ClipVertex.Create();
  int np;

  // Clip to box side 1
  np = ClipSegmentToLine(clipPoints1, incidentEdge, sideNormal.Negate(), negSide, negEdge);

  if (np < 2)
    return 0;

  // Clip to negative box side 1
  np = ClipSegmentToLine(clipPoints2, clipPoints1,  sideNormal, posSide, posEdge);

  if (np < 2)
    return 0;

  // Now clipPoints2 contains the clipping points.
  // Due to roundoff, it is possible that clipping removes all points.

  int numContacts = 0;
  for (int i = 0; i < 2; i++)
  {
    float sep = frontNormal.Dot(clipPoints2[i].v) - front;

    if (sep <= 0.0)
    {
      contacts[numContacts].separation = sep;
      contacts[numContacts].normal = normal;
      // slide contact point onto reference face (easy to cull)
      contacts[numContacts].position.SetV(clipPoints2[i].v.Minus(frontNormal.Scale(sep)));
      contacts[numContacts].feature = clipPoints2[i].fp;
      if (axis == FACE_B_X || axis == FACE_B_Y)
        Flip(contacts[numContacts].feature);

      numContacts++;
    }
  }

  return numContacts;
}


static Arbiter* Arbiter::Create(Body* b1, Body* b2)
{
  Arbiter* a = new Arbiter;
 
  if(b1.mass < FLT_MAX) {
  a.body1 = b1;
  a.body2 = b2;
  } else {
  a.body1 = b2;
  a.body2 = b1;
  }
  
  a.contacts = new Contact[MAX_CONTACTS];
  for(int i=0; i<MAX_CONTACTS; i++) {
    a.contacts[i] = Contact.Create();
  }

  a.numContacts = Collide(a.contacts, a.body1, a.body2);

  a.friction = Maths.Sqrt(a.body1.friction * a.body2.friction);
  return a;
}

void Arbiter::Update(Contact* newContacts[], int numNewContacts)
{
  Contact* mergedContacts[2];
  mergedContacts[0] = Contact.Create();
  mergedContacts[1] = Contact.Create();

  for (int i=0; i < numNewContacts; i++)
  {
    Contact* cNew = newContacts[i];
    int k = -1;
    for (int j=0; j < this.numContacts; j++)
    {
      Contact* cOld = this.contacts[j];
      if (cNew.feature.inEdge1 == cOld.feature.inEdge1 &&
          cNew.feature.inEdge2 == cOld.feature.inEdge2 &&
          cNew.feature.outEdge1 == cOld.feature.outEdge1 &&
          cNew.feature.outEdge2 == cOld.feature.outEdge2 )
      {
        k = j;
        break;
      }
    }

    if (k > -1)
    {
      Contact* cOld = this.contacts[k];
      mergedContacts[i] = cNew;
      if (_warmStarting)
      {
        cNew.Pn = cOld.Pn;
        cNew.Pt = cOld.Pt;
        cNew.Pnb = cOld.Pnb;
      }
      else
      {
        cNew.Pn = 0.0;
        cNew.Pt = 0.0;
        cNew.Pnb = 0.0;
      }
    }
    else
    {
      mergedContacts[i] = newContacts[i];
    }
  }

  for (int i = 0; i < numNewContacts; i++)
    this.contacts[i] = mergedContacts[i];

  this.numContacts = numNewContacts;
}


void Arbiter::ApplyImpulse()
{
  Body* b1 = this.body1;
  Body* b2 = this.body2;

  for (int i=0; i < this.numContacts; i++)
  {
    Contact* c = this.contacts[i];
    c.r1.x = c.position.x - b1.position_x;
    c.r1.y = c.position.y - b1.position_y;
    c.r2.x = c.position.x - b2.position_x;
    c.r2.y = c.position.y - b2.position_y;

    // Relative velocity at contact
    Vec2* tmpa = CrossS2V(b2.angularVelocity, c.r2);
    tmpa.x += b2.velocity_x;
    tmpa.y += b2.velocity_y;
    tmpa.x -= b1.velocity_x;
    tmpa.y -= b1.velocity_y;
    
    Vec2* tmpb = CrossS2V(b1.angularVelocity, c.r1);
    
    Vec2* dv = tmpa.Minus(tmpb);

    // Compute normal impulse
    float vn = dv.Dot(c.normal);

    float dPn = c.massNormal * (-vn + c.bias);

    if (_accumulateImpulses)
    {
      // Clamp the accumulated impulse
      float Pn0 = c.Pn;
      c.Pn = _max(Pn0 + dPn, 0.0);
      dPn = c.Pn - Pn0;
    }
    else
    {
      dPn = _max(dPn, 0.0);
    }

    // Apply contact impulse
    Vec2* Pn = c.normal.Scale(dPn);

    b1.velocity_x -= b1.invMass * Pn.x;
    b1.velocity_y -= b1.invMass * Pn.y;
    b1.angularVelocity -= b1.invI * c.r1.Cross(Pn);

    b2.velocity_x += b2.invMass * Pn.x;
    b2.velocity_y += b2.invMass * Pn.y;
    b2.angularVelocity += b2.invI * c.r2.Cross(Pn);
    
    // Relative velocity at contact
    
    tmpa = CrossS2V(b2.angularVelocity, c.r2);
    tmpa.x += b2.velocity_x;
    tmpa.y += b2.velocity_y;
    tmpa.x -= b1.velocity_x;
    tmpa.y -= b1.velocity_y;
    
    tmpb = CrossS2V(b1.angularVelocity, c.r1);
    
    dv = tmpa.Minus(tmpb);

    Vec2* tangent = CrossV2S(c.normal, 1.0);
    float vt = dv.Dot(tangent);
    float dPt = c.massTangent * (-vt);

    if (_accumulateImpulses)
    {
      // Compute friction impulse
      float maxPt = this.friction * c.Pn;

      // Clamp friction
      float oldTangentImpulse = c.Pt;
      c.Pt = _clamp(oldTangentImpulse + dPt, -maxPt, maxPt);
      dPt = c.Pt - oldTangentImpulse;// _clamp(c.Pt - oldTangentImpulse, -maxPt, maxPt);
    }
    else
    {
      float maxPt = this.friction * dPn;
      dPt = _clamp(dPt, -maxPt, maxPt);
    }

    // Apply contact impulse
    Vec2* Pt = tangent.Scale(dPt);

    b1.velocity_x -= b1.invMass * Pt.x;
    b1.velocity_y -= b1.invMass * Pt.y;
    b1.angularVelocity -= b1.invI * c.r1.Cross(Pt);

    b2.velocity_x += b2.invMass * Pt.x;
    b2.velocity_y += b2.invMass * Pt.y;
    b2.angularVelocity += b2.invI * c.r2.Cross(Pt);
  }
}

void Arbiter::PreStep(float inv_dt)
{
  float k_allowedPenetration = 0.01;
  float k_biasFactor = 0.0;
  if(_positionCorrection) k_biasFactor = 0.2;

  for (int i = 0; i < this.numContacts; i++)
  {
    Contact* c = this.contacts[i];

    Vec2* r1 = new Vec2;
    Vec2* r2 = new Vec2;
    
    r1.x = c.position.x - this.body1.position_x;
    r1.y = c.position.y - this.body1.position_y;
    r2.x = c.position.x - this.body2.position_x;
    r2.y = c.position.y - this.body2.position_y;

    // Precompute normal mass, tangent mass, and bias.
    float rn1 = r1.Dot(c.normal);
    float rn2 = r2.Dot(c.normal);
    float kNormal = this.body1.invMass + this.body2.invMass;
    kNormal += this.body1.invI * (r1.Dot(r1) - rn1 * rn1) + this.body2.invI * (r2.Dot(r2) - rn2 * rn2);
    c.massNormal = 1.0 / kNormal;

    Vec2* tangent = CrossV2S(c.normal, 1.0);
    float rt1 = r1.Dot(tangent);
    float rt2 = r2.Dot(tangent);
    float kTangent = this.body1.invMass + this.body2.invMass;
    kTangent += this.body1.invI * (r1.Dot(r1) - rt1 * rt1) + this.body2.invI * (r2.Dot(r2) - rt2 * rt2);
    c.massTangent = 1.0 /  kTangent;

    c.bias = -k_biasFactor * inv_dt * _min(0.0, c.separation + k_allowedPenetration);

    if (_accumulateImpulses)
    {
      // Apply normal + friction impulse
      Vec2* ppp = tangent.Scale(c.Pt);
      Vec2* pp = c.normal.Scale(c.Pn);
      Vec2* P = pp.Plus(ppp);
      pp = null; ppp = null;

      this.body1.velocity_x -= this.body1.invMass * P.x;
      this.body1.velocity_y -= this.body1.invMass * P.y;      
      this.body1.angularVelocity -= this.body1.invI * r1.Cross(P);

      this.body2.velocity_x += this.body2.invMass * P.x;
      this.body2.velocity_y += this.body2.invMass * P.y;
      this.body2.angularVelocity += this.body2.invI * r2.Cross(P);
    }
  }
}

void World::BroadPhase()
{
  // O(n^2) broad-phase
  for (int i = 0; i < this.body_count; i++)
  {
    Body* bi = this.bodies[i];

    for (int j = i + 1; j < this.body_count; j++)
    {
      Body* bj = this.bodies[j];

      if (bi.invMass == 0.0 && bj.invMass == 0.0)
        continue;

      Arbiter* newArb = Arbiter.Create(bi, bj);

      if (newArb.numContacts > 0)
      {
        Arbiter* arb = this.arbiters.Get(bi, bj);
        if (arb == null)
        {
          this.arbiters.Add(newArb);
        }
        else
        {
          arb.Update(newArb.contacts, newArb.numContacts);
        }
      }
      else
      {
        this.arbiters.Remove(bi, bj);
      }
    }
  }
}

void World::Step(float dt)
{
  float inv_dt = 0.0;
  if(dt > 0.0) {
    inv_dt = 1.0 / dt;
  }

  // Determine overlapping bodies and update contact points.
  this.BroadPhase();

  // Integrate forces.
  for (int i = 0; i < this.body_count; i++)
  {
    Body* b = this.bodies[i];

    if (b.invMass == 0.0)
      continue;

    b.velocity_x += dt * (this.gravity.x + b.invMass * b.force_x);
    b.velocity_y += dt * (this.gravity.y + b.invMass * b.force_y);
    b.angularVelocity += dt * b.invI * b.torque;
  }

  // Perform pre-steps. 
  for (int i=0; i< this.arbiters.a_count; i++)
  {
    Arbiter* arb = this.arbiters.a[i];
    arb.PreStep(inv_dt);
  }

  for (int i = 0; i < this.joint_count; i++)
  {
    this.joints[i].PreStep(inv_dt);
  }

  // Perform iterations
  for (int i=0; i < this.iterations; i++)
  {
    for (int j=0; j< this.arbiters.a_count; j++)
    {
      Arbiter* arb = this.arbiters.a[j];
      arb.ApplyImpulse();
    }

    for (int j=0; j < this.joint_count; j++)
    {
      this.joints[j].ApplyImpulse();
    }
  }

  // Integrate Velocities
  for (int i = 0; i < this.body_count; i++)
  {
    Body* b = this.bodies[i];

    b.position_x += dt * b.velocity_x;
    b.position_y += dt * b.velocity_y;
    b.rotation += dt * b.angularVelocity;

    b.force_x = 0.0;
    b.force_y = 0.0;
    b.torque = 0.0;
  }
}

void World::Init(float gravity_x, float gravity_y, int iterations)
{
  this.Clear();
  this.gravity = Vec2.New(gravity_x, gravity_y);
  this.iterations = iterations;
}
 
void game_start()
{
  _accumulateImpulses = true;
  _warmStarting = true;
  _positionCorrection = true;
}
[close]

Usage, room script example

Code: ags
// room script file
#define MAX_STUFF 32

#define SCALE 16.0

World world;
float step;
struct Thing {
  Body* b;
  Overlay* ovr;
  DynamicSprite* dnspr;
  
  import void InitFromBody(Body* b);
  import void Render();
};
Thing things[MAX_STUFF];
int thing_count;
Overlay* scr_ovr;
DynamicSprite* scr_spr;

void DrawContacts()
{
  if(scr_ovr == null) {
    scr_spr = DynamicSprite.Create(Screen.Width, Screen.Height);
    scr_ovr = Overlay.CreateGraphical(0, 0, scr_spr.Graphic);
    scr_ovr.ZOrder = 1000;
  }
  
  DrawingSurface* surf = scr_spr.GetDrawingSurface();
  surf.Clear(COLOR_TRANSPARENT);
  surf.DrawingColor = 61871;
  
  for(int arsi=0; arsi<world.arbiters.a_count; arsi++) {
    Arbiter* arb = world.arbiters.a[arsi];
    for(int i=0; i<arb.numContacts; i++) {
      int cnx = FloatToInt(arb.contacts[i].position.x*SCALE)+Screen.Width/2;
      int cny = FloatToInt(arb.contacts[i].position.y*SCALE)+Screen.Height/2;
      surf.DrawCircle(cnx, cny, 3);
    }  
  }
}

void Thing::InitFromBody(Body* b) {
  this.b = b;
  
  DrawingSurface* surf;
  int w = FloatToInt(b.width_x*SCALE);
  int h = FloatToInt(b.width_y*SCALE);
  this.dnspr = DynamicSprite.Create(w, h);
  surf = this.dnspr.GetDrawingSurface();
  surf.Clear(256 + Random(65504));
  if(w > 2 && h > 2) {
    surf.DrawingColor = 256 + Random(65504);
    surf.DrawRectangle(1, 1, w-2, h-2);
  }
  surf.Release();
  
  this.ovr = Overlay.CreateGraphical(0, 0, this.dnspr.Graphic);
  this.ovr.X = FloatToInt((this.b.position_x - this.b.width_x/2.0)*SCALE)+Screen.Width/2;
  this.ovr.Y = FloatToInt((this.b.position_y - this.b.width_y/2.0)*SCALE)+Screen.Height/2;
}

void Thing::Render() {
  int w = FloatToInt((this.b.width_x)*SCALE);
  int h = FloatToInt((this.b.width_y)*SCALE);
  this.ovr.Graphic = this.dnspr.Graphic;
  this.ovr.X = FloatToInt((this.b.position_x - this.b.width_x/2.0)*SCALE)+Screen.Width/2;
  this.ovr.Y = FloatToInt((this.b.position_y - this.b.width_y/2.0)*SCALE)+Screen.Height/2;
  this.ovr.Width = w;
  this.ovr.Height = h;
  this.ovr.Rotation = Maths.RadiansToDegrees(this.b.rotation);
}

void NewBody(float x, float y, float width, float height, float mass) {
  Body* b = Body.Create();
  b.Set(Vec2.New(width, height), mass);
  b.position_x = x;
  b.position_y = y;
  
  things[thing_count].InitFromBody(b);
  thing_count++;
	world.AddBody(b);
}

void RenderAll() {
  for(int i=0; i<thing_count; i++) {
    things[i].Render();
  }
  DrawContacts();
}

function room_Load()
{
  world.Init(0.0, 10.0);
  step = 1.0/IntToFloat(GetGameSpeed());
  
  NewBody(0.0, 0.5*20.0, 100.0, 20.0,   FLT_MAX);
  NewBody(0.0,       -4.0,   1.0,  1.0,   200.0);
  NewBody(1.0,       -6.0,   1.0,  1.0,   200.0);
  NewBody(2.0,       -5.0,   1.0,  1.0,   200.0);
  NewBody(1.5,       -2.0,   1.0,  1.0,   800.0);
  NewBody(2.4,       -9.0,   1.0,  1.0,   200.0);
  NewBody(1.4,       -10.0,   1.0,  1.0,   200.0);
  NewBody(2.8,       -12.0,   1.0,  1.0,   200.0);
  NewBody(1.8,       -14.0,   1.0,  1.0,   200.0);
  NewBody(0.8,       -12.0,   1.0,  1.0,   200.0);
}

bool start;
function room_RepExec()
{
  if(!start) {
    Display("START");
    start = !start;
  }
  
  world.Step(step);

  RenderAll();
}


Spoiler
at first it wasn't working...
[close]

It's working!!!

SMF spam blocked by CleanTalk