/// most of the engine, always experimental /* * This is the "core" file - which means it's my main playground, * messy, and does much too much. * * It implements the functionallity documented in mass.h. * This includes the core flattining algorithm. * The masses live in the "field", see field.h for details. */ #include "mass.h" #include "field.h" #include "massalgos.h" #include "debug.h" #include "math2d.h" #include "engine.h" #include "effects.h" #include #include #include "stringio.h" // hardcoded parameters #define SICKNESSDURATION 2000 void FreeMass(Mass * m) { /* free the mass memory itself */ assert(m->refcount == 0); assert(m->interfaces == NULL); if (m->displayData) (*m->displayData->freeFunc)(m->displayData); m->displayData = NULL; xfree(m->class); #include "script_free_mass.inc" xfree(m); } Mass * AllocMass(Mass * copy) { Mass * m; m = calloc(1, sizeof(Mass)); /* calloc filled the memory with zeros */ /* assign default values for non-zero fields */ m->player = -1; // (set default / copy) values that can be configured if (!copy) { #include "script_set_mass_defaults.inc" } else { m->class = malloc_strcpy(copy->class); #include "script_copy_mass.inc" } // FIXME: needed? m->flatteningSHR[LEFT] = m->normalSHR; m->flatteningSHR[RIGHT] = m->normalSHR; m->flatteningSHR[UP] = m->normalSHR; m->flatteningSHR[DOWN] = m->normalSHR; DebugLevel(3, "Mass %X allocated.\n", (int)m); return m; } int PlaceMassNearby(Mass * m, int x, int y, int radius) { FieldPoint * p; /* search radius region */ if (radius == 0) { p = FieldXY(x, y); if (p->owner) return 0; } else { Point pt; int i; for (i=50; i>0; i--) { pt.x = randarea(x-radius, 2*radius); pt.y = randarea(y-radius, 2*radius); p = FieldPoint(pt); if (!p->owner) break; } if (i == 0) return 0; x = pt.x; y = pt.y; } assert(FieldXY(x, y) == p); p->owner = m; p->height = HEIGHT_NORM*32; /* so the pixel won't just dissappear */ m->numPixels = 1; m->maxRect.x = x; m->maxRect.y = y; m->maxRect.h = 1; m->maxRect.w = 1; return 1; } void PlaceMassAt(int x, int y, char * class) { Mass * parent; Mass * m; parent = MassClassByName(class); if (!parent) { DebugLevel(1, "Mass class does not exist: %s\n", class); return; } m = AllocMass(parent); if (!PlaceMassNearby(m, x, y, 0)) { FreeMass(m); return; } DebugLevel(2, "Placed %s at %d, %d\n", class, x, y); AddMassToField(m); } void ContinueMass(Mass * m); void ReThink(Mass * m); void PreFlattening(Mass * m); void DoFlattening(Mass * m); void ScreenBorderGuard(void); void ContinueMasses() { Mass * m; List * list; /* TODO (idea): perhaps process the list once forward and once backward, so there is more justice. (for the points of conflict) */ if (engine.calculateInvisible) { list = GetAllMasses(); } else { list = GetThawnMasses(); } FOR_ITEM_IN_LIST(m, list) { int dead; assert(m->speed >= 0); assert(m->speed <= 1000*256); assert(m->time256 < 256 && m->time256 >= 0); m->time256 += m->speed; /* TODO (idea): in case there are masses with big speed values it could be better to process them in turn, to allow more interactions. */ dead = m->isDead; while (!dead && m->time256 >= 256) { int starttime = 0; if (config.profile) starttime = SDL_GetTicks(); m->time256 -= 256; if (!m->isDead) ReThink(m); if (!m->isDead) DebugCheckMass(m); if (!m->isDead) ContinueMass(m); if (!m->isDead) m->mustRedraw = 1; if (config.profile) { Mass * class; int t; t = SDL_GetTicks() - starttime; class = MassClassByName(m->class); assert(class); class->timeEngine += t; } } } list_free(list); UnbanMassZombies(); } void RenewInterfaceReactions(Mass * m) { /* add code that destroys/creates masses to ReThink(), not here */ /* FIXME: sure? why? */ MassInterface * mi; for (mi=m->interfaces; mi->m; mi++) { Mass * m2 = mi->m; if (m->isWall) { if (m2->isWall) mi->borderReaction = 256; if (m2->player != -1) mi->borderReaction = -1000; } if (m->playerFood) { if (m2->player != -1) { mi->borderReaction = 256; mi->eatme = 1; } } if (!mi->age++) { /* things which need to be done only once (or only when mi->age is reset) */ } } } void DebugPrintMass(Mass * m) { int x, y; DebugLevel(3, "Mass %X %s:\n", m, RectString(m->maxRect)); for (y=m->maxRect.y-1; ymaxRect.y+m->maxRect.h+2; y++) { for (x=m->maxRect.x-1; xmaxRect.x+m->maxRect.w+2; x++) { FieldPoint * p = FieldXY(x, y); if (p->owner == m) { DebugLevel(3, "M"); } else if (p->owner == NULL) { DebugLevel(3, "-"); } else { DebugLevel(3, "?"); } } DebugLevel(3, "\n"); } } void DebugCheckMass(Mass * m) { #ifdef DEBUG int empty; assert(m); empty = 0; if (m->numPixels == 0) empty++; if (m->maxRect.w <= 0 || m->maxRect.h <= 0) empty++; assert(m->maxRect.w <= FIELDSIZE); assert(m->maxRect.h <= FIELDSIZE); if (empty == 2) { DebugLevel(1, "Warning: empty mass %X still being around\n", m); return; } if (empty) { if (m->numPixels == 0) { DebugLevel(1, "Warning: empty mass %X has still with*height=%d. Fixed.\n", m, m->maxRect.w * m->maxRect.h); m->maxRect.w = 0; m->maxRect.h = 0; } else { DebugLevel(1, "Warning: empty mass %X has still %d numPixels\n", m, m->numPixels); } return; } assert(m->numPixels >= 0); if (HEAVY_DEBUG_ENABLED) { int x, y, n; n = 0; for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { FieldPoint * p = FieldXY(x, y); if (p->owner == m) { assert(p->height >= 0); n++; } } } assert(m->numPixels == n); } #endif } /* parts belonging to PreFlattening: */ //void PreFlattening_Movement(Mass * m); void PreFlattening_ShapePoints(Mass * m) { int x, y; int i; FieldPoint * p; /* TODO: evaluate other algos */ /* this one does rectangular shapepoints */ for (i=0; ishapePoints[i]; if (sp.radius > 0 && sp.strength != 0) { Rect r; int radiusReciproc; radiusReciproc = sp.strength*256/sp.radius; r.x = sp.pos.x - sp.radius; r.y = sp.pos.y - sp.radius; r.w = r.h = sp.radius*2+1; r = wrap_SmallestCommonRect(m->maxRect, r); for (y=r.y; yowner == m) { // TODO: wrap distance. int distance = abs(x-sp.pos.x) + abs(y-sp.pos.y); /* OPTIMIZE: redundant check: */ if (distance < sp.radius) { int addHeight; addHeight = (sp.radius - distance) * radiusReciproc / 256; p->height += addHeight; m->bufferMass -= addHeight; if (p->height < 0) { /* OPTIMIZE: remove multiple such checks and do them once before (or even after?) the flattening. */ m->bufferMass += p->height; p->height = 0; } } } } } } } } /* TODO: move the functions below to a better location and announce them in an header file */ int CountShapePoints(Mass * m) { int i, num=0; for (i=0; ishapePoints[i].radius != 0 && m->shapePoints[i].strength != 0) num++; return num; } /* condition: -1 only negative strength, 0 everything, +1 only positive strength max: 1 search maximum, 0 search minimum */ int GetStrongestShapePoint_Backend(Mass * m, int condition, int max) { int best = -1; int best_value = -1; ShapePoint sp; int i; for (i=0; ishapePoints[i]; if (sp.radius >= 0 && sp.strength != 0) { int value = sp.strength*sp.radius; if (condition == 0) value = abs(value); else value *= condition; if (value > 0) { /* condition is met */ if (!max) value = -value; if (best == -1 || value > best_value) { best_value = value; best = i; } } } } return best; } int GetStrongestShapePoint(Mass * m) { return GetStrongestShapePoint_Backend(m, 0, 1); } int GetStrongestPositiveShapePoint(Mass * m) { return GetStrongestShapePoint_Backend(m, +1, 1); } int GetStrongestNegativeShapePoint(Mass * m) { return GetStrongestShapePoint_Backend(m, -1, 1); } int GetWeakestShapePoint(Mass * m) { return GetStrongestShapePoint_Backend(m, 0, 0); } /* the returned shapepoint is always valid */ int GetFreeShapePoint(Mass * m) { int i; for (i=0; ishapePoints[i].radius == 0 || m->shapePoints[i].strength == 0) { return i; } } return GetWeakestShapePoint(m); } Point GetRandomPixelOfMass(Mass * m) { Point p; assert(m->numPixels > 0); do { p.x = randarea(m->maxRect.x, m->maxRect.w); p.y = randarea(m->maxRect.y, m->maxRect.h); } while (FieldPoint(p)->owner != m); return p; } void MovePointRandomly(Point * p) { if (randarea(0, 2)) { if (randarea(0, 2)) p->x++; else p->x--; } else { if (randarea(0, 2)) p->y++; else p->y--; } } void MovePointToPoint(Point * p, Point target) { int deltax, deltay; deltax = abs(target.x - p->x); deltay = abs(target.y - p->y); if (deltax == 0 && deltay == 0) return; if (deltay == 0 || randarea(0, deltax+deltay) < deltax) { if (p->x < target.x) p->x++; if (p->x > target.x) p->x--; } else { if (p->y < target.y) p->y++; if (p->y > target.y) p->y--; } } void MovePointAwayFromPoint(Point * p, Point target) { int deltax, deltay; deltax = abs(target.x - p->x); deltay = abs(target.y - p->y); if (deltax == 0 && deltay == 0) { MovePointRandomly(p); return; } if (deltay == 0 || randarea(0, deltax+deltay) < deltax) { if (p->x < target.x) p->x--; if (p->x > target.x) p->x++; } else { if (p->y < target.y) p->y--; if (p->y > target.y) p->y++; } } void MoveShapePointAwayFromOthers(Mass * m, int shapePoint) { ShapePoint * target = &m->shapePoints[shapePoint]; int best = -1; int best_value = -1; ShapePoint sp; int i; for (i=0; ishapePoints[i]; if (i != shapePoint && sp.radius >= 0 && sp.strength != 0) { int value; /* FIXME: is wrap_ really necessary? */ value = wrap_GetSquareDistance(sp.pos, target->pos); if (best == -1 || value < best_value) { best_value = value; best = i; } } } if (best == -1) { MovePointRandomly(&target->pos); } else { MovePointAwayFromPoint(&target->pos, m->shapePoints[best].pos); } } Point GetBorderPointFromPointInDirection(Mass * m, Point p, int dir) { /* this is in fact a poor implementation of a line drawing algo */ int i; Point res, add; add = AngleToVector(dir, 256/4); res.x = p.x * 256; res.y = p.y * 256; for (i=0; i<1000; i++) { res.x += add.x; res.y += add.y; if (FieldXY(res.x/256, res.y/256)->owner != m) { res.x /= 256; res.y /= 256; return res; } } /* this would be a very big mass, should never happen. */ assert(0); return res; } Point GetBorderPointFromCenterInDirection(Mass * m, int dir) { return GetBorderPointFromPointInDirection(m, GetMassCenter(m), dir); } Point GetPointInsideMass(Mass * m, int dir, int dist) { // dist: -255 back border ... 0 center ... +255 front border Point center, border, v; if (dist < 0) { dist = -dist; dir += 128; } if (dist > 255) dist = 255; center = GetMassCenter(m); border = GetBorderPointFromPointInDirection(m, center, dir); v = wrap_VectorFromTo(center, border); v.x = v.x * dist / 256; v.y = v.y * dist / 256; // FIXME: maybe search further if the point contains no mass? return AddVectors(center, v); } Point GetMassPixel(Mass * m) { /* get a random pixel belonging to the mass */ int pos, target; int x, y; pos = -1; target = randarea(0, m->numPixels); for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { if (FieldXY(x, y)->owner == m) { if (++pos == target) { Point p; p.x = x; p.y = y; return p; } } } } { /* FIXME: assertion? */ Point p; p.x = -1; p.y = -1; return p; } } Point AddBorderPixel(Mass * m) { int trials = 500; Point p; p = GetMassCenter(m); if (FieldPoint(p)->owner != m) p = GetMassPixel(m); while (FieldPoint(p)->owner == m && --trials) { Point np; np = p; switch (randarea(0, 4)) { case 0: np.x++; break; case 1: np.x--; break; case 2: np.y++; break; case 3: np.y--; break; } if (FieldPoint(np)->owner == m || !FieldPoint(np)->owner) { p = np; } } if (!trials) { p.x = -1; p.y = -1; } else { FieldPoint(p)->owner = m; FieldPoint(p)->height = HEIGHT_NORM/2; wrap_ExpandRectToIncludePoint(&m->maxRect, p.x, p.y); m->numPixels++; } return p; } void DeleteRandomPixel(Mass * m) { DebugCheckMass(m); if (m->numPixels > 0) { FieldPoint * p; p = FieldPoint(GetRandomPixelOfMass(m)); p->owner = NULL; p->height = 0; m->numPixels--; } else { DebugLevel(1, "No border point found to delete, mass empty.\n"); } } int CountFruits(Point p, int radius) { /* OPTIMIZE this may be obsolete, use the interfaces instead */ int numMatches = 0; List * list; Mass * m; Rect r; r.x = p.x - radius; r.y = p.y - radius; r.w = r.h = 2*radius+1; list = GetMassesInRect(r); FOR_ITEM_IN_LIST(m, list) if (m->playerFood) numMatches++; list_free(list); return numMatches; } //int LookAheadGetObstacle(Point p, int dir, int maxdist, Mass * ignoreMass) /* return how empty the field look in this direction */ int LookAheadGetEmptyness(Point p, int dir, int maxdist, Mass * ignoreMass) { /* OPTIMIZE: this is in fact a poor implementation of a line drawing algo */ /* FIXME: in fact it's an evil code duplication, see above */ int i; Point res, add; FieldPoint * fp; add = AngleToVector(dir, 256); res.x = p.x * 256; res.y = p.y * 256; for (i=maxdist; i--;) { res.x += add.x; res.y += add.y; fp = FieldXY(res.x/256, res.y/256); if (fp && fp->owner != ignoreMass) { return -(i*i); } } return 0; /* the maximum */ } /* return the better direction of dir+turn and dir-turn */ int LazyGetBetterDirection(Point start, int dir, int turn, int maxdist, Mass * ignoreMass) { int a, b; a = LookAheadGetEmptyness(start, dir+turn, maxdist, ignoreMass); b = LookAheadGetEmptyness(start, dir-turn, maxdist, ignoreMass); if (a==b) return dir; return (a>b)?(dir+turn):(dir-turn); } /* look around (radial), and return the most empty direction */ Point LookAroundSimplestPath(Point start, int maxdist, Mass * ignoreMass) { /* a vector that will point in the desired direction */ /* Algo will generally not work - it will fail in this case: ooo o o x-> ooo ooo o And point stright into the obstacle. */ Point res; int i; res.x = 0; res.y = 0; for (i=0; i<16; i++) { int emptyness; Point add; /* TODO: look ahead the line function */ emptyness = LookAheadGetEmptyness(start, i*16, maxdist, ignoreMass); add = AngleToVector(i*16, emptyness); res.x += add.x/128; res.y += add.y/128; } //printf("res={%d, %d}\n", res.x, res.y); return res; } /* returns the number of pixels cut out */ int CutCircleFromMass(Mass * src, Mass * dst, Point center, int radius, int minCut) { Rect r; int x, y; int dist; int cutPixels = 0; Point * points; r.x = center.x - radius; r.y = center.y - radius; r.w = 2*radius; r.h = 2*radius; points = malloc(r.w * r.h * sizeof(Point)); radius = sqr(radius); for (y=r.y; yowner == src) { points[cutPixels++] = XYToPoint(x, y); } } } if (cutPixels > minCut) { int i; /* i = src->bufferMass / src->numPixels * cutPixels; dst->bufferMass += i; src->bufferMass -= i; */ i = cutPixels; while (i--) { FieldPoint(points[i])->owner = dst; wrap_ExpandRectToIncludePoint(&dst->maxRect, points[i].x, points[i].y); src->numPixels--; dst->numPixels++; } xfree(points); return cutPixels; } else { xfree(points); return 0; } } void CutAllPixelsFromMass(Mass * src, Mass * dst) { int x, y; wrap_ExpandRectToIncludeRect(&dst->maxRect, src->maxRect); for (y=src->maxRect.y; ymaxRect.y+src->maxRect.h; y++) { for (x=src->maxRect.x; xmaxRect.x+src->maxRect.w; x++) { FieldPoint * p = FieldXY(x, y); if (p->owner == src) { p->owner = dst; src->numPixels--; dst->numPixels++; } } } assert(src->numPixels == 0); } Point GetRandomMassPoint(Mass * m) { Point p; assert(m->numPixels > 0); do { p.x = randarea(m->maxRect.x, m->maxRect.w); p.y = randarea(m->maxRect.y, m->maxRect.h); } while (FieldPoint(p)->owner != m); return p; } int CountMassVolume(Mass * m) { int x, y; int sum = 0; for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { FieldPoint * p = FieldXY(x, y); if (p->owner == m) { sum += p->height; } } } sum += m->bufferMass; assert(sum > 0); // overflow return sum; } void GenerateChildMass(Mass * m) { // birth of a new mass Mass * m2; Mass * parent; Point pos; char * childClass; int birthHeading, birthDistance; if (m->childCount == 0) return; childClass = malloc_ChooseStringFromList(m->childClass, ' '); parent = MassClassByName(childClass); if (!parent) { if (!streq(childClass, "self")) Die("Error: mass of class '%s' wanted to create a child of\nundefined class '%s'\n", m->class, childClass); //parent = MassClassByName(m->class); parent = m; } xfree(childClass); m2 = AllocMass(parent); if (m->childRandomDirection) { birthHeading = randarea(0, 256); } else { birthHeading = m->heading + m->childDirection; if (m->childDirectionAlternating) m->childDirection = - m->childDirection; } if (m->childRandomDistance) { birthDistance = randarea(0, abs(m->childDistance)); if (m->childDistance < 0) birthDistance *= -1; } else { birthDistance = m->childDistance; } if (m->childInside) { int numCut; pos = GetPointInsideMass(m, birthHeading, birthDistance); numCut = CutCircleFromMass(m, m2, pos, 3, 8); DebugLevel(3, "Cut %d pixels from %s to create a child of class %s\n", numCut, m->class, m2->class); } else { pos = AddVectors(GetMassCenter(m), AngleToVector(birthHeading, birthDistance)); PlaceMassNearby(m2, pos.x, pos.y, 3); DebugLevel(3, "Placed child at %d, %d\n", pos.x, pos.y); } AddMassToField(m2); if (m->childSetHeading) m2->heading = birthHeading; if (m2->numPixels > 0) { if (m->childCount != -1) m->childCount--; if (!m->childTakesMassFromParent && m->childInside) { m->bufferMass += CountMassVolume(m2); } if (m->childAndParentSwapped) { int tmpBufferMass; Mass * tmp; tmp = AllocMass(NULL); CutAllPixelsFromMass(m, tmp); CutAllPixelsFromMass(m2, m); CutAllPixelsFromMass(tmp, m2); FreeMass(tmp); tmpBufferMass = m->bufferMass; m->bufferMass = m2->bufferMass; m2->bufferMass = tmpBufferMass; DebugLevel(3, "Swapped child and parent.\n"); } } else { DebugLevel(3, "Child creation failed.\n"); RemoveMassFromField(m2); } } // Removes m, transforms m->pixels to a new mass and returns a pointer to it // class may be a space seperated list (random pick) Mass * TransformMassInto(Mass * m, char * class) { // create a new mass and get the old mass's pixels Mass * m2; Mass * parent; char * classPicked; classPicked = malloc_ChooseStringFromList(class, ' '); if (!classPicked) Die("Error: mass of class '%s' could not transform (empty transform string?)\nString: '%s'\n", m->class, class); parent = MassClassByName(classPicked); if (!parent) { if (!streq(classPicked, "self")) Die("Error: mass of class '%s' wanted to transform into\nundefined class '%s'\n", m->class, classPicked); parent = MassClassByName(m->class); } xfree(classPicked); m2 = AllocMass(parent); CutAllPixelsFromMass(m, m2); DebugLevel(3, "Transform Mass %s ==> %s\n", m->class, m2->class); // order does matter, for playerBody reference counting if (m->transformationSavesPlayerid) m2->player = m->player; AddMassToField(m2); m2->bufferMass = m->bufferMass; m2->heading = m->heading; // FIXME: make this configurable? // FIXME: messes up with mass interfaces, why?? // RemoveMassFromField(m); return m2; } void ReThink(Mass * m) { MassInterface * mi; /* handle growth (temporary) */ /* and avoid spikes (temporary too) */ /* if (m->numPixels && m->growRounds && (m->volume / m->numPixels < HEIGHT_NORM*20)) { m->growRounds--; m->bufferMass += HEIGHT_NORM*3/2; } */ if (m->growRounds) { m->growRounds--; m->bufferMass += HEIGHT_NORM*3/2; if (m->numPixels < 4) { /* boost at the start, because flattening of one lonely pixel will shrink it to 1/9 of its height immediately */ m->bufferMass += 9*HEIGHT_NORM; } if (m->superGrow) { m->bufferMass += m->numPixels * HEIGHT_NORM/2; } } if (m->growIfSmallerThan && m->numPixels < m->growIfSmallerThan) { m->bufferMass += HEIGHT_NORM; } for (mi=m->interfaces; mi->m; mi++) { Mass * m2 = mi->m; if (m2) { if (mi->contact) { if (m->isLightningMass) { m->pulse += 3; m->heading = VectorToAngle(wrap_VectorFromTo(GetMassCenter(m), GetMassCenter(m2))); } if (m->player != -1) { if (player[m->player].button1) { /* player button 1 over-animates masses in touch */ if (m->energy > 0 && m2->speed < 2*256) { m2->speed += 5; m->energy--; } } else { /* player proximity reanimates frozen masses */ if (m2->speed < 5) m2->speed = 5; } } if (m->isWall && m2->isWall && m2->speed < m->speed / 2) { /* hig-speed masses need to keep their neighbours vivid */ m2->speed++; } if (MIN(m->sickness, SICKNESSDURATION - m->sickness) > SICKNESSDURATION/8 && !m2->sickness) { DebugLevel(2, "Infected!\n"); m2->sickness = SICKNESSDURATION; } if ((m->player != -1 && m2->wakeOnPlayer) || (m->speed > 50 && m2->wakeOnTouch)) { m2->wakeOnPlayer = 0; m2->wakeOnTouch = 0; m2->speed = 256; if (m2->isWall) { m2->speed = m->speed / 4; m2->heading = VectorToAngle(wrap_VectorFromTo(GetMassCenter(m2), GetMassCenter(m))); m2->flatteningMovement = 1; //m2->velocity = 256; m2->velocity = 0; } } } } } } void PreFlattening_BaseLineGrowth(Mass * m) { /* This draws a line going through the center of the mass, in the m->heading direction (forth as well as back). Along this line, the height will increased/set. */ Point center; Point pos; Point delta; FieldPoint * fp; Point p, lastPoint; Rect rect256; int backward; center = GetMassCenter(m); p = center; fp = FieldPoint(p); /* everything is *256, to be more accurate but still fast. */ /* + 128 for rounding */ center.x = center.x * 256 + 128; center.y = center.y * 256 + 128; rect256.x = m->maxRect.x * 256; rect256.y = m->maxRect.y * 256; rect256.w = m->maxRect.w * 256; rect256.h = m->maxRect.h * 256; delta = AngleToVector(m->heading, m->baseLineGrowth_step); if (m->baseLineGrowth_randomStepStart) { int movement; movement = randarea(-m->baseLineGrowth_step/2, m->baseLineGrowth_step); center.x += movement; center.y += movement; } pos = center; lastPoint = p; for (backward=0; backward<2; backward++) { if (backward) { /* restart at the center, but center itself is already done */ delta.x = -delta.x; delta.y = -delta.y; pos.x = center.x + delta.x; pos.y = center.y + delta.y; p.x = pos.x>>8; p.y = pos.y>>8; fp = FieldPoint(p); lastPoint = p; } while (/*FIXME: wrap?*/nowrap_IsPointInRect(pos, rect256)) { /* 1. operate on this point (if valid) */ if (fp->owner == m) { if (m->baseLineGrowth) { if (m->baseLineGrowth_setHeight) { m->bufferMass += fp->height - m->baseLineGrowth; fp->height = m->baseLineGrowth; } else { fp->height += m->baseLineGrowth; m->bufferMass -= m->baseLineGrowth; } } lastPoint = p; } /* 2. go to next point */ pos.x += delta.x; pos.y += delta.y; p.x = pos.x>>8; p.y = pos.y>>8; fp = FieldPoint(p); } /* special threatement of the start/end */ { int height; if (backward) { height = m->baseLineGrowth_start; } else { height = m->baseLineGrowth_end; } if (height > 0) { p = lastPoint; fp = FieldPoint(p); if (m->baseLineGrowth_setHeight) { m->bufferMass += fp->height - height; fp->height = height; } else { fp->height += height; m->bufferMass -= height; } } } } } void PreFlattening_BaseLineGrowthFuzzy(Mass * m) { int x, y; int dx, dy; int distance; Point center; center = GetMassCenter(m); dy = cos((float)m->heading * 2 * M_PI / 256) * 256; dx = sin((float)m->heading * 2 * M_PI / 256) * 256; /* that's what it is at the center point */ distance = 0; /* go to the starting point */ distance += (m->maxRect.x - center.x) * dx; distance += (m->maxRect.y - center.y) * dy; for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { int h; FieldPoint * p; p = FieldXY(x, y); if (p->owner == m) { h = (distance*distance)/(256*256)*HEIGHT_NORM/8; assert(h>=0); if (h > p->height) { m->bufferMass += p->height; p->height = 0; } else { m->bufferMass += h; p->height -= h; } } distance += dx; } distance -= m->maxRect.w * dx; distance += dy; } } #if 0 /* TODO: find a good location for such things. */ void MazeWallChecks(Mass * m) { int i, over; Point move; Point c1; move.x = 0; move.y = 0; c1 = GetMassCenter(m); over = 0; { /* 1. move into the direction of interfaced but unconnected walls. */ for (i=0; iisMazeWall &&*/ !interfaces[i].contact ) { Mass * m2 = interfaces[i].m[1]; /* the int */ move = wrap_VectorFromTo(c1, GetMassCenter(m2)); over = 1; } } } if (!over) { /* 2. keep a minimum distance from unconnected masses nearby */ int radius = 40; int x, y; Mass * list[50]; Mass ** p; list[0] = NULL; for (y=c1.y-radius; yowner; if (m2) { /* add the item to the list, and avoid double entries */ int found = 0; for (p=list; *p; p++) { if (*p == m2) { found = 1; break; } } if (!found) { /* FIXME: possible overflow (50) */ *(p++) = m2; *(p) = NULL; } } } /* now the list contains all masses in the neighbourhood */ for (p=list; *p; p++) { Point v; int d; Mass * m2; m2 = *p; d = wrap_GetRealDistance(c1, GetMassCenter(m2)); if (d < 4*GetMassRadius(m) /*OPTIMIZE!!! sqrt... FIXME: other way to measure distance (border...) */) { /* too near - move away */ v = wrap_VectorFromTo(c1, GetMassCenter(m2)); } else { /* too far - move towards it (only if it is another wall??) */ v = wrap_VectorFromTo(GetMassCenter(m2), c1); /* TODO: only if not too far away. */ } move.x += (double)(v.x) / GetRealLength(v) * 256; move.y += (double)(v.y) / GetRealLength(v) * 256; over = 1; } } if (!over) { /* move randomly.. or better, not at all. */ m->flatteningMovement = 0; return; } /* calculate final moving direction */ m->heading = VectorToAngle(move); m->flatteningMovement = 1; } #endif void PreFlattening(Mass * m) { /* FIXME: split this up into per-mass-type or per-feature functions. */ Point massCenter; int contact; if (m->maxRect.w <= 0 || m->maxRect.h <= 0) return; m->age++; massCenter = GetMassCenter(m); { MassInterface * mi; int playersSeen = 0; contact = 0; m->lastPlayerSeenSince++; for (mi=m->interfaces; mi->m; mi++) { if (mi->contact) contact++; if (mi->m != m && mi->m->player != -1) { playersSeen++; if (m->lastPlayerSeen != mi->m->player) { m->lastPlayerSeenSince = 0; } m->lastPlayerSeen = mi->m->player; } } if (!playersSeen) m->lastPlayerSeen = -1; } if (m->changePlayer && m->lastPlayerSeen != -1 && m->lastPlayerSeenSince >= m->changePlayer && m->player != m->lastPlayerSeen) { if (m->playerBody && m->player != -1) PlayerLostBody(m->player); m->player = m->lastPlayerSeen; if (m->playerBody) PlayerFoundBody(m->player); } if (m->energyAdd && m->energy < m->maxEnergy) { if (m->energyAdd > 0) { m->energy += m->energyAdd; } else { if (m->age % (-m->energyAdd) == 0) m->energy++; } if (m->energy > m->maxEnergy) m->energy = m->maxEnergy; } if (m->headingAdd != 0 && (!m->slowHeadingAdd || m->age % 32 == 0)) { if (m->headingAdd > 0) { m->heading = (m->heading + m->headingAdd) & 255; } else { int half = -m->headingAdd/2; m->heading = (m->heading + randarea(-half, 2*half+1)) & 255; } } if (m->speedPerturb) { m->speed += randarea(-1, 3); if (m->speed <= 0) m->speed = 1; if (randarea(0, 100) == 0) m->speedPerturb = 0; } if (m->particleResetTime) { if (m->age % m->particleResetTime == 0) { m->particleReceptor1 = 0; m->particleReceptor2 = 0; m->particleReceptor3 = 0; } } if (m->player != -1) { Player * p = player + m->player; if (m->playerControl == PC_DIRECT) { // adapt to player input if (p->force_abs) { m->heading = VectorToAngle(p->force); } m->velocity = p->force_abs; } else if (m->playerControl == PC_SWARM) { // swarm player mass: adapt to player input Point to_player = wrap_VectorFromTo(massCenter, PlayerPos(p)); int to_playerheading; int to_playercenter; if (p->button1) m->pulse = 300; to_playercenter = AngleFromTo(m->heading, VectorToAngle(to_player)); to_playerheading = (p->force_abs) ? AngleFromTo(m->heading, VectorToAngle(p->force)) : 0; m->heading += randarea(-4, 9); // FIXME: replace with global randheading parameter m->heading += to_playerheading / 16; m->heading += to_playercenter / 32; m->velocity = 80; // always move a bit m->velocity += p->force_abs; if (wrap_GetSquareDistance(massCenter, PlayerPos(p)) > sqr(80)) { // try not to go out of range! m->heading += to_playercenter / 8; m->velocity += 100; } if (m->pulse) { m->pulse--; m->heading += MIN(200, m->pulse) * to_playercenter / 256; m->velocity += 80; } m->velocity = 256; } if (p->button1 && strlen(m->transformOnButton1)) { Mass * m2; m2 = TransformMassInto(m, m->transformOnButton1); // m is dead now, but this shouldn't harm further processing I think } if (p->button2 && strlen(m->transformOnButton2)) { Mass * m2; m2 = TransformMassInto(m, m->transformOnButton2); // m is dead now, but this shouldn't harm further processing I think } /* TODO: shrink the player? */ /* TODO: adapt speed to size? */ if (p->button1) m->pulse += 100; /* set player speed { int speed; speed = 128 + 256*256 / m->numPixels; speed = MIN(speed, 2*256); m->speed = speed; }*/ if (m->attractPlayer) { Point v; int d; Player * p = player + m->player; v = wrap_VectorFromTo(PlayerPos(p), massCenter); p->velocity.x += v.x * m->attractPlayer / 256; p->velocity.y += v.y * m->attractPlayer / 256; d = GetSquareLength(v); /* if (d < sqr(80)) { if (m->attractPlayer < 256) m->attractPlayer++; } else { if (m->attractPlayer > 50) m->attractPlayer--; } */ //p->heading += AngleFromTo(p->heading, m->heading) / 8; } if (m->playerLightning && p->button2 && m->energy > 50) { int i, heading; Point p; E_Lightning * l; m->energy -= 50; l = AddLightning(); l->duration = 30; l->age = 0; l->r = 4; l->maxR = 40; RGBSET(l->c, 101, 17, 255); l->numPoints = 6; p = massCenter; heading = m->heading; for (i=0; inumPoints; i++) { l->points[i] = p; p = AddVectors(p, AngleToVector(heading, randarea(20, 10))); if (i%2) { heading += randarea(10, 10); } else { heading -= randarea(10, 10); } } } } // m->player != -1 if (m->swarm && m->age > 10) { if (m->swarm_pulse > 1) m->swarm_pulse--; if (m->velocity < 156) m->velocity += 10; if (m->age % 4 == 0) { /* time to reconsider the situation */ int desiredAngle, totPulse; List * list; Mass * m2; Mass * nearest; int nearestDist; int sightDistance = 60; Rect r; Point desiredVector; /* FIXME: doesn't work as expected */ //desiredAngle = VectorToAngle(LookAroundSimplestPath(massCenter, 40, m)); /* find all other swarm masses in region */ r.x = massCenter.x - sightDistance/2; r.y = massCenter.x - sightDistance/2; r.w = sightDistance; r.h = sightDistance; list = GetMassesInRect(r); totPulse = 0; desiredAngle = 0; // keep the compiler happy nearestDist = 0; // dito desiredVector.x = 0; desiredVector.y = 0; nearest = NULL; FOR_ITEM_IN_LIST(m2, list) { if (m2->swarm) { int d; desiredVector = AddVectors(desiredVector, AngleToVector(m2->heading, m2->swarm_pulse * 256)); totPulse += m2->swarm_pulse * 256; d = wrap_GetSquareDistance(massCenter, GetMassCenter(m2)); if (d < nearestDist || !nearest) { nearest = m2; nearestDist = d; } } } list_free(list); /* TODO: idea: the swarm seekes players. It will circle around one, because if it comes to close the pressure to avoid the obstacle (the player mass) becomes more important than going stright to the player, and it will stay circling because it can not adapt the new direction (the player) fast enough (or just as before, doesn't want to get to close to the obstacle) */ if (totPulse) { /* the desire is all about peer pressure ;-) */ if (nearestDist > sqr(sightDistance*2/4)) { /* oh, oh, the others are going to get out of sight! */ //if (m->speed < 2*256) m->speed += 25; /* cry out in pain! */ m->swarm_pulse += 20; m->energy -= 20; if (m->energy < 0) { m->swarm_pulse += m->energy; m->energy = 0; } //desiredAngle = VectorToAngle(wrap_VectorFromTo(massCenter, GetMassCenter(nearest))); //m->heading = VectorToAngle(wrap_VectorFromTo(massCenter, GetMassCenter(nearest))); //m->heading += 20*sign(((desiredAngle - m->heading)&255) - 128); } else { if (m->speed > 180) m->speed -= 25; desiredAngle = VectorToAngle(desiredVector); m->heading -= 10*sign(((desiredAngle - m->heading)&255) - 128); } } /* but without the will of each member, a swarm does nothing */ m->heading = LazyGetBetterDirection(massCenter, m->heading, 5, 40, m); //m->heading += randarea(-10, 21); } /* show the pulse */ RGBSET(m->baseColor, MIN(255, 100+m->swarm_pulse), MIN(255, 100+m->swarm_pulse), MIN(255, m->swarm_pulse*10)); } if (m->cell) { if (m->age > 8) { int r; /* radius */ int found; Mass * neigh[8]; found = 0; r = 0; m->age = 0; /* get the eight nearest cell masses */ /* FIXME: it is not circular but a rectangle */ while (found < 8 && r < 20) { int steps; int i; r++; for (i=0; i<4; i++) { for (steps=-r; steps <= r; steps++) { int x, y; Mass * m2; x=y=0; // avoid compiler warning switch (i) { case 0: x = massCenter.x + r; y = massCenter.y + steps; break; case 1: x = massCenter.x - r; y = massCenter.y + steps; break; case 2: x = massCenter.x + steps; y = massCenter.y + r; break; case 3: x = massCenter.x + steps; y = massCenter.y - r; break; default: assert(0); } if ((m2 = FieldXY(x, y)->owner) && (m2->cell) && (m != m2)) { int j, ok; ok = 1; for (j=0; jcell_activation; } /* game of live rules */ if ((m->cell_activation == 1 && (c == 2 || c == 3)) || (m->cell_activation == 0 && (c == 3))) { m->cell_activation = 1; } else { m->cell_activation = 0; } if (m->cell_activation == 1) { RGBSET(m->baseColor, 220, 220, 255); } else { RGBSET(m->baseColor, 80, 80, 80); } } else { /* did not find enough neighbours to play the game - do not change the state. */ } } } if (m->shrink) m->bufferMass -= m->shrink; /* small masses are hard and buggy to control, so just kill them ;) */ if (m->numPixels < 40 && m->age > 100) m->bufferMass -= HEIGHT_NORM / 8; if (m->pulse) { assert(m->pulse > 0); m->pulse -= 5; if (m->pulse < 0) m->pulse = 0; } if (m->pulse) { Mass * m2; List * list; Rect r; int maxRadius; if (m->pulse > 40) m->pulse = 40; /* distribute the pulse */ maxRadius = m->pulse; r.x = massCenter.x - maxRadius; r.y = massCenter.y - maxRadius; r.w = r.h = 2*maxRadius; list = GetMassesInRect(r); FOR_ITEM_IN_LIST(m2, list) { if (wrap_GetSquareDistance(massCenter, GetMassCenter(m2)) < maxRadius*maxRadius) { if (m2->pulse < m->pulse) { m2->pulse += 5; } } } list_free(list); } if (m->isLightningMass && m->pulse > 20 && m->energy > 80) { int i, heading; Point p; E_Lightning * l; m->energy -= 80; /* replace some of the mass that is going to be lost */ m->bufferMass += 100*HEIGHT_NORM; m->growRounds += 300; l = AddLightning(); l->duration = 30; l->age = 0; l->r = 4; l->maxR = 40; RGBSET(l->c, 255, 200, 0); l->numPoints = 10; p = massCenter; heading = m->heading + randarea(-30, 61); for (i=0; inumPoints; i++) { l->points[i] = p; p = AddVectors(p, AngleToVector(heading, randarea(20, 10))); if (i%2) { heading += randarea(20, 20); } else { heading -= randarea(20, 20); } } } if (m->isProjectil) { if (m->age > 20) { if (m->vivid < 200) m->vivid++; } } if (m->speed > m->minSpeed) { if (m->decreaseSpeed > 0) m->speed -= m->decreaseSpeed; if (m->decreaseSpeed < 0 && randarea(0, -m->decreaseSpeed) == 0) m->speed--; } if (m->speed <= m->minSpeed) m->speed = m->minSpeed; if (m->isMazeWall) { //MazeWallChecks(m); } /* if (m->playerFood) { if (m->baseColor[0] < 255) m->baseColor[0]++; if (m->baseColor[1] > 111) m->baseColor[1]--; if (m->baseColor[2] > 80) m->baseColor[2]--; } */ if (strlen(m->transformOnBodylessPlayer)) { int id; for (id=0; idtransformOnBodylessPlayer); assert(m2->player == -1); // mass classes with player id pre-set do not make sense m2->player = id; DebugLevel(3, "TransformOnBodylessPlayer: Transformed because player[%d].bodies = %d.\n", id, player[id].bodies); if (m2->playerBody) PlayerFoundBody(id); break; } } } if (m->vivid > 0 && m->age > 5) { int r, i; Point p; r = 16; // FIXME: not useful, but sqrt() bad if (r < 2*2) r = 2*2; m->heading += randarea(-2, 5); p = GetBorderPointFromCenterInDirection(m, m->heading+128); for (i=0; ivivid; i++) { Point p1; FieldPoint * fp; p1.x = p.x + randarea(-r/2, r); p1.y = p.y + randarea(-r/2, r); if ((fp=FieldPoint(p1))->owner == m) { m->bufferMass += fp->height/2; fp->height -= fp->height/2; } } } if (m->vivid2 && m->age > 5) { /* create a "bearable" number of avoidpoints */ int i, count, idealCount; ShapePoint * sp; idealCount = m->numPixels / 9; count = CountShapePoints(m); if (count < idealCount) { sp = &m->shapePoints[GetFreeShapePoint(m)]; sp->radius = 7; sp->strength = -m->vivid2*HEIGHT_NORM/4; sp->pos.x = randarea(m->maxRect.x, m->maxRect.w); sp->pos.y = randarea(m->maxRect.y, m->maxRect.h); } /* move them randomly */ for (i=0; ishapePoints[i]; sp->pos.x += randarea(-1, 3); sp->pos.y += randarea(-1, 3); /* perhaps... FitPointIntoCircle? */ wrap_FitPointIntoRect(&sp->pos, m->maxRect); } } if (m->sickness) { //if (randarea(0, SICKNESSDURATION/2) < MIN(m->sickness, SICKNESSDURATION - m->sickness)) { if (m->sickness) { FieldPoint * p; int i; i = MIN(m->sickness, SICKNESSDURATION - m->sickness) * m->numPixels/(20 * SICKNESSDURATION); if (i == 0) i = 1; assert(i>0); while (i--) { p = FieldPoint(GetRandomMassPoint(m)); m->bufferMass += p->height / 2; p->height = 0; } } m->sickness--; if (!m->sickness) DebugLevel(1, "Sickness over.\n", m->sickness); } PreFlattening_ShapePoints(m); if (m->baseLineGrowth) PreFlattening_BaseLineGrowth(m); /* saved comment from m->isPlant removal - an der Spitze wachsen, aber in Kontakt mit dem Stamm bleiben - Richtung (heading) veraendern (freies Gelaende bevorzugt?) - wenn ein Hinderniss kommt *nicht* wachsen. - Wenn Wachstum abgeschlossen, eventuell einen Ast wachsen lassen - Verlangsamung (speed), erhaertung. - Ueberpruefen, ob Kontakt zum Stamm. Wenn nein, absterben. */ if (m->isCoinMass) { DeleteRandomPixel(m); AddBorderPixel(m); } if (m->drawSparks && randarea(0, 20) == 0) { E_Spark * s; s = AddSpark(); s->duration = 10; s->radius = 5; s->x = massCenter.x; s->y = massCenter.y; RGBSET(s->c, 241, 251, 89); } if (m->foggy && randarea(0, 500) == 0) { E_Fog * f; int b; f = AddFog(); f->pos256.x = massCenter.x * 256; f->pos256.y = massCenter.y * 256; // TODO (idea) use grayed complementary color of the mass generating the fog b = randarea(64, 100); RGBSET(f->c, randarea(b-10, 20), randarea(b-10, 20), randarea(b-10, 20)); //RGBSET(f->c, 255, 255, 255); //f->velocity = AngleToVector(randarea(m->heading, 128), randarea(100, 20)); //f->velocity = AngleToVector(randarea(0, 256), randarea(5, 20)); f->velocity = AngleToVector(randarea(m->heading-64, 128), randarea(1, 8)); f->strength = 8*256; f->maxstrength = f->strength; f->radius = randarea(10, 23); } #if 0 if (m->isNetworkMass) { int i; //Point p; /* TODO: open the mass if networking enabled */ //ForceMassCircle(m, XYToPoint(0, 0), NETWORKMASS_RADIUS_SMALL, NETWORKMASS_RADIUS_LARGE); //ForceMassMoon(m, XYToPoint(0, 0), NETWORKMASS_RADIUS_SMALL, XYToPoint(0, 0), NETWORKMASS_RADIUS_LARGE); i = (m->age / 50) % 20; ForceMassMoon(m, XYToPoint(i, i), NETWORKMASS_RADIUS_SMALL, XYToPoint(0, 0), NETWORKMASS_RADIUS_LARGE); //if (m->bufferMass < 0) m->bufferMass = 0; /* if (m->age % 8 == 0) m->heading++; p = AngleToVector(m->heading, (NETWORKMASS_RADIUS_SMALL+NETWORKMASS_RADIUS_LARGE)/2); */ //ForceMassCircle(NULL, p, 0, NETWORKMASS_RADIUS_LARGE - NETWORKMASS_RADIUS_SMALL); //ForceMassCircle(NULL, XYToPoint(0, 0), 0, NETWORKMASS_RADIUS_SMALL-3); //ForceMassCircle(NULL, XYToPoint(0, 0), NETWORKMASS_RADIUS_LARGE, NETWORKMASS_RADIUS_LARGE+3); } #endif // children and transformations if (m->age > 5) { if (m->childTime > 0 && m->age % m->childTime == 0) GenerateChildMass(m); if (m->childTime < 0 && randarea(0, - m->childTime) == 0) GenerateChildMass(m); if (m->transformAt != TR_NEVER) { if ((m->transformAt == TR_AGE && m->age >= m->transformArgument) || (m->transformAt == TR_ENERGY && m->energy >= m->transformArgument) || (m->transformAt == TR_SIZE && m->numPixels >= m->transformArgument) || (m->transformAt == TR_RANDOM && randarea(0, m->transformArgument) == 0) || (m->transformAt == TR_TOUCH && contact)) { TransformMassInto(m, m->transformClass); /* Note, returned mass ignored; m is dead now. */ return; } } } // transformAtParticle if (m->transformOnParticle) { if (m->age >= m->transformOnParticle_minAge && (!m->transformOnParticle_maxAge || m->age < m->transformOnParticle_maxAge)) { if ((m->transformOnParticle == 1 && m->particleReceptor1) || (m->transformOnParticle == 2 && m->particleReceptor2) || (m->transformOnParticle == 3 && m->particleReceptor3) || (m->transformOnParticle == -1 && !m->particleReceptor1) || (m->transformOnParticle == -2 && !m->particleReceptor2) || (m->transformOnParticle == -3 && !m->particleReceptor3)) { TransformMassInto(m, m->transformOnParticle_transformClass); /* Note, returned mass ignored; m is dead now. */ } } } } void DebugMaxRectCheck(Mass * m) { if (HEAVY_DEBUG_ENABLED) { int x, y; for (y=0; ymaxRect.x) & FIELDWRAP; y1 = (y - m->maxRect.y) & FIELDWRAP; if (x1 >= 0 && x1 < m->maxRect.w && y1 >= 0 && y1 < m->maxRect.h) { // nothing } else { /* this point is not included in the maxRect */ assert(FieldXY(x, y)->owner != m); } } } else { /* TODO: replace this code, it's not that easy with wrapping borders any more */ /* int x, y; for (x=m->maxRect.x-2; xmaxRect.x+m->maxRect.w+2; x++) { y = m->maxRect.y-1; assert(FieldXY(x, y)->owner != m); y = m->maxRect.y-2; assert(FieldXY(x, y)->owner != m); } for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { x = m->maxRect.x-1; assert(FieldXY(x, y)->owner != m); x = m->maxRect.x+m->maxRect.w; assert(FieldXY(x, y)->owner != m); } */ } } MassInterface * GetMassInterface(Mass * m, Mass * m2) { MassInterface * mi = m->interfaces; while (mi->m && mi->m != m2) mi++; // the last entry in m->interfaces is an empty one, used as both // an interface to the NULL mass, as well as to masses without interface return mi; } /* good code */ void ConvolveMass256_nonexpanding(Mass * m, int * matrix, int radius) { int x, y; int * copy; int * src; Rect r; if (m->numPixels == 0) return; r = m->maxRect; r.x -= radius; r.y -= radius; r.w += 2*radius; r.h += 2*radius; copy = malloc(r.h*r.w*sizeof(int)); /* selectively copy the source region */ src = copy; for (y=r.y; yowner) { h = 0; } else if (p->owner == m) { h = p->height; } else { MassInterface * mi; mi = GetMassInterface(m, p->owner); h = (p->height * mi->borderReaction) >> 8; } *src++ = h; } } assert(src == copy+r.h*r.w); /* apply the matrix using the copy as input */ { int size; int wraparound; int buffdiff=m->bufferMass; Rect mr = m->maxRect; size = 2*radius+1; src = copy; wraparound = r.w - size; for (y=mr.y; yowner == m) { int i, j; int * mt; int h = 0; mt = matrix; for (i=size; i--;) { for (j=size; j--;) { h += *src++ * *mt++; } src += wraparound; } if (h<0) h=0; h >>= 8; m->bufferMass += p->height - h; p->height = h; src = src - size * r.w; } src++; } src += size-1; } assert(src + 2*radius*r.w == copy + r.w*r.h); buffdiff = m->bufferMass - buffdiff; // the smaller this value the better masses will melt together //DebugLevel(1, "Buffdiff = %d (%d * HEIGHT_NORM)\n", buffdiff, buffdiff/HEIGHT_NORM); } xfree(copy); } /* faster movement */ void ConvolveMass256_expanding(Mass * m, int * matrix, int radius) { int x, y; int * copy; int * src; int radius2; Rect r; if (m->numPixels == 0) return; radius2 = 2*radius; r = m->maxRect; r.x -= radius; r.y -= radius; r.w += 2*radius; r.h += 2*radius; copy = malloc(r.h*r.w*sizeof(int)); /* selectively copy the source region */ src = copy; for (y=m->maxRect.y-radius; ymaxRect.y+m->maxRect.h+radius; y++) { for (x=m->maxRect.x-radius; xmaxRect.x+m->maxRect.w+radius; x++) { FieldPoint * p; int h; p = FieldXY(x, y); if (!p->owner) { h = 0; } else if (p->owner == m) { h = p->height; } else { MassInterface * mi; mi = GetMassInterface(m, p->owner); h = (p->height * mi->borderReaction) / 256; } *src++ = h; } } assert(src == copy+r.h*r.w); /* apply the matrix using the copy as input */ { int size; int wraparound; size = 2*radius+1; src = copy; wraparound = r.w - size; for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { FieldPoint * p; p = FieldXY(x, y); /* old, good code: if (p->owner == m) { int i, j; int * mt; int h = 0; mt = matrix; for (i=size; i--;) { for (j=size; j--;) { h += *src++ * *mt++; } src += wraparound; } if (h<0) h=0; h >>= 8; m->bufferMass += p->height - h; p->height = h; src = src - size * r.w; } */ /* new, fast moving, evil code: */ { int i, j; int * mt; int h = 0; mt = matrix; for (i=size; i--;) { for (j=size; j--;) { h += *src++ * *mt++; } src += wraparound; } if (h<0) h=0; h >>= 8; if (!p->owner && h > m->minBorderHeight) { p->owner = m; p->height = 0; wrap_ExpandRectToIncludePoint(&m->maxRect, x, y); m->numPixels++; } if (p->owner == m) { m->bufferMass += p->height - h; p->height = h; } src = src - size * r.w; } src++; } src += size-1; } assert(src + 2*radius*r.w == copy + r.w*r.h); } xfree(copy); } void FastMovement(Mass * m) { int x, y; Rect r; if (m->numPixels == 0) return; r = m->maxRect; r.x += 3; m->maxRect = r; for (y=r.y; yowner == m) { p->owner = NULL; m->bufferMass += p->height; p->height = 0; m->numPixels--; } } if (MIN(r.y+r.h-1 - y, y-r.y) < 3) continue; for (x=r.x+r.w-3; xowner) { p->owner = m; p->height = HEIGHT_NORM; m->bufferMass -= p->height; m->numPixels++; } } } } void UpdateInterfaces(Mass * m) { int i; int neighbourCount; List * neighbours; MassInterface * mi; MassInterface * miNew; Mass * m2; if (m->lookDistance) { Rect r; r.x = m->maxRect.x - m->lookDistance; r.y = m->maxRect.y - m->lookDistance; r.w = m->maxRect.w + 2*m->lookDistance; r.h = m->maxRect.h + 2*m->lookDistance; neighbours = GetMassesInRect(r); } else { neighbours = list_alloc(); list_append(neighbours, m); } neighbourCount = list_len(neighbours); // FIXME: the mass also holds an interface to itself /* remove stale entries, update lasting entries */ miNew = malloc((neighbourCount+1) * sizeof(MassInterface)); for (i=0; i < neighbourCount+1; i++) { m2 = (i < neighbourCount) ? list_index(neighbours, i) : NULL; /* check whether this neighbour was already known */ mi = GetMassInterface(m, m2); if (mi->m) { mi->oldContact = mi->contact; mi->contact = 0; memcpy(miNew+i, mi, sizeof(MassInterface)); assert((miNew+i)->m == m2); } else { memset(miNew+i, 0, sizeof(MassInterface)); (miNew+i)->m = m2; } if ((miNew+i)->m) (miNew+i)->m->refcount++; } for (mi=m->interfaces; mi->m; mi++) mi->m->refcount--; xfree(m->interfaces); m->interfaces = miNew; list_free(neighbours); } /* FIXME: rename this function, it actually does lots more housekeeping */ void DistributeBufferMass(Mass * m) { int x, y; int addToEachPixel; FieldPoint * p = NULL; Rect r; if (m->numPixels == 0) return; /* OPTIMIZE: perhaps this could be done elsewhere */ m->volume = 0; m->maxHeight = 0; addToEachPixel = m->bufferMass / m->numPixels; /* may get < 0 */ DebugLevel(4, "AddToEachPixel is %d (%d/%d)\n", addToEachPixel, m->bufferMass, m->numPixels); r.w = 0; for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { int found = 0; for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { p = FieldXY(x, y); if (p->owner == m) { p->height += addToEachPixel; if (p->height < 0) { m->bufferMass += p->height; p->height = 0; } m->maxHeight = MAX(m->maxHeight, p->height); m->volume += p->height; /* manual inline optimization: wrap_ExpandRectToIncludePoint(&r, x, y); */ if (r.w == 0) { r.w = 1; r.h = 1; r.x = x; r.y = y; } else { if (!found && x < r.x) { r.w += r.x-x; r.x = x; } else if (x >= r.x+r.w) { r.w = x - r.x + 1; } } found = 1; } } if (found) { if (y < r.y) { r.h += r.y-y; r.y = y; } else if (y >= r.y+r.h) { r.h = y - r.y + 1; } } } m->maxRect = r; m->bufferMass -= addToEachPixel * m->numPixels; } void HandleBorderPoints(Mass * m) { Rect r; int x, y; int i; /* TODO: try this with 8 directions, just for fun :) */ const int addx[4] = { -1, 0, +1, 0 }; const int addy[4] = { 0, -1, 0, +1 }; //printf("HandleBorderPoints\n"); /* FIXME: the m->maxRect changes during this algo. Would it be a good idea to do the for loop with those values directly? */ /* // Experiment if (m->numPixels && m->volume) { m->maxBorderHeight = 10*HEIGHT_NORM - 10*m->volume/sqr(m->numPixels); //m->maxBorderHeight = (float)HEIGHT_NORM*(float)HEIGHT_NORM*(float)m->numPixels/(float)m->volume; //printf("maxBorderHeight = %d, HEIGHT_NORM = %d\n", m->maxBorderHeight, HEIGHT_NORM); } */ r = m->maxRect; for (y=r.y; yowner == m) { if (p->height < m->minBorderHeight) { p->owner = NULL; m->bufferMass += p->height; p->height = 0; m->numPixels--; } else { /* TODO: randomize which direction gets checked first */ FieldPoint * n[4]; int isBorderPoint = 4; /* OPTIMIZE: this is one of the more time-consuming parts */ /* Manually unrolled loop. It helps a bit, I measured it. for (i=4; i--;) { n[i] = FieldXY(x+addx[i], y+addy[i]); if (n[i]->owner == m) isBorderPoint--; } */ n[0] = FieldXY(x-1, y+0); if (n[0]->owner == m) isBorderPoint--; n[1] = FieldXY(x+0, y-1); if (n[1]->owner == m) isBorderPoint--; n[2] = FieldXY(x+1, y+0); if (n[2]->owner == m) isBorderPoint--; n[3] = FieldXY(x+0, y+1); if (n[3]->owner == m) isBorderPoint--; if (isBorderPoint) { for (i=4; i--;) { if (n[i]->owner && n[i]->owner != m) { MassInterface * mi; Mass * m2 = n[i]->owner; assert(IsValidMass(m2)); mi = GetMassInterface(m, m2); mi->contact++; if (mi->eatme && n[i]->height > p->height) { p->owner = m2; if (m->player != -1 && m2->player != -1) { /* don't eat players too fast, and take some of the total mass (so the sum will shrink) */ m->bufferMass += 3 * p->height / 4; p->height /= 4; } m->numPixels--; m2->numPixels++; wrap_ExpandRectToIncludePoint(&m2->maxRect, x, y); /* mi->m->bufferMass += p->height; p->owner = NULL; p->height = 0; m->numPixels--; */ break; } else if (mi->borderReaction <= 0 && n[i]->height > p->height) { p->owner = NULL; m->bufferMass += p->height; p->height = 0; m->numPixels--; break; } } } if (p->owner == m && p->height > m->maxBorderHeight) { for (i=4; i--;) { if (!n[i]->owner) { n[i]->owner = m; n[i]->height = p->height / 2; m->bufferMass -= p->height / 2; wrap_ExpandRectToIncludePoint(&m->maxRect, x+addx[i], y+addy[i]); m->numPixels++; break; } } } } } } } } } void DoConvolveFlattening(Mass * m) { /* light blur matrix int matrix2[3*3] = { 1*16, 2*16, 1*16, 2*16, 4*16, 2*16, 1*16, 2*16, 1*16, }; */ /* big gaussian blur matrix */ int matrix[7*7] = { 0,0 ,0 ,5 ,0 ,0 ,0, 0,5 ,18 ,32 ,18 ,5 ,0, 0,18,64 ,100,64 ,18,0, 5,32,100,100,100,32,5, 0,18,64 ,100,64 ,18,0, 0,5 ,18 ,32 ,18 ,5 ,0, 0,0 ,0 ,5 ,0 ,0 ,0, }; /* just for testing, same as above int matrixBig[13*13] = { 0,0,0,0,0 ,0 ,0 ,0 ,0 ,0,0,0,0, 0,0,0,0,0 ,0 ,0 ,0 ,0 ,0,0,0,0, 0,0,0,0,0 ,0 ,0 ,0 ,0 ,0,0,0,0, 0,0,0,0,0 ,0 ,5 ,0 ,0 ,0,0,0,0, 0,0,0,0,5 ,18 ,32 ,18 ,5 ,0,0,0,0, 0,0,0,0,18,64 ,100,64 ,18,0,0,0,0, 0,0,0,5,32,100,100,100,32,5,0,0,0, 0,0,0,0,18,64 ,100,64 ,18,0,0,0,0, 0,0,0,0,5 ,18 ,32 ,18 ,5 ,0,0,0,0, 0,0,0,0,0 ,0 ,5 ,0 ,0 ,0,0,0,0, 0,0,0,0,0 ,0 ,0 ,0 ,0 ,0,0,0,0, 0,0,0,0,0 ,0 ,0 ,0 ,0 ,0,0,0,0, 0,0,0,0,0 ,0 ,0 ,0 ,0 ,0,0,0,0, }; */ #define RADIUS 3 /* TODO: movement is a problem... it would be so easy... */ /* ideas: gaussian blur constructed from scratch, with centre replaced according to the current speed. Use radius from center, and a gaussian curve, maybe indexed [256] */ int x, y; int vx, vy; int sum = 0; Point p; Point vCenter; if (m->flatteningMovement) { vCenter = AngleToVector(m->heading, MIN(m->velocity, 256)); vCenter.x = -vCenter.x; vCenter.y = -vCenter.y; } else { vCenter.x = vCenter.y = 0; } p = AngleToVector(m->heading, m->velocity); vx = p.x; vy = p.y; for (y=-RADIUS; y<=RADIUS; y++) { for (x=-RADIUS; x<=RADIUS; x++) { int v; /* gaussian curve */ /* FIXME: don't use double */ double distsqr; distsqr = sqr(x*256 - vCenter.x) + sqr(y*256 - vCenter.y); distsqr *= 2.0/sqr(RADIUS*256); //DebugLevel(1, "distsqr=%f\n", distsqr); v = 1024.0*exp(-distsqr); //DebugLevel(1, "v=%d\n", v); matrix[(y+RADIUS)*(2*RADIUS+1)+(x+RADIUS)] = v; sum += v; } } for (x=0; xheading, MIN(m->velocity, 256)); if (abs(v.x) > randarea(0, 256)) movex = sign(v.x); if (abs(v.y) > randarea(0, 256)) movey = sign(v.y); minheight = m->minBorderHeight; r = m->maxRect; r.w += 2; r.h += 2; r.x -= 1; r.y -= 1; m->maxRect = r; yblur = calloc(r.w, sizeof(int)); // randomize order (the resulting unwanted drift depends a bit on it) // FIXME: but there is still a slight drift upwards, why? i = 0; if (randarea(0, 2)) goto label_reverse; label_forward: // forward (top-left to bottom right) for (y=r.y; y>= 1; *yb >>= 1; if (!p->owner) { m->numPixels++; p->owner = m; p->height = 0; } if (p->owner == m) { int h = p->owner ? p->height : 0; m->bufferMass += p->height; xblur += h; *yb += h; // OPTIMIZE: unroll loops instead of condition if (movex == -1) { if (movey == -1) { // do nothing } else { // don't move right p->height = ((*yb) >> 2) + (h >> 1); } } else if (movey == -1) { // don't move down p->height = ((xblur) >> 2) + (h >> 1); } else { // most common case p->height = (xblur + *yb) >> 2; } if (p->height > minheight) { m->bufferMass -= p->height; } else { m->numPixels--; p->owner = NULL; p->height = 0; } } } } if (i != 0) goto label_finish; i++; label_reverse: memset(yblur, 0, sizeof(int)*r.w); // backward (bottom right to top left) for (y=r.y+r.h-1; y>=r.y; y--) { int * yb = yblur + r.w-1; xblur = 0; for (x=r.x+r.w-1; x>=r.x; x--, yb--) { FieldPoint * p; p = FieldXY(x, y); xblur >>= 1; *yb >>= 1; if (!p->owner) { m->numPixels++; p->owner = m; p->height = 0; } if (p->owner == m) { int h = p->owner ? p->height : 0; m->bufferMass += p->height; xblur += h; *yb += h; // OPTIMIZE: unroll loops instead of condition if (movex == +1) { if (movey == +1) { // do nothing } else { // don't move left p->height = ((*yb) >> 2) + (h >> 1); } } else if (movey == +1) { // don't move up p->height = ((xblur) >> 2) + (h >> 1); } else { // most common case p->height = (xblur + *yb) >> 2; } if (p->height > minheight) { m->bufferMass -= p->height; } else { m->numPixels--; p->owner = NULL; p->height = 0; } } } } if (i == 0) { i++; goto label_forward; } label_finish: xfree(yblur); } void DoExpFlattening_normal(Mass * m) { /* fast heavy exponential blur */ int x, y; int xblur; int * yblur; Rect r; int i; int movex, movey; Point v; movex = 0; movey = 0; v = AngleToVector(m->heading, MIN(m->velocity, 256)); if (abs(v.x) > randarea(0, 256)) movex = sign(v.x); if (abs(v.y) > randarea(0, 256)) movey = sign(v.y); r = m->maxRect; yblur = calloc(r.w, sizeof(int)); // randomize order (the resulting unwanted drift depends a bit on it) // FIXME: but there is still a slight drift upwards, why? i = 0; if (randarea(0, 2)) goto label_reverse; label_forward: // forward (top-left to bottom right) for (y=r.y; y>= 1; *yb >>= 1; if (p->owner == m) { int h = p->height; xblur += h; *yb += h; // OPTIMIZE: unroll loops instead of condition if (movex == -1) { if (movey == -1) { // do nothing } else { // don't move right p->height = ((*yb) >> 2) + (h >> 1); } } else if (movey == -1) { // don't move down p->height = ((xblur) >> 2) + (h >> 1); } else { // most common case p->height = (xblur + *yb) >> 2; } m->bufferMass += h - p->height; } } } if (i != 0) goto label_finish; i++; label_reverse: memset(yblur, 0, sizeof(int)*r.w); // backward (bottom right to top left) for (y=r.y+r.h-1; y>=r.y; y--) { int * yb = yblur + r.w-1; xblur = 0; for (x=r.x+r.w-1; x>=r.x; x--, yb--) { FieldPoint * p; p = FieldXY(x, y); xblur >>= 1; *yb >>= 1; if (p->owner == m) { int h = p->height; xblur += h; *yb += h; // OPTIMIZE: unroll loops instead of condition /* if (movex == +1) { if (movey == +1) { // do nothing } else { // don't move left p->height = ((*yb) >> 2) + (h >> 1); } } else if (movey == +1) { // don't move up p->height = ((xblur) >> 2) + (h >> 1); } else { */ // most common case p->height = (xblur + *yb) >> 2; /* } */ m->bufferMass += h - p->height; } } } if (i == 0) { i++; goto label_forward; } label_finish: xfree(yblur); } void DoOldFlattening(Mass * m) { if (m->flatteningMovement) { int movex, movey; Point v; movex = 0; movey = 0; v = AngleToVector(m->heading, MIN(m->velocity, 256)); if (abs(v.x) > randarea(0, 256)) movex = sign(v.x); if (abs(v.y) > randarea(0, 256)) movey = sign(v.y); /* FIXME: remove those parameters from the mass, they belong only to this algo */ m->flatteningSHR[LEFT] = m->normalSHR; m->flatteningSHR[RIGHT] = m->normalSHR; m->flatteningSHR[UP] = m->normalSHR; m->flatteningSHR[DOWN] = m->normalSHR; /* flattening algo SHR movement */ switch (movex) { case -1: m->flatteningSHR[LEFT] = m->strongSHR; m->flatteningSHR[RIGHT] = m->weakSHR; break; case +1: m->flatteningSHR[LEFT] = m->weakSHR; m->flatteningSHR[RIGHT] = m->strongSHR; break; default: m->flatteningSHR[LEFT] = m->normalSHR; m->flatteningSHR[RIGHT] = m->normalSHR; } switch (movey) { case -1: m->flatteningSHR[UP] = m->strongSHR; m->flatteningSHR[DOWN] = m->weakSHR; break; case +1: m->flatteningSHR[UP] = m->weakSHR; m->flatteningSHR[DOWN] = m->strongSHR; break; default: m->flatteningSHR[UP] = m->normalSHR; m->flatteningSHR[DOWN] = m->normalSHR; } //DebugLevel(0, "Heading=%d, movex=%d, movey=%d\n", m->heading, movex, movey); } /* the evil big old flattening algo - but simply the best ;-) */ { int x, y; int flatteningValueR, flatteningValueL, oldHeight; FieldPoint * p; MassInterface * mi; /* flatten horizontally.... */ for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { p = FieldXY(x, y); if (p->owner == m) { /* special threatement of the sector start point */ oldHeight = p->height; mi = GetMassInterface(m, FieldXY(x-1, y)->owner); flatteningValueR = (FieldXY(x-1, y)->height * mi->borderReaction) >> (m->flatteningSHR[RIGHT] + 8); /* give something to the right neighbour */ m->bufferMass -= flatteningValueR; p->height += flatteningValueR; /* calculate flatteningValue for the new point */ flatteningValueL = oldHeight >> m->flatteningSHR[LEFT]; flatteningValueR = oldHeight >> m->flatteningSHR[RIGHT]; /* give something to the left neighbour */ p->height -= flatteningValueL; m->bufferMass += flatteningValueL; /* necessary because mi->borderContact may be negative */ if (p->height < flatteningValueR) { m->bufferMass += p->height - flatteningValueR; p->height = flatteningValueR; } /* normal threatement of the center points */ while (FieldXY(x+1,y)->owner == m) { oldHeight = FieldXY(x+1, y)->height; /* give something to the right neighbour */ p->height -= flatteningValueR; FieldXY(x+1,y)->height += flatteningValueR; x++; p = FieldXY(x, y); /* calculate flatteningValue for the new point */ flatteningValueR = oldHeight >> m->flatteningSHR[RIGHT]; flatteningValueL = oldHeight >> m->flatteningSHR[LEFT]; /* give something to the left neighbour */ p->height -= flatteningValueL; FieldXY(x-1,y)->height += flatteningValueL; } /* special threatement of the sector end point */ mi = GetMassInterface(m, FieldXY(x+1, y)->owner); flatteningValueL = (FieldXY(x+1, y)->height * mi->borderReaction) >> (m->flatteningSHR[LEFT] + 8); flatteningValueL -= flatteningValueR; p->height += flatteningValueL; m->bufferMass -= flatteningValueL; /* necessary because mi->borderContact may be negative */ if (p->height < 0) { m->bufferMass += p->height; p->height = 0; } x++; /* the current position is known to be a border pixel outside the mass. The next pixel to check ist x+1, the for loop will increment x once more. */ } } } /* ... and (CODE DUPLICATION - KEEP IN SYNC!) vertically. */ /* comments removed. */ for (x=m->maxRect.x; xmaxRect.x+m->maxRect.w; x++) { for (y=m->maxRect.y; ymaxRect.y+m->maxRect.h; y++) { p = FieldXY(x, y); if (p->owner == m) { oldHeight = p->height; mi = GetMassInterface(m, FieldXY(x, y-1)->owner); flatteningValueR = (FieldXY(x, y-1)->height * mi->borderReaction) >> (m->flatteningSHR[DOWN] + 8); m->bufferMass -= flatteningValueR; p->height += flatteningValueR; flatteningValueL = oldHeight >> m->flatteningSHR[UP]; flatteningValueR = oldHeight >> m->flatteningSHR[DOWN]; p->height -= flatteningValueL; m->bufferMass += flatteningValueL; if (p->height < flatteningValueR) { m->bufferMass += p->height - flatteningValueR; p->height = flatteningValueR; } while (FieldXY(x,y+1)->owner == m) { oldHeight = FieldXY(x, y+1)->height; p->height -= flatteningValueR; FieldXY(x,y+1)->height += flatteningValueR; y++; p = FieldXY(x, y); flatteningValueR = oldHeight >> m->flatteningSHR[DOWN]; flatteningValueL = oldHeight >> m->flatteningSHR[UP]; p->height -= flatteningValueL; FieldXY(x,y-1)->height += flatteningValueL; } mi = GetMassInterface(m, FieldXY(x, y+1)->owner); flatteningValueL = (FieldXY(x, y+1)->height * mi->borderReaction) >> (m->flatteningSHR[UP] + 8); flatteningValueL -= flatteningValueR; p->height += flatteningValueL; m->bufferMass -= flatteningValueL; if (p->height < 0) { m->bufferMass += p->height; p->height = 0; } y++; } } } } } void DoFlattening(Mass * m) { DistributeBufferMass(m); if (m->convolveFlattening) DoConvolveFlattening(m); if (m->oldFlattening) DoOldFlattening(m); if (m->expFlattening) DoExpFlattening(m); if (m->debugTest) FastMovement(m); } void ContinueMass(Mass * m) { ReThink(m); PreFlattening(m); if (HEAVY_DEBUG_ENABLED) DebugCheckField(); /* the functions below must not delete/create any masses */ UpdateInterfaces(m); RenewInterfaceReactions(m); if (!m->experimentalBorderPoints) { HandleBorderPoints(m); } //if (HEAVY_DEBUG_ENABLED) DebugCheckField(); DoFlattening(m); MassPositionHasChanged(m); if (HEAVY_DEBUG_ENABLED) { DebugCheckField(); DebugCheckMass(m); DebugLevel(3, "Post-Flattening:\n"); DebugPrintMass(m); } if (m->numPixels == 0) RemoveMassFromField(m); }