openclonk/src/object/C4Rope.cpp

1078 lines
33 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2012 Armin Burgmeier
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#include <C4Include.h>
#include <C4Landscape.h>
#include <C4Rope.h>
#define C4ROPE_DRAW_DEBUG
namespace
{
struct Vertex {
Vertex() {}
Vertex(float x, float y): x(x), y(y) {}
float x;
float y;
};
struct DrawVertex: Vertex {
float u;
float v;
};
// TODO: If the sqrts become a performance bottleneck we could also use an
// approximation which works without Sqrt, cf. http://www.azillionmonkeys.com/qed/sqroot.html
C4Real Len(C4Real dx, C4Real dy)
{
// Prevent possible overflow
if(Abs(dx) > 120 || Abs(dy) > 120)
return itofix(SqrtI(fixtoi(dx)*fixtoi(dx) + fixtoi(dy)*fixtoi(dy)));// ftofix(sqrt(fixtoi(dx)*fixtoi(dx) + fixtoi(dy)*fixtoi(dy)));
else
return Sqrt(dx*dx + dy*dy);//ftofix(sqrt(fixtof(dx*dx + dy*dy)));
}
// For use in initializer list
C4Real ObjectDistance(C4Object* first, C4Object* second)
{
C4Real dx = second->fix_x - first->fix_x;
C4Real dy = second->fix_y - first->fix_y;
return Len(dx, dy);
}
// Helper function for Draw: determines vertex positions for one segment
void VertexPos(Vertex& out1, Vertex& out2, Vertex& out3, Vertex& out4,
const Vertex& v1, const Vertex& v2, float w)
{
// This is for graphics only, so plain sqrt() is OK.
const float l = sqrt( (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y));
out1.x = v1.x + w/2.0f * (v1.y - v2.y) / l;
out1.y = v1.y - w/2.0f * (v1.x - v2.x) / l;
out2.x = v1.x - w/2.0f * (v1.y - v2.y) / l;
out2.y = v1.y + w/2.0f * (v1.x - v2.x) / l;
out3.x = v2.x + w/2.0f * (v1.y - v2.y) / l;
out3.y = v2.y - w/2.0f * (v1.x - v2.x) / l;
out4.x = v2.x - w/2.0f * (v1.y - v2.y) / l;
out4.y = v2.y + w/2.0f * (v1.x - v2.x) / l;
}
// Copied from StdGL.cpp... actually the rendering code should be moved
// there so we don't need to duplicate it here.
bool ApplyZoomAndTransform(float ZoomX, float ZoomY, float Zoom, C4BltTransform* pTransform)
{
// Apply zoom
glTranslatef(ZoomX, ZoomY, 0.0f);
glScalef(Zoom, Zoom, 1.0f);
glTranslatef(-ZoomX, -ZoomY, 0.0f);
// Apply transformation
if (pTransform)
{
const GLfloat transform[16] = { pTransform->mat[0], pTransform->mat[3], 0, pTransform->mat[6], pTransform->mat[1], pTransform->mat[4], 0, pTransform->mat[7], 0, 0, 1, 0, pTransform->mat[2], pTransform->mat[5], 0, pTransform->mat[8] };
glMultMatrixf(transform);
// Compute parity of the transformation matrix - if parity is swapped then
// we need to cull front faces instead of back faces.
const float det = transform[0]*transform[5]*transform[15]
+ transform[4]*transform[13]*transform[3]
+ transform[12]*transform[1]*transform[7]
- transform[0]*transform[13]*transform[7]
- transform[4]*transform[1]*transform[15]
- transform[12]*transform[5]*transform[3];
return det > 0;
}
return true;
}
// PathFinder callback function. This saves the first and last waypoint which
// determine the direction toward which to pull.
struct PullPathInfo
{
bool FirstSeg;
const int32_t bx, by;
const int32_t ex, ey;
int32_t px, py;
int32_t nx, ny;
C4Real d;
};
bool PullPathAccumulator(int32_t iX, int32_t iY, intptr_t iTransferTarget, intptr_t PathInfoPtr)
{
PullPathInfo* Info = (PullPathInfo*)PathInfoPtr;
// Ignore points which are very close (~7 pixels) to start or end. Such
// points are often misleading because the PathFinder first pulls away a
// bit from the ground before actually moving toward the destination.
if(Info->px != iX || Info->py != iY)
{
if(Info->FirstSeg)
{
if( (Info->ex - iX) * (Info->ex - iX) + (Info->ey - iY) * (Info->ey - iY) > 50)
{
Info->nx = iX;
Info->ny = iY;
Info->FirstSeg = false;
}
}
if( (Info->bx - iX) * (Info->bx - iX) + (Info->by - iY) * (Info->by - iY) > 50)
{
C4Real dx = itofix(Info->px - iX);
C4Real dy = itofix(Info->py - iY);
Info->px = iX;
Info->py = iY;
Info->d += Len(dx, dy);
}
}
return true; // note return value is ignored
}
}
C4RopeElement::C4RopeElement(C4Object* obj, bool fixed):
Fixed(fixed), oldx(obj->fix_x), oldy(obj->fix_y), fx(Fix0), fy(Fix0),
rx(Fix0), ry(Fix0), rdt(Fix0), fcx(Fix0), fcy(Fix0),
Next(NULL), Prev(NULL), FirstLink(NULL), LastLink(NULL),
Object(obj), LastContactVertex(-1)
{
}
C4RopeElement::C4RopeElement(C4Real x, C4Real y, C4Real m, bool fixed):
Fixed(fixed), x(x), y(y), oldx(x), oldy(y), vx(Fix0), vy(Fix0), m(m),
fx(Fix0), fy(Fix0), rx(Fix0), ry(Fix0), rdt(Fix0), fcx(Fix0), fcy(Fix0),
Next(NULL), Prev(NULL), FirstLink(NULL), LastLink(NULL),
Object(NULL), LastContactVertex(-1)
{
}
C4RopeElement::~C4RopeElement()
{
for(C4RopeLink* link = FirstLink, *next; link != NULL; link = next)
{
next = link->Next;
delete link;
}
}
void C4RopeElement::AddForce(C4Real x, C4Real y)
{
fx += x;
fy += y;
#ifdef C4ROPE_DRAW_DEBUG
fcx += x;
fcy += y;
#endif
}
C4Real C4RopeElement::GetTargetX() const
{
// TODO: Prevent against object changes: Reset when Target changes wrt
// to the object LastContactVertex refers to.
if(LastContactVertex == -1) return Object->fix_x;
C4Object* obj = Object;
while(obj->Contained)
obj = obj->Contained;
return obj->fix_x + itofix(obj->Shape.VtxX[LastContactVertex]);
}
C4Real C4RopeElement::GetTargetY() const
{
// TODO: Prevent against object changes: Reset when Target changes wrt
// to the object LastContactVertex refers to.
if(LastContactVertex == -1) return Object->fix_y;
C4Object* obj = Object;
while(obj->Contained)
obj = obj->Contained;
return obj->fix_y + itofix(obj->Shape.VtxY[LastContactVertex]);
}
bool C4RopeElement::InsertLinkPosition(int from_x, int from_y, int to_x, int to_y, int link_x, int link_y, int& insert_x, int& insert_y)
{
if(PathFree(to_x, to_y, link_x, link_y)) return false;
// Sanity check, these might be violated if the landscape changed.
// In that case the rope might end up buried in earth, in which case we
// do not want to do any maneuvering around, but the rope is just stuck.
if(!PathFree(from_x, from_y, link_x, link_y)) return false;
// If the element has been moved through solid material, only take the part
// of the way into account that is free.
int stop_x, stop_y;
if(!PathFree(from_x, from_y, to_x, to_y, &stop_x, &stop_y))
{
to_x = stop_x;
to_y = stop_y;
}
// Find the position between from_x,from_y, and to_x,to_y
// where there is no path free to link_x,link_y anymore.
int prev_x = from_x;
int prev_y = from_y;
int coll_x = -1;
int coll_y = -1;
int max_p = Max(abs(to_x - from_x), abs(to_y - from_y));
for(int i = 1; i <= max_p; ++i)
{
int inter_x = from_x + i * (to_x - from_x) / max_p;
int inter_y = from_y + i * (to_y - from_y) / max_p;
if(!PathFree(inter_x, inter_y, link_x, link_y))
{
coll_x = inter_x;
coll_y = inter_y;
break;
}
prev_x = inter_x;
prev_y = inter_y;
}
// Such a position must have been found, because of the initial
// PathFree(to_x, to_y, link_x, link_y) check.
assert(coll_x != -1 && coll_y != -1);
// Now we have:
// prev_x,prev_y: Last position between from_x,from_y and to_x,to_y
// where the path to link_x,link_y is still free.
// coll_x,coll_y: First position between from_x,from_y and to_x,to_y
// where the path to link_x,link_y is no longer free.
// prev_x,prev_y and coll_x,coll_y are 1 pixel apart from each other.
// Now the idea is to insert a new link somewhere on the line from
// prev_x,prev_y to link_x,link_y such that
// PathFree(coll_x, coll_y, insert_x, insert_y) holds and the point is
// as close to link_x, link_y as possible.
max_p = Max(abs(link_x - prev_x), abs(link_y - prev_y));
for(int i = 1; i <= max_p; ++i)
{
int inter_x = link_x + i * (prev_x - link_x) / max_p;
int inter_y = link_y + i * (prev_y - link_y) / max_p;
// Again a sanity check, to account for pixel rounding mismatches
if(!PathFree(inter_x, inter_y, link_x, link_y)) continue;
if(PathFree(coll_x, coll_y, inter_x, inter_y))
{
insert_x = inter_x;
insert_y = inter_y;
return true;
}
}
// The final point in the loop is prev_x, prev_y, and there should be a
// free path from it, as per the sanity check precondition.
assert(false);
return false;
}
void C4RopeElement::InsertLink(C4RopeElement* from, C4RopeElement* to, int insert_x, int insert_y)
{
assert(this == from || this == to);
C4RopeLink* Link = new C4RopeLink;
Link->x = itofix(insert_x);
Link->y = itofix(insert_y);
if(this == from)
{
assert(from->Next == to);
Link->Next = from->FirstLink;
from->FirstLink = Link;
Link->Prev = NULL;
if(to->LastLink == NULL)
to->LastLink = Link;
}
else
{
assert(to->Prev == from);
Link->Prev = to->LastLink;
to->LastLink = Link;
Link->Next = NULL;
if(from->FirstLink == NULL)
from->FirstLink = Link;
}
}
void C4RopeElement::Execute(const C4Rope* rope, C4Real dt)
{
ResetForceRedirection(dt);
// If attached object is contained, apply force to container
C4Object* Target = Object;
if(Target)
while(Target->Contained)
Target = Target->Contained;
// Apply sticking friction
if(!Target)
{
// Sticking friction: If a segment has contact with the landscape and it
// is at rest then one needs to exceed a certain threshold force until it
// starts moving.
int ix = fixtoi(x);
int iy = fixtoi(y);
if(GBackSolid(ix+1, iy) || GBackSolid(ix-1, iy) || GBackSolid(ix, iy-1) || GBackSolid(ix, iy+1))
{
if(vx*vx + vy*vy < Fix1/4) // TODO: Threshold should be made a property
{
if(fx*fx + fy*fy < Fix1/4) // TODO: Threshold should be made a property
{
fx = fy = Fix0;
vx = vy = Fix0;
return;
}
}
}
}
// Apply forces
if(!Fixed)
{
if(!Target)
{
vx += dt * fx / m;
vy += dt * fy / m;
}
else if( (Target->Category & C4D_StaticBack) == 0)
{
// Only apply xdir/ydir to targets if they are not attached
if((Target->Action.t_attach & (CNAT_Left | CNAT_Right)) == 0)
Target->xdir += dt * fx / Target->Mass;
if((Target->Action.t_attach & (CNAT_Top | CNAT_Bottom)) == 0)
Target->ydir += dt * fy / Target->Mass;
}
}
fx = fy = Fix0;
// Execute movement
if(!Target)
{
// Compute old and new coordinates
int old_x = fixtoi(x);
int old_y = fixtoi(y);
int new_x = fixtoi(x + dt * vx);
int new_y = fixtoi(y + dt * vy);
// Maximum distance in pixels in either X or Y
int max_p = Max(abs(new_x - old_x), abs(new_y - old_y));
// Check all pixels between old and new position
int prev_x = old_x;
int prev_y = old_y;
bool hit = false;
for(int i = 1; i <= max_p; ++i)
{
// Intermediate pixel position
int inter_x = old_x + i * (new_x - old_x) / max_p;
int inter_y = old_y + i * (new_y - old_y) / max_p;
// Collision check
if(GBackSolid(inter_x, inter_y))
{
x = itofix(prev_x);
y = itofix(prev_y);
hit = true;
// Apply friction force
fx -= rope->GetOuterFriction() * vx; fy -= rope->GetOuterFriction() * vy;
// Force redirection so that not every single pixel on a
// chunky landscape is an obstacle for the rope.
SetForceRedirection(rope, 0, 0);
break;
}
prev_x = inter_x;
prev_y = inter_y;
}
if(!hit)
{
x += dt * vx;
y += dt * vy;
}
}
else
{
if(!Fixed)
{
// Object Force redirection if object has no contact attachment (if it
// has then the procedure takes care of moving the object around
// O(pixel) obstacles in the landscape).
if(!Target->Action.t_attach && Target->xdir*Target->xdir + Target->ydir*Target->ydir >= Fix1)
{
// Check if the object has contact to the landscape
//long iResult = 0;
const DWORD dwCNATCheck = CNAT_Left | CNAT_Right | CNAT_Top | CNAT_Bottom;
int iContactVertex = -1;
for (int i = 0; i < Target->Shape.VtxNum; ++i)
if(Target->Shape.GetVertexContact(i, dwCNATCheck, Target->GetX(), Target->GetY()))
iContactVertex = i;
if(iContactVertex != -1)
{
LastContactVertex = iContactVertex;
SetForceRedirection(rope, Target->Shape.VtxX[iContactVertex], Target->Shape.VtxY[iContactVertex]);
}
}
}
}
}
void C4RopeElement::ResetForceRedirection(C4Real dt)
{
// countdown+reset force redirection
if(rdt != Fix0)
{
if(dt > rdt)
{
rx = ry = Fix0;
rdt = Fix0;
}
else
{
rdt -= dt;
}
}
}
void C4RopeElement::SetForceRedirection(const C4Rope* rope, int ox, int oy)
{
SetForceRedirectionByLookAround(rope, ox, oy, GetVx(), GetVy(), itofix(5), itofix(75));
#if 0
if(!SetForceRedirectionByLookAround(rope, ox, oy, GetVx(), GetVy(), itofix(5), itofix(15)))
if(!SetForceRedirectionByLookAround(rope, ox, oy, GetVx(), GetVy(), itofix(5), itofix(30)))
SetForceRedirectionByLookAround(rope, ox, oy, GetVx(), GetVy(), itofix(5), itofix(45));
//SetForceRedirectionByLookAround(rope, ox, oy, GetVx(), GetVy(), itofix(5), itofix(60));
#endif
}
bool C4RopeElement::SetForceRedirectionByLookAround(const C4Rope* rope, int ox, int oy, C4Real dx, C4Real dy, C4Real l, C4Real angle)
{
// The procedure is the following: we try manuevering around
// the obstacle, either left or right. Which way we take is determined by
// checking whether or not there is solid material in a 75 degree angle
// relative to the direction of movement.
const C4Real Cos75 = Cos(angle);
const C4Real Sin75 = Sin(angle);
C4Real vx1 = Cos75 * dx + Sin75 * dy;
C4Real vy1 = -Sin75 * dx + Cos75 * dy;
C4Real vx2 = Cos75 * dx - Sin75 * dy;
C4Real vy2 = Sin75 * dx + Cos75 * dy;
const C4Real v = Len(dx, dy);
// TODO: We should check more than a single pixel. There's some more potential for optimization here.
if(v != Fix0 && !GBackSolid(ox + fixtoi(GetX() + vx1*l/v), oy + fixtoi(GetY() + vy1*l/v)))
//if(v != Fix0 && PathFree(ox + fixtoi(GetX()), oy + fixtoi(GetY()), ox + fixtoi(GetX() + vx1*l/v), oy + fixtoi(GetY() + vy1*l/v)))
{ rx = vx1/v; ry = vy1/v; rdt = Fix1/4; } // Enable force redirection for 1/4th of a frame
else if(v != Fix0 && !GBackSolid(ox + fixtoi(GetX() + vx2/v), oy + fixtoi(GetY() + vy2/v)))
//else if(v != Fix0 && PathFree(ox + fixtoi(GetX()), oy + fixtoi(GetY()), ox + fixtoi(GetX() + vx2/v), oy + fixtoi(GetY() + vy2/v)))
{ rx = vx2/v; ry = vy2/v; rdt = Fix1/4; } // Enable force redirection for 1/4th of a frame
else
return false;
return true;
}
C4Rope::C4Rope(C4PropList* Prototype, C4Object* first_obj, C4Object* second_obj, C4Real segment_length, C4DefGraphics* graphics):
C4PropListNumbered(Prototype), Width(5.0f), Graphics(graphics), SegmentCount(fixtoi(ObjectDistance(first_obj, second_obj)/segment_length)),
l(segment_length), k(Fix1*3), mu(Fix1*3), eta(Fix1*3), NumIterations(10),
FrontAutoSegmentation(Fix0), BackAutoSegmentation(Fix0), FrontPull(Fix0), BackPull(Fix0)
{
if(!PathFree(first_obj->GetX(), first_obj->GetY(), second_obj->GetX(), second_obj->GetY()))
throw C4RopeError("Path between objects is blocked");
if(Graphics->Type != C4DefGraphics::TYPE_Bitmap)
throw C4RopeError("Can only use bitmap as rope graphics");
Front = new C4RopeElement(first_obj, false);
Back = new C4RopeElement(second_obj, false);
const C4Real m(Fix1); // TODO: This should be a property
C4RopeElement* prev_seg = Front;
for(int32_t i = 0; i < SegmentCount; ++i)
{
// Create new element
C4Real seg_x = first_obj->fix_x + (second_obj->fix_x - first_obj->fix_x) * (i+1) / (SegmentCount+1);
C4Real seg_y = first_obj->fix_y + (second_obj->fix_y - first_obj->fix_y) * (i+1) / (SegmentCount+1);
C4RopeElement* seg = new C4RopeElement(seg_x, seg_y, m, false);
// Link it
seg->Prev = prev_seg;
prev_seg->Next = seg;
prev_seg = seg;
}
// Link back segment
prev_seg->Next = Back;
Back->Prev = prev_seg;
}
C4Rope::~C4Rope()
{
for(C4RopeElement* cur = Front, *next; cur != NULL; cur = next)
{
next = cur->Next;
delete cur;
}
}
C4Real C4Rope::GetL(const C4RopeElement* prev, const C4RopeElement* next) const
{
// Normally the segment length is fixed at l, however if auto segmentation
// is enabled then the first or last segments can be shorter.
const C4Real dx = next->GetX() - prev->GetX();
const C4Real dy = next->GetY() - prev->GetY();
if(FrontAutoSegmentation > Fix0)
if(prev == Front || next == Front)
return Min(itofix(5), Len(dx, dy));
if(BackAutoSegmentation > Fix0)
if(prev == Back || next == Back)
return Min(itofix(5), Len(dx, dy));
return l;
}
void C4Rope::DoAutoSegmentation(C4RopeElement* fixed, C4RopeElement* first, C4Real max)
{
// TODO: Should add a timeout to prevent oscillations: After one segment was
// inserted do not allow segments to be removed in the same frame or couple
// of frames, and vice versa. This gives the system a chance to get into
// equilibrium before continuing with auto-segmentation.
// Auto segmentation enabled?
if(max > Fix0)
{
const C4Real dx = first->GetX() - fixed->GetX();
const C4Real dy = first->GetY() - fixed->GetY();
const C4Real lf = dx*dx+dy*dy;
if(lf > l*l*itofix(15,10)*itofix(15,10) && l * SegmentCount < max)
{
const C4Real x = fixed->GetX() + itofix(5,10)*dx;
const C4Real y = fixed->GetY() + itofix(5,10)*dy;
C4RopeElement* new_elem = new C4RopeElement(x, y, first->GetMass(), false);
new_elem->vx = (fixed->GetVx() + first->GetVx())/itofix(2);
new_elem->vy = (fixed->GetVy() + first->GetVy())/itofix(2);
// Link the new element
if(fixed->Next == first)
{
new_elem->Prev = fixed;
new_elem->Next = first;
fixed->Next = new_elem;
first->Prev = new_elem;
}
else
{
new_elem->Prev = first;
new_elem->Next = fixed;
fixed->Prev = new_elem;
first->Next = new_elem;
}
++SegmentCount;
}
else if(SegmentCount > 0) // Rope cannot be shorter than just Beginning and End segment
{
// To find out whether we can shorten the rope we do the following:
// We go through all elements and if at some point the nominal rope length
// is shorter than the distance between that element and the fixpoint
// and the path between the two is free, then the rope is shortened.
unsigned int i = 1;
for(C4RopeElement* cur = first; cur != NULL; cur = (fixed->Next == first ? cur->Next : cur->Prev), ++i)
{
// We use integers, not reals here, to protect for overflows. This works
// because these numbers are large enough so we don't need to take care
// about subpixel precision.
const unsigned int nd = fixtoi(l*itofix(i));
const unsigned int dx = fixtoi(cur->GetX() - fixed->GetX());
const unsigned int dy = fixtoi(cur->GetY() - fixed->GetY());
const unsigned int d2 = dx*dx+dy*dy;
if(d2 > nd*nd*15/10*15/10)
break;
else if(d2 < nd*nd*8/10*8/10)
{
// TODO: Check whether all elements have PathFree, and stop if one hasn't?
if(PathFree(fixtoi(fixed->GetX()), fixtoi(fixed->GetY()), fixtoi(cur->GetX()), fixtoi(cur->GetY())))
{
C4RopeElement* second = ((fixed->Next == first) ? first->Next : first->Prev);
assert(second != NULL);
// Remove first, relink fixed and second
C4RopeElement* Del = first;
if(fixed->Next == first)
{
fixed->Next = second;
second->Prev = fixed;
}
else
{
fixed->Prev = second;
second->Next = fixed;
}
delete Del;
--SegmentCount;
}
break;
}
}
}
}
}
void C4Rope::Solve(C4RopeElement* prev, C4RopeElement* next)
{
// Rope forces
const C4Real dx = prev->GetX() - next->GetX();
const C4Real dy = prev->GetY() - next->GetY();
if(dx != Fix0 || dy != Fix0) //dx*dx + dy*dy > Fix0)
{
// Get segment length between prev and next
const C4Real l = GetL(prev, next);
// Compute forces between these points. If there is no material between the
// rope segments, this is just a straight line. Otherwise, run the
// pathfinder to find out in which direction to pull.
// If the PathFinder doesn't find a path... then well.. no idea. Maybe
// the rope got buried by an earthquake, or someone built a loam bridge
// over a rope segment. In that case just apply the normal "straight"
// forces and hope the best...
// TODO: Avoid any of these expensive computations if both prev and next
// have force redirection active
int pix = fixtoi(prev->GetX());
int piy = fixtoi(prev->GetY());
int nix = fixtoi(next->GetX());
int niy = fixtoi(next->GetY());
// We only run the PathFinder when the distance between the two segments
// is at least twice the nominal distance.
C4Real dx1, dy1, dx2, dy2, d;
PullPathInfo Info = { true, pix, piy, nix, niy, nix, niy, pix, piy, Fix0 };
if((Abs(dx) > l*2 || Abs(dy) > l*2 || dx*dx+dy*dy > l*l*4) && // The first two checks exist to avoid an overflow in the dx*dx+dy*dy expression
!PathFree(pix, piy, nix, niy) &&
Game.PathFinder.Find(pix, piy, nix, niy, PullPathAccumulator, (intptr_t)&Info))
{
C4Real dpx = itofix(Info.px - pix);
C4Real dpy = itofix(Info.py - piy);
C4Real dp = Len(dpx, dpy); // TODO: Could be computed in accumulator
C4Real dnx = itofix(Info.nx - nix);
C4Real dny = itofix(Info.ny - niy);
C4Real dn = Len(dnx, dny); // TODO: Could be computed in accumulator
if(dp != Fix0)
{ dx1 = dpx / dp; dy1 = dpy / dp; }
else
{ dx1 = Fix0; dy1 = Fix0; }
if(dn != Fix0)
{ dx2 = dnx / dn; dy2 = dny / dn; }
else
{ dx2 = Fix0; dy2 = Fix0; }
d = Info.d + dp;
/*int index = 0;
C4RopeElement* prev2 = prev;
while(prev2 != Front) { prev2 = prev2->Prev; ++index; }
printf("Solved %p(index=%d) via PathFinder, from %d/%d to %d/%d\n", this, index, pix, piy, nix, niy);
printf(" --> p to %d/%d, n to %d/%d\n", Info.px, Info.py, Info.nx, Info.ny);
printf(" --> vec_p=%f/%f, vec_n=%f/%f, d(pathfinder)=%f, d(direct)=%f\n", fixtof(dx1), fixtof(dy1), fixtof(dx2), fixtof(dy2), fixtof(d), fixtof(Len(dx,dy)));*/
}
else
{
d = Len(dx, dy);
if(d != Fix0)
{ dx1 = -(dx / d); dy1 = -(dy / d); }
else
{ dx1 = 0; dx2 = 0; }
dx2 = -dx1;
dy2 = -dy1;
}
if(ApplyRepulsive || d > l)
{
if(prev->rdt != Fix0) { dx1 = prev->rx; dy1 = prev->ry; }
if(next->rdt != Fix0) { dx2 = next->rx; dy2 = next->ry; }
prev->AddForce(dx1 * k * (d - l), dy1 * k * (d - l));
next->AddForce(dx2 * k * (d - l), dy2 * k * (d - l));
}
}
// Inner friction
// TODO: This is very sensitive to numerical instabilities for mid-to-high
// eta values. We might want to prevent a sign change of either F or V induced
// by this factor.
// TODO: Don't apply inner friction for segments connected to fixed rope ends?
C4Real fx = (prev->GetVx() - next->GetVx()) * eta;
C4Real fy = (prev->GetVy() - next->GetVy()) * eta;
prev->AddForce(-fx, -fy);
next->AddForce(fx, fy);
// Could add air/water friction here
// TODO: Apply gravity separately. This is applied twice now for
// non-end rope segments!
// Don't apply gravity to objects since it's applied already in C4Object execution.
prev->AddForce(Fix0, (prev->GetObject() ? Fix0 : prev->GetMass() * ::Landscape.Gravity/5));
next->AddForce(Fix0, (prev->GetObject() ? Fix0 : next->GetMass() * ::Landscape.Gravity/5));
}
void C4Rope::Execute()
{
C4Real dt = itofix(1, NumIterations);
for(unsigned int i = 0; i < NumIterations; ++i)
{
// Execute auto-segmentation
DoAutoSegmentation(Front, Front->Next, FrontAutoSegmentation);
DoAutoSegmentation(Back, Back->Prev, BackAutoSegmentation);
// Reset previous forces
#ifdef C4ROPE_DRAW_DEBUG
for(C4RopeElement* cur = Front; cur != NULL; cur = cur->Next)
cur->fcx = cur->fcy = Fix0;
#endif
// Compute inter-rope forces
for(C4RopeElement* cur = Front; cur->Next != NULL; cur = cur->Next)
Solve(cur, cur->Next);
// Front/BackPull
if(FrontPull != Fix0)
{
// Pull at all elements that are near to the front element
C4RopeElement* cur = Front;
C4Real d;
do
{
cur = cur->Next;
const C4Real dx = cur->GetX() - Front->GetX();
const C4Real dy = cur->GetY() - Front->GetY();
d = Len(dx, dy);
if(d != Fix0)
cur->AddForce(-dx/d * FrontPull, -dy/d * FrontPull);
} while(cur->Next != NULL && d < itofix(5,10*l));
}
if(BackPull != Fix0)
{
// Pull at all elements that are near to the back element
C4RopeElement* cur = Back;
C4Real d;
do
{
cur = cur->Prev;
const C4Real dx = cur->GetX() - Back->GetX();
const C4Real dy = cur->GetY() - Back->GetY();
d = Len(dx, dy);
if(d != Fix0)
cur->AddForce(-dx/d * BackPull, -dy/d * BackPull);
} while(cur->Prev != NULL && d < itofix(5,10*l));
}
// Apply forces
for(C4RopeElement* cur = Front; cur != NULL; cur = cur->Next)
cur->Execute(this, dt);
}
// Insert/remove links between rope elements. We rely that object movement
// is executed before rope movement at this point, so this needs to stay in
// sync with C4Game::Execute().
for(C4RopeElement* cur = Front; cur != NULL; cur = cur->Next)
{
int insert_x, insert_y;
if(cur->Prev && cur->InsertLinkPosition(cur->oldx, cur->oldy, cur->GetX(), cur->GetY(), cur->GetPrevLinkX(), cur->GetPrevLinkY(), insert_x, insert_y))
cur->InsertLink(cur->Prev, cur, insert_x, insert_y);
if(cur->Next && cur->InsertLinkPosition(cur->oldx, cur->oldy, cur->GetX(), cur->GetY(), cur->GetNextLinkX(), cur->GetNextLinkY(), insert_x, insert_y))
cur->InsertLink(cur, cur->Next, insert_x, insert_y);
// TODO: removal
}
// Update old coordinates for next iteration. Don't do this in the loop above,
// so that GetPrevLinkX/GetPrevLinkY still return the old values.
for(C4RopeElement* cur = Front; cur != NULL; cur = cur->Next)
{
cur->oldx = cur->GetX();
cur->oldy = cur->GetY();
}
}
void C4Rope::ClearPointers(C4Object* obj)
{
for(C4RopeElement* cur = Front; cur != NULL; cur = cur->Next)
{
if(cur->GetObject() == obj)
{
cur->x = obj->fix_x;
cur->y = obj->fix_y;
cur->vx = obj->xdir;
cur->vy = obj->ydir;
cur->m = obj->Mass;
cur->Object = NULL;
}
}
}
// TODO: Move this to StdGL
void C4Rope::Draw(C4TargetFacet& cgo, C4BltTransform* pTransform)
{
#ifndef C4ROPE_DRAW_DEBUG
Vertex Tmp[4];
DrawVertex* Vertices = new DrawVertex[SegmentCount*2+4]; // TODO: Use a vbo and map it into memory instead?
VertexPos(Vertices[0], Vertices[1], Tmp[0], Tmp[1],
Vertex(fixtof(Front->GetX()), fixtof(Front->GetY())),
Vertex(fixtof(Front->Next->GetX()), fixtof(Front->Next->GetY())), Width);
Vertices[0].u = 0.0f;
Vertices[0].v = 0.0f;
Vertices[1].u = 1.0f;
Vertices[1].v = 0.0f;
const float rsl = 1.0f/Width * Graphics->GetBitmap()->Wdt / Graphics->GetBitmap()->Hgt; // rope segment length mapped to Gfx bitmap
float accl = 0.0f;
unsigned int i = 2;
bool parity = true;
for(C4RopeElement* cur = Front->Next; cur->Next != NULL; cur = cur->Next, i += 2)
{
Vertex v1(fixtof(cur->GetX()),
fixtof(cur->GetY()));
Vertex v2(fixtof(cur->Next->GetX()),// ? cur->Next->GetX() : Back->GetX()),
fixtof(cur->Next->GetY()));// ? cur->Next->GetY() : Back->GetY()));
Vertex v3(fixtof(cur->Prev->GetX()),// ? cur->Prev->GetX() : Front->GetX()),
fixtof(cur->Prev->GetY()));// ? cur->Prev->GetY() : Front->GetY()));
//const C4Real l = GetL(cur->Prev, cur);
//const float rsl = fixtof(l)/fixtof(Width) * Graphics->GetBitmap()->Wdt / Graphics->GetBitmap()->Hgt; // rope segment length mapped to Gfx bitmap
// Parity -- parity swaps for each pointed angle (<90 deg)
float cx = v1.x - v3.x;
float cy = v1.y - v3.y;
float ex = v1.x - v2.x;
float ey = v1.y - v2.y;
// TODO: Another way to draw this would be to insert a "pseudo" segment so that there are no pointed angles at all
if(cx*ex+cy*ey > 0)
parity = !parity;
// Obtain vertex positions
if(parity)
VertexPos(Tmp[2], Tmp[3], Vertices[i+2], Vertices[i+3], v1, v2, Width);
else
VertexPos(Tmp[3], Tmp[2], Vertices[i+3], Vertices[i+2], v1, v2, Width);
Tmp[2].x = (Tmp[0].x + Tmp[2].x)/2.0f;
Tmp[2].y = (Tmp[0].y + Tmp[2].y)/2.0f;
Tmp[3].x = (Tmp[1].x + Tmp[3].x)/2.0f;
Tmp[3].y = (Tmp[1].y + Tmp[3].y)/2.0f;
// renormalize
float dx = Tmp[3].x - Tmp[2].x;
float dy = Tmp[3].y - Tmp[2].y;
float dx2 = Vertices[i-1].x - Vertices[i-2].x;
float dy2 = Vertices[i-1].y - Vertices[i-2].y;
const float d = (dx2*dx2+dy2*dy2)/(dx*dx2+dy*dy2);
Vertices[i ].x = ( (Tmp[2].x + Tmp[3].x)/2.0f) - BoundBy((Tmp[3].x - Tmp[2].x)*d, -Width, Width)/2.0f;
Vertices[i ].y = ( (Tmp[2].y + Tmp[3].y)/2.0f) - BoundBy((Tmp[3].y - Tmp[2].y)*d, -Width, Width)/2.0f;
Vertices[i+1].x = ( (Tmp[2].x + Tmp[3].x)/2.0f) + BoundBy((Tmp[3].x - Tmp[2].x)*d, -Width, Width)/2.0f;
Vertices[i+1].y = ( (Tmp[2].y + Tmp[3].y)/2.0f) + BoundBy((Tmp[3].y - Tmp[2].y)*d, -Width, Width)/2.0f;
accl += fixtof(GetL(cur->Prev, cur));
Vertices[i].u = 0.0f; //parity ? 0.0f : 1.0f;
Vertices[i].v = accl * rsl;
Vertices[i+1].u = 1.0f; //parity ? 1.0f : 0.0f;
Vertices[i+1].v = accl * rsl;
Tmp[0] = Vertices[i+2];
Tmp[1] = Vertices[i+3];
}
accl += fixtof(GetL(Back->Prev, Back));
Vertices[i].u = 0.0f; //parity ? 0.0f : 1.0f;
Vertices[i].v = accl * rsl;
Vertices[i+1].u = 1.0f; //parity ? 1.0f : 0.0f;
Vertices[i+1].v = accl * rsl;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, (*Graphics->GetBitmap()->ppTex)->texName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glVertexPointer(2, GL_FLOAT, sizeof(DrawVertex), &Vertices->x);
glTexCoordPointer(2, GL_FLOAT, sizeof(DrawVertex), &Vertices->u);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_QUAD_STRIP, 0, SegmentCount*2+4);
glDisable(GL_TEXTURE_2D);
//glDisable(GL_BLEND);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
delete[] Vertices;
#else
// Debug:
for(C4RopeElement* cur = Front; cur != NULL; cur = cur->Next)
{
if(!cur->GetObject())
{
glBegin(GL_QUADS);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(fixtof(cur->x)-1.5f, fixtof(cur->y)-1.5f);
glVertex2f(fixtof(cur->x)-1.5f, fixtof(cur->y)+1.5f);
glVertex2f(fixtof(cur->x)+1.5f, fixtof(cur->y)+1.5f);
glVertex2f(fixtof(cur->x)+1.5f, fixtof(cur->y)-1.5f);
glEnd();
}
// Draw links
for(C4RopeLink* link = cur->FirstLink; link != NULL; link = link->Next)
{
glBegin(GL_QUADS);
glColor3f(1.0f, 1.0f, 1.0f);
glVertex2f(fixtof(cur->x)-1.0f, fixtof(cur->y)-1.0f);
glVertex2f(fixtof(cur->x)-1.0f, fixtof(cur->y)+1.0f);
glVertex2f(fixtof(cur->x)+1.0f, fixtof(cur->y)+1.0f);
glVertex2f(fixtof(cur->x)+1.0f, fixtof(cur->y)-1.0f);
glEnd();
}
const float vx = fixtof(cur->GetVx());
const float vy = fixtof(cur->GetVy());
const float v = sqrt(vx*vx + vy*vy);
if(v > 0.1)
{
glBegin(GL_TRIANGLES);
glColor3f(BoundBy(v/2.5f, 0.0f, 1.0f), 0.0f, 1.0f);
glVertex2f(fixtof(cur->GetX()) + vx/v*4.0f, fixtof(cur->GetY()) + vy/v*4.0f);
glVertex2f(fixtof(cur->GetX()) - vy/v*1.5f, fixtof(cur->GetY()) + vx/v*1.5f);
glVertex2f(fixtof(cur->GetX()) + vy/v*1.5f, fixtof(cur->GetY()) - vx/v*1.5f);
glEnd();
}
const float fx = fixtof(cur->fcx);
const float fy = fixtof(cur->fcy);
const float f = sqrt(fx*fx + fy*fy);
if(f > 0.1)
{
glBegin(GL_TRIANGLES);
glColor3f(0.0f, 1.0f, BoundBy(v/2.5f, 0.0f, 1.0f));
glVertex2f(fixtof(cur->GetX()) + fx/f*4.0f, fixtof(cur->GetY()) + fy/f*4.0f);
glVertex2f(fixtof(cur->GetX()) - fy/f*1.5f, fixtof(cur->GetY()) + fx/f*1.5f);
glVertex2f(fixtof(cur->GetX()) + fy/f*1.5f, fixtof(cur->GetY()) - fx/f*1.5f);
glEnd();
}
const float rx = fixtof(cur->rx);
const float ry = fixtof(cur->ry);
const float r = sqrt(rx*rx + ry*ry);
if(r > 0.1)
{
glBegin(GL_TRIANGLES);
glColor3f(0.0f, BoundBy(r/2.5f, 0.0f, 1.0f), 1.0f);
glVertex2f(fixtof(cur->x) + rx/r*4.0f, fixtof(cur->y) + ry/r*4.0f);
glVertex2f(fixtof(cur->x) - ry/r*1.5f, fixtof(cur->y) + rx/r*1.5f);
glVertex2f(fixtof(cur->x) + ry/r*1.5f, fixtof(cur->y) - rx/r*1.5f);
glEnd();
}
}
#endif
}
C4RopeList::C4RopeList()
{
for(unsigned int i = 0; i < Ropes.size(); ++i)
delete Ropes[i];
}
C4Rope* C4RopeList::CreateRope(C4Object* first_obj, C4Object* second_obj, C4Real segment_length, C4DefGraphics* graphics)
{
Ropes.push_back(new C4Rope(RopeAul.GetPropList(), first_obj, second_obj, segment_length, graphics));
return Ropes.back();
}
void C4RopeList::RemoveRope(C4Rope* rope)
{
for(std::vector<C4Rope*>::iterator iter = Ropes.begin(); iter != Ropes.end(); ++iter)
{
if(*iter == rope)
{
Ropes.erase(iter);
delete rope;
break;
}
}
}
void C4RopeList::Execute()
{
for(unsigned int i = 0; i < Ropes.size(); ++i)
Ropes[i]->Execute();
}
void C4RopeList::Draw(C4TargetFacet& cgo, C4BltTransform* pTransform)
{
ZoomData z;
pDraw->GetZoom(&z);
glPushMatrix();
ApplyZoomAndTransform(z.X, z.Y, z.Zoom, pTransform);
glTranslatef(cgo.X-cgo.TargetX, cgo.Y-cgo.TargetY, 0.0f);
for(unsigned int i = 0; i < Ropes.size(); ++i)
Ropes[i]->Draw(cgo, pTransform);
glPopMatrix();
}
void C4RopeList::ClearPointers(C4Object* obj)
{
for(unsigned int i = 0; i < Ropes.size(); ++i)
Ropes[i]->ClearPointers(obj);
}