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

Messages - eri0o

#141
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.
#142
In which function in global script are you using this? What do you mean by the value of name is 0?
#143
Does any error appears on the bottom of the Editor? (Something like "Unable to load")

Also have you installed the Editor or have you just run the ags editor from a zip? If you haven't installed AGS Editor ever before, you may be missing vc redistributables.
#144
Thanks @Nahuel ! I really want to write a bit how everything works at some point, just been a bit short on time recently, but hopefully at some point in the next month.
#145
Can you specify which ags version you are using? Is it possible to share the video file?
#146
Hi, there are different boards with different things going on, if you and the team like jams, there's the MAGS (Monthly AGS), which should always have an interesting theme to help you get inspired. Welcome to the AGS community and hope you have fun here!
#147
Just to note here, but recently I was thinking about cache locality and how this plays out with local memory inside a function and member variables in a struct, so I foundout that the order of member variables in a struct is not optimized by the compiler - apparently for compatibility with some serialization assumptions from C. But I found this super fun VS extension on the marketplace:

https://github.com/Viladoman/StructLayout

So I decided to play around with the order of the members of the cc_instance struct and test what this would change regarding fps on these script intensive games running with unlimited fps.

So, with current improvements, for CW space game I get normally 108fps, I managed to make things worse enough to get 98fps. I could not find any order that makes things faster. But, this is one more hidden thing to lookout for when changing things that are ran a LOT of times per frame.

Ah right, in modern processors, according to information on the internet, a cache line is around 64 bytes, so if you can make things that operate accessing only this much memory locally, you get some performance improvements.

So from all this, I feel like reducing branching is a better place to find hidden performance gains so far. Something more for the future, just wanted to note down these things as they were new for me.

Edit: had an idea to change the order of the instructions in the switch, if I put the instructions in the same order as their values (SCMD_ADD first, ...) then I get ~2 fps improvement (108fps->110fps), in the space game.

Also found an interesting old thread and source code.
#148
I was thinking here, and if we want at some point have both the script runner with checks and without checks in the same engine, it could be done with some preprocessor tricks by separating the script runner in it's own file and including it twice with different defines - allegro library does this. There's maybe some way with c++ templates to do this too, but I don't know much how to use those in C++11 restricted land.

Obviously this is something for a future, what-if case. The results so far is really good! I am sure there are ways to push things further but probably with more complicated rewrites - maybe the approach from Nick's branch, but now I am unsure if the speed improvements from his branch is from not making the checks too.

If we are free to change compiler and interpreter, it looks like we are not benefiting from the register nature as much as we could, as there are stack like operations to pass certain types of values.
#149
Hey, so far, desktop only, but I added a web playable version of the game. Hopefully it helps if people can't run the binary on desktop for whatever reason. It can only be played in fullscreen.
#150
@Crimson Wizard I tried some of my games, apparently most of my stuff is bounded by the drawing operations, but my ImGi module benefited enormously by your changes.

This module has it's own software render where each drawing command is hashed and it only draws the rectangles where the hash has changed and this math is still reasonably expensive on regular AGS but with your changes the processing in those is more than cut in half.
#151
Thanks for the ideas CW, I am still thinking about them.


I want to have an actual release as module later but there's a lot to do until then. In the meantime, I just made the repository public, just to have a reference to the code somewhere else beyond the forums.

https://github.com/ericoporto/simple_physics

Maybe even in this bad stage it can be useful for someone. Noted a rough draft of intentions in the README.
#152
I usually use https://www.mentimeter.com/

But privacy friendly is not a thing there I think.
#153
Quote from: Crimson Wizard on Thu 27/04/2023 01:42:01if AGS script had some kind of a 3D polygon API, and engine did the main calculation/drawing internally, then things could be done much more efficiently.

SDL2 has a polygon API, but it depends on the SDL2 renderer. In SDL3 it should be rewritten to use it's new SDL GPU backend, which is a new API in development in SDL3 for a generic, shader first, 3D rendering - in Metal/Vulkan/Direct12 first spirit. Just mentioning, in case we ever want to walk that road, it should be easier in a not so far future.
#154
I kinda want to figure out a very simple actual game I can use this to add a bit motivation.

Code wise I looked into a few things

  • adding circles
  • adding rounded rectangles (capsules)
  • optimize broadphase
  • optimize AGS Script
  • simplify API
#155


Minor update! Got the interaction between static and dynamic objects working! Now need to figure out what is causing dynamic-dynamic interactions to be occasionally explosive!

Edit: found the error! it's working now, updating the first topic with t he current library. It's going to be a little while until I post the actual more final thing in modules since this is ags4 only.
#156
minor updates above...



Still not working but thought the failure was still fun. I think I am positioning and calculating the contacts wrong in some way, not sure how yet.
#157
Hey, after applying this, would pursuing a refactor to port the script rewrite idea from Nick's branch much harder or the same difficulty? Because if it doesn't change much, perhaps applying it and later figuring out how to recover some performance by applying that approach would be a way to go.
#158
Hey, when I played with that Nick branch I kinda didn't push it to GitHub after properly recovering it and lost my work. If you had a somewhat working version of it, I advise to push to GitHub - or somewhere, just to have a backup.
#159
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!!!

#160
I found another text for the same pictures in the Python GC article here: https://csrgxtu.github.io/2020/02/18/CPython-s-Garbage-Collector/

Reading this shorter version I could understand it better how it works. (there is also a for dummies video here, but it doesn't go into specifics)

Spoiler
thinking about the concept of generations Python GC implements, for optimization, if we want to do that at some point, it could be like, a collection that runs per frame and the old generation is collected in room transitions.

thinking about the basic of the garbage collection, I wonder if it needs the managed pool, the rtti for cross-object references, but not the object themselves for it to work, meaning it can be implemented reasonably separated - which I guess is what you meant when you said the same thing.

Design wise, maybe the GC holds a reference to both things. And I was thinking, that, in the text, when it's designing the memory layout, it mentions that because it's a better performance to do that way instead of the GC holding a map that would contain the objects and their extra properties (gc_ref), and also because this avoids the need to keep things in sync.
[close]

Quote from: Crimson Wizard on Wed 19/04/2023 00:42:00Comparing these two, I'd rather first try the one that does not require this.

Agreed.
SMF spam blocked by CleanTalk