///////////////////////////////////////////////////////////////////////////////////////// // ClumpPlacer // // Class for generating a roughly circular clump of points // // size: The average number of points in the clump // coherence: How much the radius of the clump varies (1.0 = circle, 0.0 = very random) // smoothness: How smooth the border of the clump is (1.0 = few "peaks", 0.0 = very jagged) // failfraction: Percentage of place attempts allowed to fail (optional) // x, z: Tile coordinates of placer center (optional) // ///////////////////////////////////////////////////////////////////////////////////////// function ClumpPlacer(size, coherence, smoothness, failFraction, x, z) { this.size = size; this.coherence = coherence; this.smoothness = smoothness; this.failFraction = (failFraction !== undefined ? failFraction : 0); this.x = (x !== undefined ? x : -1); this.z = (z !== undefined ? z : -1); } ClumpPlacer.prototype.place = function(constraint) { // Preliminary bounds check if (!g_Map.inMapBounds(this.x, this.z) || !constraint.allows(this.x, this.z)) { return undefined; } var retVec = []; var size = getMapSize(); var gotRet = new Array(size); for (var i = 0; i < size; ++i) { gotRet[i] = new Uint8Array(size); // bool / uint8 } var radius = sqrt(this.size / PI); var perim = 4 * radius * 2 * PI; var intPerim = ceil(perim); var ctrlPts = 1 + Math.floor(1.0/Math.max(this.smoothness,1.0/intPerim)); if (ctrlPts > radius * 2 * PI) { ctrlPts = Math.floor(radius * 2 * PI) + 1; } var noise = new Float32Array(intPerim); //float32 var ctrlCoords = new Float32Array(ctrlPts+1); //float32 var ctrlVals = new Float32Array(ctrlPts+1); //float32 // Generate some interpolated noise for (var i=0; i < ctrlPts; i++) { ctrlCoords[i] = i * perim / ctrlPts; ctrlVals[i] = 2.0*randFloat(); } var c = 0; var looped = 0; for (var i=0; i < intPerim; i++) { if (ctrlCoords[(c+1) % ctrlPts] < i && !looped) { c = (c+1) % ctrlPts; if (c == ctrlPts-1) looped = 1; } // Cubic interpolation of ctrlVals var t = (i - ctrlCoords[c]) / ((looped ? perim : ctrlCoords[(c+1)%ctrlPts]) - ctrlCoords[c]); var v0 = ctrlVals[(c+ctrlPts-1)%ctrlPts]; var v1 = ctrlVals[c]; var v2 = ctrlVals[(c+1)%ctrlPts]; var v3 = ctrlVals[(c+2)%ctrlPts]; var P = (v3 - v2) - (v0 - v1); var Q = (v0 - v1) - P; var R = v2 - v0; var S = v1; noise[i] = P*t*t*t + Q*t*t + R*t + S; } var failed = 0; for (var p=0; p < intPerim; p++) { var th = 2 * PI * p / perim; var r = radius * (1 + (1-this.coherence)*noise[p]); var s = sin(th); var c = cos(th); var xx=this.x; var yy=this.z; for (var k=0; k < ceil(r); k++) { var i = Math.floor(xx); var j = Math.floor(yy); if (g_Map.inMapBounds(i, j) && constraint.allows(i, j)) { if (!gotRet[i][j]) { // Only include each point once gotRet[i][j] = 1; retVec.push(new PointXZ(i, j)); } } else { failed++; } xx += s; yy += c; } } return ((failed > this.size*this.failFraction) ? undefined : retVec); }; ///////////////////////////////////////////////////////////////////////////////////////// // Chain Placer // // Class for generating a more random clump of points it randomly creates circles around the edges of the current clump // // minRadius: minimum radius of the circles // maxRadius: maximum radius of the circles // numCircles: the number of the circles // failfraction: Percentage of place attempts allowed to fail (optional) // x, z: Tile coordinates of placer center (optional) // fcc: Farthest circle center (optional) // q: a list containing numbers. each time if the list still contains values, pops one from the end and uses it as the radius (optional) // ///////////////////////////////////////////////////////////////////////////////////////// function ChainPlacer(minRadius, maxRadius, numCircles, failFraction, x, z, fcc, q) { this.minRadius = minRadius; this.maxRadius = maxRadius; this.numCircles = numCircles this.failFraction = (failFraction !== undefined ? failFraction : 0); this.x = (x !== undefined ? x : -1); this.z = (z !== undefined ? z : -1); this.fcc = (fcc !== undefined ? fcc : 0); this.q = (q !== undefined ? q : []); } ChainPlacer.prototype.place = function(constraint) { // Preliminary bounds check if (!g_Map.inMapBounds(this.x, this.z) || !constraint.allows(this.x, this.z)) { return undefined; } var retVec = []; var size = getMapSize(); var failed = 0, count = 0; var queueEmpty = (this.q.length ? false : true); var gotRet = new Array(size); for (var i = 0; i < size; ++i) { gotRet[i] = new Array(size); for (var j = 0; j < size; ++j) { gotRet[i][j] = -1; } } --size; if (this.minRadius < 1) this.minRadius = 1; if (this.minRadius > this.maxRadius) this.minRadius = this.maxRadius; var edges = [[this.x, this.z]] for (var i = 0; i < this.numCircles; ++i) { var point = edges[randInt(edges.length)]; var cx = point[0], cz = point[1]; if (queueEmpty) { var radius = randInt(this.minRadius, this.maxRadius); } else { var radius = this.q.pop(); queueEmpty = (this.q.length ? false : true); } //log (edges); var sx = cx - radius, lx = cx + radius; var sz = cz - radius, lz = cz + radius; sx = (sx < 0 ? 0 : sx); sz = (sz < 0 ? 0 : sz); lx = (lx > size ? size : lx); lz = (lz > size ? size : lz); var radius2 = radius * radius; var dx, dz; //log (uneval([sx, sz, lx, lz])); for (var ix = sx; ix <= lx; ++ix) { for (var iz = sz; iz <= lz; ++ iz) { dx = ix - cx; dz = iz - cz; if (dx * dx + dz * dz <= radius2) { if (g_Map.inMapBounds(ix, iz) && constraint.allows(ix, iz)) { var state = gotRet[ix][iz]; if (state == -1) { retVec.push(new PointXZ(ix, iz)); gotRet[ix][iz] = -2; } else if (state >= 0) { //log (uneval(edges)); //log (state) var s = edges.splice(state, 1); //log (uneval(s)); //log (uneval(edges)); gotRet[ix][iz] = -2; var edgesLength = edges.length; for (var k = state; k < edges.length; ++k) { --gotRet[edges[k][0]][edges[k][1]]; } } } else { ++failed; } ++count; } } } for (var ix = sx; ix <= lx; ++ix) { for (var iz = sz; iz <= lz; ++ iz) { if (this.fcc) if ((this.x - ix) > this.fcc || (ix - this.x) > this.fcc || (this.z - iz) > this.fcc || (iz - this.z) > this.fcc) continue; if (gotRet[ix][iz] == -2) { if (ix > 0) { if (gotRet[ix-1][iz] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } if (iz > 0) { if (gotRet[ix][iz-1] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } if (ix < size) { if (gotRet[ix+1][iz] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } if (iz < size) { if (gotRet[ix][iz+1] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } } } } } return ((failed > count*this.failFraction) ? undefined : retVec); }; ///////////////////////////////////////////////////////////////////////////////////////// // RectPlacer // // Class for generating a rectangular block of points // // x1,z1: Top left corner of block // x2,z2: Bottom right corner of block // ///////////////////////////////////////////////////////////////////////////////////////// function RectPlacer(x1, z1, x2, z2) { this.x1 = x1; this.z1 = z1; this.x2 = x2; this.z2 = z2; if (x1 > x2 || z1 > z2) { throw("RectPlacer: incorrect bounds on rect"); } } RectPlacer.prototype.place = function(constraint) { // Preliminary bounds check if (!g_Map.inMapBounds(this.x1, this.z1) || !constraint.allows(this.x1, this.z1) || !g_Map.inMapBounds(this.x2, this.z2) || !constraint.allows(this.x2, this.z2)) { return undefined; } var ret = []; var x2 = this.x2; var z2 = this.z2; for (var x=this.x1; x < x2; x++) { for (var z=this.z1; z < z2; z++) { if (g_Map.inMapBounds(x, z) && constraint.allows(x, z)) { ret.push(new PointXZ(x, z)); } else { return undefined; } } } return ret; }; ///////////////////////////////////////////////////////////////////////////////////////// // ObjectGroupPlacer ///////////////////////////////////////////////////////////////////////////////////////// function ObjectGroupPlacer() {} ///////////////////////////////////////////////////////////////////////////////////////// // SimpleObject // // Class specifying a type of entity that can be placed on the map // // type: The entity's template name // minCount,maxCount: The number of objects to place // minDistance,maxDistance: The distance between placed objects // minAngle,maxAngle: The variation in angle of placed objects (optional) // ///////////////////////////////////////////////////////////////////////////////////////// function SimpleObject(type, minCount, maxCount, minDistance, maxDistance, minAngle, maxAngle) { this.type = type; this.minCount = minCount; this.maxCount = maxCount; this.minDistance = minDistance; this.maxDistance = maxDistance; this.minAngle = (minAngle !== undefined ? minAngle : 0); this.maxAngle = (maxAngle !== undefined ? maxAngle : 2*PI); if (minCount > maxCount) warn("SimpleObject: minCount should be less than or equal to maxCount"); if (minDistance > maxDistance) warn("SimpleObject: minDistance should be less than or equal to maxDistance"); if (minAngle > maxAngle) warn("SimpleObject: minAngle should be less than or equal to maxAngle"); } SimpleObject.prototype.place = function(cx, cz, player, avoidSelf, constraint) { var failCount = 0; var count = randInt(this.minCount, this.maxCount); var resultObjs = []; for (var i=0; i < count; i++) { while(true) { var distance = randFloat(this.minDistance, this.maxDistance); var direction = randFloat(0, 2*PI); var x = cx + 0.5 + distance*cos(direction); var z = cz + 0.5 + distance*sin(direction); var fail = false; // reset place failure flag if (!g_Map.validT(x, z)) { fail = true; } else { if (avoidSelf) { var length = resultObjs.length; for (var i = 0; (i < length) && !fail; i++) { var dx = x - resultObjs[i].position.x; var dy = z - resultObjs[i].position.z; if ((dx*dx + dy*dy) < 1) { fail = true; } } } if (!fail) { if (!constraint.allows(Math.floor(x), Math.floor(z))) { fail = true; } else { // if we got here, we're good var angle = randFloat(this.minAngle, this.maxAngle); resultObjs.push(new Entity(this.type, player, x, z, angle)); break; } } } if (fail) { failCount++; if (failCount > 20) // TODO: Make this adjustable { return undefined; } } } } return resultObjs; }; ///////////////////////////////////////////////////////////////////////////////////////// // RandomObject // // Class specifying entities that can be placed on the map, selected randomly // // types: Array of entity template names // minCount,maxCount: The number of objects to place // minDistance,maxDistance: The distance between placed objects // minAngle,maxAngle: The variation in angle of placed objects (optional) // ///////////////////////////////////////////////////////////////////////////////////////// function RandomObject(types, minCount, maxCount, minDistance, maxDistance, minAngle, maxAngle) { this.types = types; this.minCount = minCount; this.maxCount = maxCount; this.minDistance = minDistance; this.maxDistance = maxDistance; this.minAngle = (minAngle !== undefined ? minAngle : 0); this.maxAngle = (maxAngle !== undefined ? maxAngle : 2*PI); if (minCount > maxCount) warn("RandomObject: minCount should be less than or equal to maxCount"); if (minDistance > maxDistance) warn("RandomObject: minDistance should be less than or equal to maxDistance"); if (minAngle > maxAngle) warn("RandomObject: minAngle should be less than or equal to maxAngle"); } RandomObject.prototype.place = function(cx, cz, player, avoidSelf, constraint) { var failCount = 0; var count = randInt(this.minCount, this.maxCount); var resultObjs = []; for (var i=0; i < count; i++) { while(true) { var distance = randFloat(this.minDistance, this.maxDistance); var direction = randFloat(0, 2*PI); var x = cx + 0.5 + distance*cos(direction); var z = cz + 0.5 + distance*sin(direction); var fail = false; // reset place failure flag if (!g_Map.validT(x, z)) { fail = true; } else { if (avoidSelf) { var length = resultObjs.length; for (var i = 0; (i < length) && !fail; i++) { var dx = x - resultObjs[i].position.x; var dy = z - resultObjs[i].position.z; if ((dx*dx + dy*dy) < 1) { fail = true; } } } if (!fail) { if (!constraint.allows(Math.floor(x), Math.floor(z))) { fail = true; } else { // if we got here, we're good var angle = randFloat(this.minAngle, this.maxAngle); //Randomly select entity var type = this.types[randInt(this.types.length)]; resultObjs.push(new Entity(type, player, x, z, angle)); break; } } } if (fail) { failCount++; if (failCount > 20) // TODO: Make this adjustable { return undefined; } } } } return resultObjs; }; ///////////////////////////////////////////////////////////////////////////////////////// // SimpleGroup // // Class for placing groups of different objects // // elements: Array of SimpleObjects // avoidSelf: Objects will not overlap // tileClass: Optional tile class to add with these objects // x,z: Tile coordinates of center of placer // ///////////////////////////////////////////////////////////////////////////////////////// function SimpleGroup(elements, avoidSelf, tileClass, x, z) { this.elements = elements; this.tileClass = (tileClass !== undefined ? getTileClass(tileClass) : undefined); this.avoidSelf = (avoidSelf !== undefined ? avoidSelf : false); this.x = (x !== undefined ? x : -1); this.z = (z !== undefined ? z : -1); } SimpleGroup.prototype.place = function(player, constraint) { var resultObjs = []; // Try placement of objects var length = this.elements.length; for (var i = 0; i < length; i++) { var objs = this.elements[i].place(this.x, this.z, player, this.avoidSelf, constraint); if (objs === undefined) { // Failure return false; } else { for (var j = 0; j < objs.length; ++j) { resultObjs.push(objs[j]); } } } // Add placed objects to map length = resultObjs.length; for (var i=0; i < length; i++) { g_Map.addObject(resultObjs[i]); if (this.tileClass !== undefined) { // Convert position to integer number of tiles this.tileClass.add(Math.floor(resultObjs[i].position.x/CELL_SIZE), Math.floor(resultObjs[i].position.z/CELL_SIZE)); } } return true; }; ///////////////////////////////////////////////////////////////////////////////////////// // RandomGroup // // Class for placing group of a random simple object // // elements: Array of SimpleObjects // avoidSelf: Objects will not overlap // tileClass: Optional tile class to add with these objects // x,z: Tile coordinates of center of placer // ///////////////////////////////////////////////////////////////////////////////////////// function RandomGroup(elements, avoidSelf, tileClass, x, z) { this.elements = elements; this.tileClass = (tileClass !== undefined ? getTileClass(tileClass) : undefined); this.avoidSelf = (avoidSelf !== undefined ? avoidSelf : false); this.x = (x !== undefined ? x : -1); this.z = (z !== undefined ? z : -1); } RandomGroup.prototype.place = function(player, constraint) { var resultObjs = []; // Pick one of the object placers at random var placer = this.elements[randInt(this.elements.length)]; var objs = placer.place(this.x, this.z, player, this.avoidSelf, constraint); if (objs === undefined) { // Failure return false; } else { for (var j = 0; j < objs.length; ++j) { resultObjs.push(objs[j]); } } // Add placed objects to map var length = resultObjs.length; for (var i=0; i < length; i++) { g_Map.addObject(resultObjs[i]); if (this.tileClass !== undefined) { // Convert position to integer number of tiles this.tileClass.add(Math.floor(resultObjs[i].position.x/CELL_SIZE), Math.floor(resultObjs[i].position.z/CELL_SIZE)); } } return true; };