//Anything above this #include will be ignored by the compiler #include "../qcommon/exe_headers.h" #include "cm_local.h" #include "cm_patch.h" #include "cm_landscape.h" #include "../qcommon/GenericParser2.h" #include "cm_randomterrain.h" #define NOISE_SIZE 256 #define NOISE_MASK (NOISE_SIZE - 1) static float noiseTable[NOISE_SIZE]; static int noisePerm[NOISE_SIZE]; #if 0 static void CM_NoiseInit( CCMLandScape *landscape ) { int i; for ( i = 0; i < NOISE_SIZE; i++ ) { noiseTable[i] = landscape->flrand(-1.0f, 1.0f); noisePerm[i] = (byte)landscape->irand(0, 255); } } #endif #define VAL( a ) noisePerm[ ( a ) & ( NOISE_MASK )] #define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) #define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w ) static float GetNoiseValue( int x, int y, int z, int t ) { int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); return noiseTable[index]; } #if 0 static float GetNoiseTime( int t ) { int index = VAL( t ); return (1 + noiseTable[index]); } #endif static float CM_NoiseGet4f( float x, float y, float z, float t ) { int i; int ix, iy, iz, it; float fx, fy, fz, ft; float front[4]; float back[4]; float fvalue, bvalue, value[2], finalvalue; ix = ( int ) floor( x ); fx = x - ix; iy = ( int ) floor( y ); fy = y - iy; iz = ( int ) floor( z ); fz = z - iz; it = ( int ) floor( t ); ft = t - it; for ( i = 0; i < 2; i++ ) { front[0] = GetNoiseValue( ix, iy, iz, it + i ); front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); value[i] = LERP( fvalue, bvalue, fz ); } finalvalue = LERP( value[0], value[1], ft ); return finalvalue; } /****** lincrv.c ******/ /* Ken Shoemake, 1994 */ /* Perform a generic vector unary operation. */ #define V_Op(vdst,gets,vsrc,n) {register int V_i;\ for(V_i=(n)-1;V_i>=0;V_i--) (vdst)[V_i] gets ((vsrc)[V_i]);} static void lerp(float t, float a0, float a1, vec4_t p0, vec4_t p1, int m, vec4_t p) { register float t0=(a1-t)/(a1-a0), t1=1-t0; register int i; for (i=m-1; i>=0; i--) p[i] = t0*p0[i] + t1*p1[i]; } /* DialASpline(t,a,p,m,n,work,Cn,interp,val) computes a point val at parameter t on a spline with knot values a and control points p. The curve will have Cn continuity, and if interp is TRUE it will interpolate the control points. Possibilities include Langrange interpolants, Bezier curves, Catmull-Rom interpolating splines, and B-spline curves. Points have m coordinates, and n+1 of them are provided. The work array must have room for n+1 points. */ static int DialASpline(float t, float a[], vec4_t p[], int m, int n, vec4_t work[], unsigned int Cn, bool interp, vec4_t val) { register int i, j, k, h, lo, hi; if (Cn>n-1) Cn = n-1; /* Anything greater gives one polynomial */ for (k=0; t> a[k]; k++); /* Find enclosing knot interval */ for (h=k; t==a[k]; k++); /* May want to use fewer legs */ if (k>n) {k = n; if (h>k) h = k;} h = 1+Cn - (k-h); k--; lo = k-Cn; hi = k+1+Cn; if (interp) { /* Lagrange interpolation steps */ int drop=0; if (lo<0) {lo = 0; drop += Cn-k; if (hi-lon) {hi = n; drop += k+1+Cn-n; if (hi-lo=j; i--) { lerp(t,a[lo+i],a[lo+i+tmp],work[lo+i],work[lo+i+1],m,work[lo+i+1]); } } V_Op(val,=,work[lo+h],m); return (k); } #define BIG (1.0e12) static vec_t Vector2Normalize( vec2_t v ) { float length, ilength; length = v[0]*v[0] + v[1]*v[1]; length = sqrt (length); if ( length ) { ilength = 1/length; v[0] *= ilength; v[1] *= ilength; } return length; } CPathInfo::CPathInfo(CCMLandScape *landscape, int numPoints, float bx, float by, float ex, float ey, float minWidth, float maxWidth, float depth, float deviation, float breadth, CPathInfo *Connected, unsigned CreationFlags) : mNumPoints(numPoints), mMinWidth(minWidth), mMaxWidth(maxWidth), mDepth(depth), mDeviation(deviation), mBreadth(breadth) { int i, numConnected, index; float position, goal, deltaGoal; // float random, delta; bool horizontal; float *point; float currentWidth; float currentPosition; vec2_t testPoint, percPoint, diffPoint, normalizedPath; float distance, length; CreateCircle(); numConnected = -1; if (Connected) { // we are connecting to an existing spline numConnected = Connected->GetNumPoints(); if (numConnected >= SPLINE_MERGE_SIZE) { // plenty of points to choose from mNumPoints += SPLINE_MERGE_SIZE; } else { // the existing spline doesn't have enough points mNumPoints += numConnected; } } mPoints = (vec4_t *)malloc(sizeof(vec4_t) * mNumPoints); mWork = (vec4_t *)malloc(sizeof(vec4_t) * (mNumPoints+1)); mWeights = (vec_t *)malloc(sizeof(vec_t) * (mNumPoints+1)); length = sqrt((ex-bx)*(ex-bx) + (ey-by)*(ey-by)); if (fabs(ex - bx) >= fabs(ey - by)) { // this appears to be a horizontal path mInc = 1.0 / fabs(ex - bx); horizontal = true; position = by; goal = ey; deltaGoal = (ey-by) / (numPoints-1); } else { // this appears to be a vertical path mInc = 1.0 / fabs(ey - by); horizontal = false; position = bx; goal = ex; deltaGoal = (ex-bx) / (numPoints-1); } normalizedPath[0] = (ex-bx); normalizedPath[1] = (ey-by); Vector2Normalize(normalizedPath); // approx calculate how much we need to iterate through the spline to hit every point mInc /= 16; currentWidth = landscape->flrand(minWidth, maxWidth); currentPosition = 0.0; for(i=0;iGetPoint(index); mPoints[i][0] = point[0]; mPoints[i][1] = point[1]; mPoints[i][3] = point[3]; } else { if (horizontal) { // we appear to be going horizontal, so spread the randomness across the vertical mPoints[i][0] = ((ex - bx) * currentPosition) + bx; mPoints[i][1] = position; } else { // we appear to be going vertical, so spread the randomness across the horizontal mPoints[i][0] = position; mPoints[i][1] = ((ey - by) * currentPosition) + by; } currentPosition += 1.0 / (numPoints-1); // set the width of the spline mPoints[i][3] = currentWidth; currentWidth += landscape->flrand(-0.10, 0.10); if (currentWidth < minWidth) { currentWidth = minWidth; } else if (currentWidth > maxWidth) { currentWidth = maxWidth; } // see how far we are from the goal /* delta = (goal - position) * currentPosition; // calculate the randomness we are allowed at this place random = landscape->flrand(-mDeviation/1.0, mDeviation/1.0) * (1.0 - currentPosition); position += delta + random;*/ if (i == mNumPoints-2) { // -2 because we are calculating for the next point position = goal; } else { if (i == 0) { position += deltaGoal + landscape->flrand(-mDeviation/10.0, mDeviation/10.0); } else { position += deltaGoal + landscape->flrand(-mDeviation*1.5, mDeviation*1.5); } } if (position > 0.9) { // too far over, so move back a bit position = 0.9 - landscape->flrand(0.02, 0.1); } if (position < 0.1) { // too near, so move bakc a bit position = 0.1 + landscape->flrand(0.02, 0.1); } // check our deviation from the straight line to the end if (horizontal) { testPoint[0] = ((ex - bx) * currentPosition) + bx; testPoint[1] = position; } else { testPoint[0] = position; testPoint[1] = ((ey - by) * currentPosition) + by; } // dot product of the normal of the path to the point we are at distance = ((testPoint[0]-bx)*normalizedPath[0]) + ((testPoint[1]-by)*normalizedPath[1]); // find the perpendicular place that is intersected by the point and the path percPoint[0] = (distance * normalizedPath[0]) + bx; percPoint[1] = (distance * normalizedPath[1]) + by; // calculate the difference between the perpendicular point and the test point diffPoint[0] = testPoint[0] - percPoint[0]; diffPoint[1] = testPoint[1] - percPoint[1]; // calculate the distance distance = sqrt((diffPoint[0]*diffPoint[0]) + (diffPoint[1]*diffPoint[1])); if (distance > mDeviation) { // we are beyond our allowed deviation, so head back if (horizontal) { position = (ey-by) * currentPosition + by; } else { position = (ex-bx) * currentPosition + bx; } position += landscape->flrand(-mDeviation/2.0, mDeviation/2.0); } } } mWeights[mNumPoints] = BIG; } CPathInfo::~CPathInfo(void) { free(mWeights); free(mWork); free(mPoints); } void CPathInfo::CreateCircle(void) { int x, y; float r, d; memset(mCircleStamp, 0, sizeof(mCircleStamp)); r = CIRCLE_STAMP_SIZE; for(x=0;x r) { mCircleStamp[y][x] = 255; } else { mCircleStamp[y][x] = pow(sin(d / r * M_PI / 2), mBreadth) * 255; } } } } void CPathInfo::Stamp(int x, int y, int size, int depth, unsigned char *Data, int DataWidth, int DataHeight) { // int xPos; // float yPos; int dx, dy, fx, fy; float offset; byte value; byte invDepth; offset = (float)(CIRCLE_STAMP_SIZE-1) / size; invDepth = 255-depth; for(dx = -size; dx <= size; dx++) { for ( dy = -size; dy <= size; dy ++ ) { float d; d = dx * dx + dy * dy ; if ( d > size * size ) { continue; } fx = x + dx; if (fx < 2 || fx > DataWidth-2) { continue; } fy = y + dy; if (fy < 2 || fy > DataHeight-2) { continue; } value = pow ( sin ( d / (size * size) * M_PI / 2), mBreadth ) * invDepth + depth; if (value < Data[(fy * DataWidth) + fx]) { Data[(fy * DataWidth) + fx] = value; } } } /* fx = x + dx; if (fx < 2 || fx > DataWidth-2) { continue; } xPos = abs((int)(dx*offset)); yPos = offset*size + offset; for(dy = -size; dy < 0; dy++) { yPos -= offset; fy = y + dy; if (fy < 2 || fy > DataHeight-2) { continue; } value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth; if (value < Data[(fy * DataWidth) + fx]) { Data[(fy * DataWidth) + fx] = value; } } yPos = -offset; for(; dy <= size; dy++) { yPos += offset; fy = y + dy; if (fy < 2 || fy > DataHeight-2) { continue; } value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth; if (value < Data[(fy * DataWidth) + fx]) { Data[(fy * DataWidth) + fx] = value; } } } */ } void CPathInfo::GetInfo(float PercentInto, vec4_t Coord, vec4_t Vector) { vec4_t before, after; float testPercent; DialASpline(PercentInto, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, Coord); testPercent = PercentInto - 0.01; if (testPercent < 0) { testPercent = 0; } DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, before); testPercent = PercentInto + 0.01; if (testPercent > 1.0) { testPercent = 1.0; } DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, after); Coord[2] = mDepth; Vector[0] = after[0] - before[0]; Vector[1] = after[1] - before[1]; } void CPathInfo::DrawPath(unsigned char *Data, int DataWidth, int DataHeight ) { float t; vec4_t val, vector;//, perp; int size; float inc; int x, y, lastX, lastY; float depth; inc = mInc / DataWidth; lastX = lastY = -999; for (t=0.0; t<=1.0; t+=inc) { GetInfo(t, val, vector); /* perp[0] = -vector[1]; perp[1] = vector[0]; if (fabs(perp[0]) > fabs(perp[1])) { perp[1] /= fabs(perp[0]); perp[0] /= fabs(perp[0]); } else { perp[0] /= fabs(perp[1]); perp[1] /= fabs(perp[1]); } */ x = val[0] * DataWidth; y = val[1] * DataHeight; if (x == lastX && y == lastY) { continue; } lastX = x; lastY = y; size = val[3] * DataWidth; depth = mDepth * 255.0f; Stamp(x, y, size, (int)depth, Data, DataWidth, DataHeight); } } CRandomTerrain::CRandomTerrain(void) { memset(mPaths, 0, sizeof(mPaths)); } CRandomTerrain::~CRandomTerrain(void) { Shutdown(); } void CRandomTerrain::Init(CCMLandScape *landscape, byte *grid, int width, int height) { Shutdown(); mLandScape = landscape; mWidth = width; mHeight = height; mArea = mWidth * mHeight; mBorder = (width + height) >> 6; mGrid = grid; } void CRandomTerrain::ClearPaths(void) { int i; for(i=0;i= MAX_RANDOM_PATHS || mPaths[PathID]) { return false; } if (ConnectedID >= 0 && ConnectedID < MAX_RANDOM_PATHS) { connected = mPaths[ConnectedID]; } mPaths[PathID] = new CPathInfo(mLandScape, numPoints, bx, by, ex, ey, minWidth, maxWidth, depth, deviation, breadth, connected, CreationFlags ); return true; } bool CRandomTerrain::GetPathInfo(int PathNum, float PercentInto, vec4_t Coord, vec4_t Vector) { if (PathNum < 0 || PathNum >= MAX_RANDOM_PATHS || !mPaths[PathNum]) { return false; } mPaths[PathNum]->GetInfo(PercentInto, Coord, Vector); return true; } void CRandomTerrain::ParseGenerate(const char *GenerateFile) { } void CRandomTerrain::Smooth ( void ) { // Scale down to 1/4 size then back up to smooth out the terrain byte *temp; int x, y, o; temp = mLandScape->GetFlattenMap ( ); // Copy over anything in the flatten map for ( o = 0; o < mHeight * mWidth; o++) { if ( temp[o] > 0 ) { mGrid[o] = (byte)temp[o] & 0x7F; } } temp = (byte *)Z_Malloc(mWidth * mHeight, TAG_RESAMPLE); #if 1 unsigned total, count; for(x=1;x> 1, mHeight >> 1, 1); R_Resample(temp, mWidth >> 1, mHeight >> 1, mGrid, mWidth, mHeight, 1); // now lets filter it. memcpy(temp, mGrid, mWidth * mHeight); for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++) { for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++) { smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] = 1.0f / (1.0f + fabs(float(dx) * float(dx) * float(dx)) + fabs(float(dy) * float(dy) * float(dy))); } } for (y = 0; y < mHeight; y++) { for (x = 0; x < mWidth; x++) { total = 0.0f; num = 0.0f; for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++) { for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++) { xx = x + dx; if (xx >= 0 && xx < mWidth) { yy = y + dy; if (yy >= 0 && yy < mHeight) { total += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] * (float)temp[yy * mWidth + xx]; num += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE]; } } } } total /= num; mGrid[y * mWidth + x] = (byte)Com_Clamp(0, 255, (int)Round(total)); } } #endif Z_Free(temp); /* Uncomment to see the symmetry line on the map for ( x = 0; x < mWidth; x ++ ) { mGrid[x * mWidth + x] = 255; } */ } void CRandomTerrain::Generate(int symmetric) { int i,j; int x, y; // Clear out all existing data memset(mGrid, 255, mArea); // make landscape a little bumpy float t1 = mLandScape->flrand(0, 2); #if 0 float t2 = mLandScape->flrand(0, 2); float t3 = mLandScape->flrand(0, 2); #endif /* CM_NoiseInit(mLandScape); for (y = 0; y < mHeight; y++) for (x = 0; x < mWidth; x++) { i = x + y*mWidth; byte val = (byte)Com_Clamp(0, 255, (int)(220 + (CM_NoiseGet4f( x*0.25, y*0.25, 0, t3 ) * 20)) + (CM_NoiseGet4f( x*0.5, y*0.5, 0, t2 ) * 15)); mGrid[i] = val; } */ for ( i = 0; mPaths[i] != 0; i ++ ) { mPaths[i]->DrawPath(mGrid, mWidth, mHeight); } for (y = 0; y < mHeight; y++) for (x = 0; x < mWidth; x++) { i = x + y*mWidth; byte val = (byte)Com_Clamp(0, 255, (int)(mGrid[i] + (CM_NoiseGet4f( x, y, 0, t1 ) * 5))); mGrid[i] = val; } // if symmetric, do this now if (symmetric) { assert (mWidth == mHeight); // must be square for (y = 0; y < mHeight; y++) for (x = 0; x < (mWidth-y); x++) { i = x + y*mWidth; j = (mWidth-1 - x) + (mHeight-1 - y)*mWidth; byte val = mGrid[i] < mGrid[j] ? mGrid[i] : mGrid[j]; mGrid[i] = mGrid[j] = val; } } } typedef enum { CP_NONE = -1, CP_CONSONANT, CP_COMPLEX_CONSONANT, CP_VOWEL, CP_COMPLEX_VOWEL, CP_ENDING, CP_NUM_PIECES, } ECPType; typedef struct SCharacterPiece { char *mPiece; int mCommonality; } TCharacterPiece; static TCharacterPiece Consonants[] = { { "b", 6 }, { "c", 8 }, { "d", 6 }, { "f", 5 }, { "g", 4 }, { "h", 5 }, { "j", 2 }, { "k", 4 }, { "l", 4 }, { "m", 7 }, { "n", 7 }, { "r", 6 }, { "s", 10 }, { "t", 10 }, { "v", 1 }, { "w", 2 }, { "x", 1 }, { "z", 1 }, { 0, 0 } }; static TCharacterPiece ComplexConsonants[] = { { "st", 10 }, { "ck", 10 }, { "ss", 10 }, { "tt", 7 }, { "ll", 8 }, { "nd", 10 }, { "rn", 6 }, { "nc", 6 }, { "mp", 4 }, { "sc", 10 }, { "sl", 10 }, { "tch", 6 }, { "th", 4 }, { "rn", 5 }, { "cl", 10 }, { "sp", 10 }, { "st", 10 }, { "fl", 4 }, { "sh", 7 }, { "ng", 4 }, // { "" }, { 0, 0 } }; static TCharacterPiece Vowels[] = { { "a", 10 }, { "e", 10 }, { "i", 10 }, { "o", 10 }, { "u", 2 }, // { "" }, { 0, 0 } }; static TCharacterPiece ComplexVowels[] = { { "ea", 10 }, { "ue", 3 }, { "oi", 10 }, { "ai", 8 }, { "oo", 10 }, { "io", 10 }, { "oe", 10 }, { "au", 3 }, { "ee", 7 }, { "ei", 7 }, { "ou", 7 }, { "ia", 4 }, // { "" }, { 0, 0 } }; static TCharacterPiece Endings[] = { { "ing", 10 }, { "ed", 10 }, { "ute", 10 }, { "ance", 10 }, { "ey", 10 }, { "ation", 10 }, { "ous", 10 }, { "ent", 10 }, { "ate", 10 }, { "ible", 10 }, { "age", 10 }, { "ity", 10 }, { "ist", 10 }, { "ism", 10 }, { "ime", 10 }, { "ic", 10 }, { "ant", 10 }, { "etry", 10 }, { "ious", 10 }, { "ative", 10 }, { "er", 10 }, { "ize", 10 }, { "able", 10 }, { "itude", 10 }, // { "" }, { 0, 0 } }; static void FindPiece(ECPType type, char *&pos) { TCharacterPiece *search, *start; int count = 0; switch(type) { case CP_CONSONANT: default: start = Consonants; break; case CP_COMPLEX_CONSONANT: start = ComplexConsonants; break; case CP_VOWEL: start = Vowels; break; case CP_COMPLEX_VOWEL: start = ComplexVowels; break; case CP_ENDING: start = Endings; break; } search = start; while(search->mPiece) { count += search->mCommonality; search++; } count = irand(0, count-1); search = start; while(count > search->mCommonality) { count -= search->mCommonality; search++; } strcpy(pos, search->mPiece); pos += strlen(search->mPiece); } unsigned RMG_CreateSeed(char *TextSeed) { int Length; char Ending[256], *pos; int ComplexVowelChance, ComplexConsonantChance; ECPType LookingFor; unsigned SeedValue = 0, high; Length = irand(4, 9); if (irand(0, 100) < 20) { LookingFor = CP_VOWEL; } else { LookingFor = CP_CONSONANT; } Ending[0] = 0; if (irand(0, 100) < 55) { pos = Ending; FindPiece(CP_ENDING, pos); Length -= (pos - Ending); } pos = TextSeed; *pos = 0; ComplexVowelChance = -1; ComplexConsonantChance = -1; while((pos - TextSeed) < Length || LookingFor == CP_CONSONANT) { if (LookingFor == CP_VOWEL) { if (irand(0, 100) < ComplexVowelChance) { ComplexVowelChance = -1; LookingFor = CP_COMPLEX_VOWEL; } else { ComplexVowelChance += 10; } FindPiece(LookingFor, pos); LookingFor = CP_CONSONANT; } else { if (irand(0, 100) < ComplexConsonantChance) { ComplexConsonantChance = -1; LookingFor = CP_COMPLEX_CONSONANT; } else { ComplexConsonantChance += 45; } FindPiece(LookingFor, pos); LookingFor = CP_VOWEL; } } if (Ending[0]) { strcpy(pos, Ending); } pos = TextSeed; while(*pos) { high = SeedValue >> 28; SeedValue ^= (SeedValue << 4) + ((*pos)-'a'); SeedValue ^= high; pos++; } return SeedValue; }