Ticket #658: mapgen.js

File mapgen.js, 53.7 KB (added by historic_bruno, 13 years ago)
Line 
1var startTime = dateNow();
2
3function log(msg)
4{
5 println(msg);
6}
7
8function error(msg)
9{
10 println("Error: "+msg);
11}
12
13function warning(msg)
14{
15 println("Warning: "+msg);
16}
17
18function Area(points)
19{
20 this.points = (points !== undefined ? points : []);
21}
22
23///////////////////////////////////////////////////////////////////////////
24// NullConstraints: No constraint - always return true
25///////////////////////////////////////////////////////////////////////////
26function NullConstraint() {}
27
28NullConstraint.prototype.allows = function(x, y)
29{
30 return true;
31};
32
33///////////////////////////////////////////////////////////////////////////
34// AndConstraints: Check multiple constraints
35///////////////////////////////////////////////////////////////////////////
36function AndConstraint(constraints)
37{
38 this.constraints = constraints;
39}
40
41AndConstraint.prototype.allows = function(x, y)
42{
43 for (var i=0; i < this.constraints.length; ++i)
44 {
45 if (!this.constraints[i].allows(x, y))
46 return false;
47 }
48
49 return true;
50};
51
52///////////////////////////////////////////////////////////////////////////
53// AvoidAreaConstraint
54///////////////////////////////////////////////////////////////////////////
55function AvoidAreaConstraint(area)
56{
57 this.area = area;
58}
59
60AvoidAreaConstraint.prototype.allows = function(x, y)
61{
62 return g_Map.area[x][y] != this.area;
63};
64
65///////////////////////////////////////////////////////////////////////////
66// AvoidTextureConstraint
67///////////////////////////////////////////////////////////////////////////
68function AvoidTextureConstraint(textureID)
69{
70 this.textureID = textureID;
71}
72
73AvoidTextureConstraint.prototype.allows = function(x, y)
74{
75 return g_Map.texture[x][y] != this.textureID;
76};
77
78///////////////////////////////////////////////////////////////////////////
79// AvoidTileClassConstraint
80///////////////////////////////////////////////////////////////////////////
81function AvoidTileClassConstraint(tileClassID, distance)
82{
83 this.tileClass = getTileClass(tileClassID);
84 this.distance = distance;
85}
86
87AvoidTileClassConstraint.prototype.allows = function(x, y)
88{
89 return this.tileClass.countMembersInRadius(x, y, this.distance) == 0;
90};
91
92///////////////////////////////////////////////////////////////////////////
93// StayInTileClassConstraint
94///////////////////////////////////////////////////////////////////////////
95function StayInTileClassConstraint(tileClassID, distance)
96{
97 this.tileClass = getTileClass(tileClassID);
98 this.distance = distance;
99}
100
101StayInTileClassConstraint.prototype.allows = function(x, y)
102{
103 return this.tileClass.countNonMembersInRadius(x, y, this.distance) == 0;
104};
105
106///////////////////////////////////////////////////////////////////////////
107// BorderTileClassConstraint
108///////////////////////////////////////////////////////////////////////////
109function BorderTileClassConstraint(tileClassID, distanceInside, distanceOutside)
110{
111 this.tileClass = getTileClass(tileClassID);
112 this.distanceInside = distanceInside;
113 this.distanceOutside = distanceOutside;
114}
115
116BorderTileClassConstraint.prototype.allows = function(x, y)
117{
118 return (this.tileClass.countMembersInRadius(x, y, this.distanceOutside) > 0
119 && this.tileClass.countNonMembersInRadius(x, y, this.distanceInside) > 0);
120};
121function Entity(name, player, x, y, angle)
122{
123 // Get unique ID
124 this.id = g_Map.getEntityID();
125 this.name = name;
126
127 // Convert from tile coords to map coords
128 this.x = x;
129 this.y = y;
130
131 if (player !== undefined)
132 {
133 this.player = player;
134 this.isActor = false;
135 }
136 else
137 { // Actors have no player ID
138 this.isActor = true;
139 }
140
141 this.orientation = (angle !== undefined ? angle : 0);
142}
143
144/////////////////////////////////////////////////////////////////////////////////////////////
145// Constant definitions
146/////////////////////////////////////////////////////////////////////////////////////////////
147
148const PI = Math.PI;
149
150const SEA_LEVEL = 20.0;
151
152const TERRAIN_SEPARATOR = "|";
153
154const TILES_PER_PATCH = 16;
155
156/////////////////////////////////////////////////////////////////////////////////////////////
157// Utility functions
158/////////////////////////////////////////////////////////////////////////////////////////////
159
160function fractionToTiles(f) {
161 return getMapSize() * f;
162}
163
164function tilesToFraction(t) {
165 return t / getMapSize();
166}
167
168function fractionToSize(f) {
169 return getMapSizeSqr() * f;
170}
171
172function sizeToFraction(s) {
173 return s / getMapSizeSqr();
174}
175
176function cos(x) {
177 return Math.cos(x);
178}
179
180function sin(x) {
181 return Math.sin(x);
182}
183
184function tan(x) {
185 return Math.tan(x);
186}
187
188function abs(x) {
189 return Math.abs(x);
190}
191
192function round(x) {
193 return Math.round(x);
194}
195
196function lerp(a, b, t) {
197 return a + (b-a) * t;
198}
199
200function sqrt(x) {
201 return Math.sqrt(x);
202}
203
204function ceil(x) {
205 return Math.ceil(x);
206}
207
208function floor(x) {
209 return Math.floor(x);
210}
211
212function max(x, y) {
213 return x > y ? x : y;
214}
215
216function min(x, y) {
217 return x < y ? x : y;
218}
219
220function println(x) {
221 print(x);
222 print("\n");
223}
224
225function argsToArray(x)
226{
227 var numArgs = x.length;
228 if (numArgs != 1)
229 {
230 var ret = new Array(numArgs);
231 for (var i=0; i < numArgs; i++)
232 {
233 ret[i] = x[i];
234 }
235 return ret;
236 }
237 else
238 {
239 return x[0];
240 }
241}
242
243function chooseRand()
244{
245 if (arguments.length==0)
246 {
247 error("chooseRand: requires at least 1 argument");
248 }
249 var ar = argsToArray(arguments);
250 return ar[randInt(ar.length)];
251}
252
253function createAreas(centeredPlacer, painter, constraint, num, retryFactor)
254{
255 if (retryFactor === undefined)
256 retryFactor = 10;
257
258 var maxFail = num * retryFactor;
259 var good = 0;
260 var bad = 0;
261 var ret = [];
262 while(good < num && bad <= maxFail)
263 {
264 centeredPlacer.x = randInt(getMapSize());
265 centeredPlacer.y = randInt(getMapSize());
266 var r = g_Map.createArea(centeredPlacer, painter, constraint);
267 if (r !== undefined)
268 {
269 good++;
270 ret.push(r);
271 }
272 else
273 {
274 bad++;
275 }
276 }
277
278 return ret;
279}
280
281function createObjectGroups(placer, player, constraint, num, retryFactor)
282{
283 if (retryFactor === undefined)
284 retryFactor = 10;
285
286 var maxFail = num * retryFactor;
287 var good = 0;
288 var bad = 0;
289 while(good < num && bad <= maxFail)
290 {
291 placer.x = randInt(getMapSize());
292 placer.y = randInt(getMapSize());
293 var r = createObjectGroup(placer, player, constraint);
294
295 if (r !== undefined)
296 {
297 good++;
298 }
299 else
300 {
301 bad++;
302 }
303 }
304 return good;
305}
306
307function createTerrain(terrain)
308{
309 if (terrain instanceof Array)
310 {
311 var terrainList = [];
312
313 for (var i = 0; i < terrain.length; ++i)
314 terrainList.push(createTerrain(terrain[i]));
315
316 return new RandomTerrain(terrainList);
317 }
318 else
319 {
320 return createSimpleTerrain(terrain);
321 }
322}
323
324function createSimpleTerrain(terrain)
325{
326 if (typeof(terrain) == "string")
327 { // Split string by pipe | character, this allows specifying terrain + tree type in single string
328 var params = terrain.split(TERRAIN_SEPARATOR, 2);
329
330 if (params.length != 2)
331 {
332 return new SimpleTerrain(terrain);
333 }
334 else
335 {
336 return new SimpleTerrain(params[0], params[1]);
337 }
338 }
339 else
340 {
341 error("createSimpleTerrain expects string as input, received "+terrain);
342 return undefined;
343 }
344}
345
346function placeObject(type, player, x, y, angle)
347{
348 g_Map.addObjects(new Entity(type, player, x, y, angle));
349}
350
351function placeTerrain(x, y, terrain)
352{
353 // convert terrain param into terrain object
354 g_Map.placeTerrain(x, y, createTerrain(terrain));
355
356}
357
358/////////////////////////////////////////////////////////////////////////////////////////////
359// Access global map variable
360/////////////////////////////////////////////////////////////////////////////////////////////
361
362function createTileClass()
363{
364 return g_Map.createTileClass();
365}
366
367function getTileClass(id)
368{
369 // Check for valid class id
370 if (id < 1 || id > g_Map.tileClasses.length)
371 {
372 //error("Invalid tile class id: "+id);
373 return null;
374 }
375
376 return g_Map.tileClasses[id - 1];
377}
378
379function createArea(placer, painter, constraint)
380{
381 return g_Map.createArea(placer, painter, constraint);
382}
383
384function createObjectGroup(placer, player, constraint)
385{
386 return g_Map.createObjectGroup(placer, player, constraint);
387}
388
389function getMapSize()
390{
391 return g_Map.size;
392}
393
394function getMapSizeSqr()
395{
396 return g_Map.size*g_Map.size;
397}
398
399function getNumPlayers()
400{
401 return g_MapSettings.PlayerData.length;
402}
403
404function getCivCode(player)
405{
406 return g_MapSettings.PlayerData[player].Civ;
407}
408
409function getHeight(x, y)
410{
411 g_Map.getHeight(x, y);
412}
413
414function setHeight(x, y, height)
415{
416 g_Map.setHeight(x, y, height);
417}
418
419/////////////////////////////////////////////////////////////////////////////////////////////
420// Utility functions for classes
421/////////////////////////////////////////////////////////////////////////////////////////////
422
423
424// Add point to given class by id
425function addToClass(x, y, id)
426{
427 var tileClass = getTileClass(id);
428
429 if (tileClass !== null)
430 tileClass.add(x, y);
431}
432
433// Create a painter for the given class
434function paintClass(id)
435{
436 return new TileClassPainter(getTileClass(id));
437}
438
439// Create an avoid constraint for the given classes by the given distances
440function avoidClasses(/*class1, dist1, class2, dist2, etc*/)
441{
442 var ar = new Array(arguments.length/2);
443 for (var i=0; i < arguments.length/2; i++)
444 {
445 ar[i] = new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1]);
446 }
447 // Return single constraint
448 return new AndConstraint(ar);
449}
450//////////////////////////////////////////////////////////////////////
451// Map
452//////////////////////////////////////////////////////////////////////
453
454function Map(size, baseHeight)
455{
456 // Size must be 0 to 1024, divisible by 16
457 this.size = size;
458
459 // Create 2D arrays for texture, object, and area maps
460 this.texture = new Array(size);
461 this.terrainObjects = new Array(size);
462 this.area = new Array(size);
463
464 for (var i=0; i < size; i++)
465 {
466 this.texture[i] = new Uint16Array(size); // uint16
467 this.terrainObjects[i] = new Array(size); // entity
468 this.area[i] = new Array(size); // area
469
470 for (var j = 0; j < size; j++)
471 {
472 this.terrainObjects[i][j] = [];
473 }
474 }
475
476 var mapSize = size+1;
477
478 // Create 2D array for heightmap
479 this.height = new Array(mapSize);
480 for (var i=0; i < mapSize; i++)
481 {
482 this.height[i] = new Float32Array(mapSize); //float32
483 for (var j=0; j < mapSize; j++)
484 { // Initialize height map to baseHeight
485 this.height[i][j] = baseHeight;
486 }
487 }
488
489 // Create name <-> id maps for textures
490 this.nameToID = {};
491 this.IDToName = []; //string
492
493 // Other arrays
494 this.objects = []; //object
495 this.areas = []; //area
496 this.tileClasses = []; //int
497
498 // Starting entity ID
499 this.entityCount = 150;
500}
501
502Map.prototype.initTerrain = function(baseTerrain)
503{
504 // Initialize base terrain
505 var size = this.size;
506 for (var i=0; i < size; i++)
507 {
508 for (var j=0; j < size; j++)
509 {
510 baseTerrain.place(i, j);
511 }
512 }
513};
514
515// Return ID of texture (by name)
516Map.prototype.getID = function(texture)
517{
518 if (texture in (this.nameToID))
519 {
520 return this.nameToID[texture];
521 }
522
523 // Add new texture
524 var id = this.IDToName.length;
525 this.nameToID[texture] = id;
526 this.IDToName[id] = texture;
527
528 return id;
529};
530
531// Return next free entity ID
532Map.prototype.getEntityID = function()
533{
534 return this.entityCount++;
535}
536
537// Check bounds
538Map.prototype.validT = function(x, y)
539{
540 return x >= 0 && y >= 0 && x < this.size && y < this.size;
541};
542
543// Check bounds on height map (size + 1 by size + 1)
544Map.prototype.validH = function(x, y)
545{
546 return x >= 0 && y >= 0 && x <= this.size && y <= this.size;
547};
548
549// Check bounds on tile class
550Map.prototype.validClass = function(c)
551{
552 return c >= 0 && c < this.tileClasses.length;
553};
554
555Map.prototype.getTexture = function(x, y)
556{
557 if (!this.validT(x, y))
558 error("getTexture: invalid tile position ("+x+", "+y+")");
559
560 return this.IDToName[this.texture[x][y]];
561};
562
563Map.prototype.setTexture = function(x, y, texture)
564{
565 if (!this.validT(x, y))
566 error("setTexture: invalid tile position ("+x+", "+y+")");
567
568 this.texture[x][y] = this.getID(texture);
569};
570
571Map.prototype.getHeight = function(x, y)
572{
573 if (!this.validH(x, y))
574 error("getHeight: invalid vertex position ("+x+", "+y+")");
575
576 return this.height[x][y];
577};
578
579Map.prototype.setHeight = function(x, y, height)
580{
581 if (!this.validH(x, y))
582 error("setHeight: invalid vertex position ("+x+", "+y+")");
583
584 this.height[x][y] = height;
585};
586
587Map.prototype.getTerrainObjects = function(x, y)
588{
589 if (!this.validT(x, y))
590 error("getTerrainObjects: invalid tile position ("+x+", "+y+")");
591
592 return this.terrainObjects[x][y];
593};
594
595Map.prototype.setTerrainObjects = function(x, y, objects)
596{
597 if (!this.validT(x, y))
598 error("setTerrainObjects: invalid tile position ("+x+", "+y+")");
599
600 this.terrainObjects[x][y] = objects;
601};
602
603Map.prototype.placeTerrain = function(x, y, terrain)
604{
605 terrain.place(x, y);
606};
607
608Map.prototype.addObjects = function(obj)
609{
610 this.objects = this.objects.concat(obj);
611};
612
613Map.prototype.createArea = function(placer, painter, constraint)
614{
615 // Check for multiple painters
616 if (painter instanceof Array)
617 {
618 var painterArray = painter;
619 painter = new MultiPainter(painterArray);
620 }
621
622 // Check for null constraint
623 if (constraint === undefined || constraint === null)
624 {
625 constraint = new NullConstraint();
626 }
627 else if (constraint instanceof Array)
628 { // Check for multiple constraints
629 var constraintArray = constraint;
630 constraint = new AndConstraint(constraintArray);
631 }
632
633 var points = placer.place(constraint);
634 if (!points)
635 return undefined;
636
637 var a = new Area(points);
638 for (var i=0; i < points.length; i++)
639 {
640 this.area[points[i].x][points[i].y] = a;
641 }
642
643 painter.paint(a);
644 this.areas.push(a);
645
646 return a;
647};
648
649Map.prototype.createObjectGroup = function(placer, player, constraint)
650{
651 // Check for null constraint
652 if (constraint === undefined || constraint === null)
653 {
654 constraint = new NullConstraint();
655 }
656 else if (constraint instanceof Array)
657 { // Check for multiple constraints
658 var constraintArray = constraint;
659 constraint = new AndConstraint(constraintArray);
660 }
661
662 return placer.place(player, constraint);
663};
664
665Map.prototype.createTileClass = function()
666{
667 this.tileClasses.push(new TileClass(this.size));
668
669 return this.tileClasses.length;
670};
671
672// Get height taking into account terrain curvature
673Map.prototype.getExactHeight = function(x, y)
674{
675 var xi = min(Math.floor(x), this.size);
676 var yi = min(Math.floor(y), this.size);
677 var xf = x - xi;
678 var yf = y - yi;
679
680 var h00 = this.height[xi][yi];
681 var h01 = this.height[xi][yi+1];
682 var h10 = this.height[xi+1][yi];
683 var h11 = this.height[xi+1][yi+1];
684
685 return ( 1 - yf ) * ( ( 1 - xf ) * h00 + xf * h10 ) + yf * ( ( 1 - xf ) * h01 + xf * h11 ) ;
686};
687
688Map.prototype.getMapData = function()
689{
690 var data = {};
691
692 // Build entity array
693 var entities = [];
694
695 // Terrain objects first (trees)
696 var size = this.size;
697 for (var x=0; x < size; ++x)
698 {
699 for (var y=0; y < size; ++y)
700 {
701 if (this.terrainObjects[x][y].length)
702 entities = entities.concat(this.terrainObjects[x][y]);
703 }
704 }
705
706 // Now other entities
707 for (var n in this.objects)
708 {
709 entities = entities.concat(this.objects[n]);
710 }
711
712 // Convert from tiles to map coordinates
713 for (var n in entities)
714 {
715 var e = entities[n];
716 e.x *= 4;
717 e.y *= 4;
718
719 entities[n] = e;
720 }
721 data["entities"] = entities;
722
723 // Terrain
724 data["size"] = this.size;
725
726 // Convert 2D heightmap array to flat array
727 // Flat because it's easier to handle by the engine
728 var mapSize = size+1;
729 var height16 = new Array(mapSize*mapSize); // uint16
730 for (var x=0; x < mapSize; x++)
731 {
732 for (var y=0; y < mapSize; y++)
733 {
734 var intHeight = Math.floor((this.height[x][y] + SEA_LEVEL) * 256.0 / 0.35); // floor
735
736 if (intHeight > 65000)
737 intHeight = 65000;
738 else if (intHeight < 0)
739 intHeight = 0;
740
741 height16[y*mapSize + x] = intHeight;
742 }
743 }
744 data["height"] = height16;
745 data["seaLevel"] = SEA_LEVEL;
746
747 // Get array of textures used in this map
748 var textureNames = [];
749 for (var name in this.nameToID)
750 textureNames.push(name);
751
752 data["textureNames"] = textureNames;
753 data["numTextures"] = textureNames.length;
754
755 // Convert 2D tile data to flat array, reodering into patches as expected by MapReader
756 var tiles = new Array(size*size);
757 var patches = size/16;
758 for (var x=0; x < size; x++)
759 {
760 var patchX = Math.floor(x/16);
761 var offX = x%16;
762 for (var y=0; y < size; y++)
763 {
764 var patchY = Math.floor(y/16);
765 var offY = y%16;
766 tiles[(patchY*patches + patchX)*256 + (offY*16 + offX)] = this.texture[x][y];
767 }
768 }
769 data["tileData"] = tiles;
770
771 return data;
772};
773// TODO: Since there's only one map anyway, just use this global variable
774var g_Map;
775
776// Map settings - once initialized, has all the game setup data, including player array
777var g_MapSettings = {
778 Size : 13,
779 BaseTerrain: "grass1_spring",
780 BaseHeight: 0,
781 PlayerData : [ {}, {} ]
782};
783
784var g_Environment = {
785 SkySet: "cirrus",
786 SunColour: {r: 1.47461, g: 1.47461, b: 1.47461},
787 SunElevation: 0.951868,
788 SunRotation: -0.532844,
789 TerrainAmbientColour: {r: 0.337255, g: 0.403922, b: 0.466667},
790 UnitsAmbientColour: {r: 0.501961, g: 0.501961, b: 0.501961},
791 Water: {
792 WaterBody: {
793 Type: "default",
794 Colour: {r: 0.294118, g: 0.34902, b: 0.694118},
795 Height: 17.6262,
796 Shininess: 150,
797 Waviness: 8,
798 Murkiness: 0.458008,
799 Tint: {r: 0.447059, g: 0.411765, b: 0.321569},
800 ReflectionTint: {r: 0.619608, g: 0.584314, b: 0.47451},
801 ReflectionTintStrength: 0.298828
802 }
803 }
804};
805
806var g_Camera = {
807 Position: {x: 100, y: 150, z: -100},
808 Rotation: 0,
809 Declination: 0.523599
810};
811
812function InitMapGen(settings)
813{
814 if (settings === undefined || settings == {})
815 {
816 warn("InitMapGen: settings missing");
817 }
818 else
819 {
820 g_MapSettings = settings;
821 }
822
823 // Create new map
824 log("Creating new map...");
825 var terrain = createTerrain(g_MapSettings.BaseTerrain);
826
827 g_Map = new Map(g_MapSettings.Size * TILES_PER_PATCH, g_MapSettings.BaseHeight);
828 g_Map.initTerrain(terrain);
829}
830
831function SaveMap()
832{
833 log("Saving map...");
834
835 // Get necessary data from map
836 var data = g_Map.getMapData();
837
838 // Add environment and camera settings
839 g_Environment.Water.WaterBody.Height = SEA_LEVEL - 0.1;
840 data.Environment = g_Environment;
841 data.Camera = g_Camera;
842
843 return data;
844}
845// Utility function used in both noises as an ease curve
846function easeCurve(t)
847{
848 return t*t*t*(t*(t*6-15)+10);
849}
850
851// Find mod of number but only positive values
852function modPos(num, m)
853{
854 var p = num % m;
855 if (p < 0)
856 p += m;
857
858 return p;
859}
860
861/////////////////////////////////////////////////////////////////////
862// Noise2D
863/////////////////////////////////////////////////////////////////////
864
865function Noise2D(freq)
866{
867 freq = Math.floor(freq);
868 this.freq = freq;
869 this.grads = new Array(freq);
870
871 for (var i=0; i < freq; ++i)
872 {
873 this.grads[i] = new Array(freq);
874 for (var j=0; j < freq; ++j)
875 {
876 var a = randFloat() * 2 * PI;
877
878 this.grads[i][j] = new Vector2D(Math.cos(a), Math.sin(a));
879 }
880 }
881}
882
883Noise2D.prototype.get = function(x, y)
884{
885 x *= this.freq;
886 y *= this.freq;
887
888 var ix = modPos(Math.floor(x), this.freq);
889 var iy = modPos(Math.floor(y), this.freq);
890
891 var fx = x - ix;
892 var fy = y - iy;
893
894 var ix1 = (ix+1) % this.freq;
895 var iy1 = (iy+1) % this.freq;
896
897 var s = this.grads[ix][iy].dot(new Vector2D(fx, fy));
898 var t = this.grads[ix1][iy].dot(new Vector2D(fx-1, fy));
899 var u = this.grads[ix][iy1].dot(new Vector2D(fx, fy-1));
900 var v = this.grads[ix1][iy1].dot(new Vector2D(fx-1, fy-1));
901
902 var ex = easeCurve(fx);
903 var ey = easeCurve(fy);
904 var a = s + ex*(t-s);
905 var b = u + ex*(v-u);
906 return (a + ey*(b-a)) * 0.5 + 0.5;
907};
908
909/////////////////////////////////////////////////////////////////////
910// Noise3D
911/////////////////////////////////////////////////////////////////////
912
913function Noise3D(freq, vfreq)
914{
915 freq = Math.floor(freq);
916 vfreq = Math.floor(vfreq);
917 this.freq = freq;
918 this.vfreq = vfreq;
919 this.grads = new Array(freq);
920
921 for (var i=0; i < freq; ++i)
922 {
923 this.grads[i] = new Array(freq);
924 for (var j=0; j < freq; ++j)
925 {
926 this.grads[i][j] = new Array(vfreq);
927 for(var k=0; k < vfreq; ++k)
928 {
929 var v = new Vector3D();
930 do
931 {
932 v.set(2*randFloat()-1, 2*randFloat()-1, 2*randFloat()-1);
933 }
934 while(v.lengthSquared() > 1 || v.lengthSquared() < 0.1);
935
936 v.normalize();
937
938 this.grads[i][j][k] = v;
939 }
940 }
941 }
942}
943
944Noise3D.prototype.get = function(x, y, z)
945{
946 x *= this.freq;
947 y *= this.freq;
948 z *= this.vfreq;
949
950 var ix =modPos(Math.floor(x), this.freq);
951 var iy = modPos(Math.floor(y), this.freq);
952 var iz = modPos(Math.floor(z), this.vfreq);
953
954 var fx = x - ix;
955 var fy = y - iy;
956 var fz = z - iz;
957
958 var ix1 = (ix+1) % this.freq;
959 var iy1 = (iy+1) % this.freq;
960 var iz1 = (iz+1) % this.vfreq;
961
962 var s0 = this.grads[ix][iy][iz].dot(new Vector3D(fx, fy, fz));
963 var t0 = this.grads[ix1][iy][iz].dot(new Vector3D(fx-1, fy, fz));
964 var u0 = this.grads[ix][iy1][iz].dot(new Vector3D(fx, fy-1, fz));
965 var v0 = this.grads[ix1][iy1][iz].dot(new Vector3D(fx-1, fy-1, fz));
966
967 var s1 = this.grads[ix][iy][iz1].dot(new Vector3D(fx, fy, fz-1));
968 var t1 = this.grads[ix1][iy][iz1].dot(new Vector3D(fx-1, fy, fz-1));
969 var u1 = this.grads[ix][iy1][iz1].dot(new Vector3D(fx, fy-1, fz-1));
970 var v1 = this.grads[ix1][iy1][iz1].dot(new Vector3D(fx-1, fy-1, fz-1));
971
972 var ex = easeCurve(fx);
973 var ey = easeCurve(fy);
974 var ez = easeCurve(fz);
975
976 var a0 = s0 + ex*(t0-s0);
977 var b0 = u0 + ex*(v0-u0);
978 var c0 = a0 + ey*(b0-a0);
979
980 var a1 = s1 + ex*(t1-s1);
981 var b1 = u1 + ex*(v1-u1);
982 var c1 = a1 + ey*(b1-a1);
983
984 return (c0 + ez*(c1-c0)) * 0.5 + 0.5;
985};
986const ELEVATION_SET = 0;
987const ELEVATION_MODIFY = 1;
988
989/////////////////////////////////////////////////////////////////////////////
990// ElevationPainter
991/////////////////////////////////////////////////////////////////////////////
992
993function ElevationPainter(elevation)
994{
995 this.elevation = elevation;
996 this.DX = [0, 1, 1, 0];
997 this.DY = [0, 0, 1, 1];
998}
999
1000ElevationPainter.prototype.paint = function(area)
1001{
1002 var length = area.points.length;
1003 var elevation = this.elevation;
1004
1005 for (var i=0; i < length; i++)
1006 {
1007 var pt = area.points[i];
1008
1009 for (var j=0; j < 4; j++)
1010 {
1011 g_Map.height[pt.x+this.DX[j]][pt.y+this.DY[j]] = elevation;
1012 }
1013 }
1014};
1015
1016/////////////////////////////////////////////////////////////////////////////
1017// LayeredPainter
1018/////////////////////////////////////////////////////////////////////////////
1019
1020function LayeredPainter(terrainArray, widths)
1021{
1022 if (!(terrainArray instanceof Array))
1023 error("terrains must be an array!");
1024
1025 this.terrains = [];
1026 for (var i = 0; i < terrainArray.length; ++i)
1027 this.terrains.push(createTerrain(terrainArray[i]));
1028
1029 this.widths = widths;
1030}
1031
1032LayeredPainter.prototype.paint = function(area)
1033{
1034 var size = getMapSize();
1035 var saw = new Array(size);
1036 var dist = new Array(size);
1037
1038 // init typed arrays
1039 for (var i = 0; i < size; ++i)
1040 {
1041 saw[i] = new Array(size); // bool / uint8
1042 dist[i] = new Uint16Array(size); // uint16
1043 }
1044
1045 // Point queue (implemented with array)
1046 var pointQ = [];
1047
1048 // push edge points
1049 var pts = area.points;
1050 var length = pts.length;
1051
1052 for (var i=0; i < length; i++)
1053 {
1054 var x = pts[i].x;
1055 var y = pts[i].y;
1056
1057 for (var dx=-1; dx <= 1; dx++)
1058 {
1059 var nx = x+dx;
1060 for (var dy=-1; dy <= 1; dy++)
1061 {
1062 var ny = y+dy;
1063
1064 if (g_Map.validT(nx, ny) && g_Map.area[nx][ny] && g_Map.area[nx][ny] != area && !saw[nx][ny])
1065 {
1066 saw[nx][ny] = 1;
1067 dist[nx][ny] = 0;
1068 pointQ.push(new Point(nx, ny));
1069 }
1070 }
1071 }
1072 }
1073
1074 // do BFS inwards to find distances to edge
1075 while (pointQ.length)
1076 {
1077 var pt = pointQ.shift(); // Pop queue
1078 var px = pt.x;
1079 var py = pt.y;
1080 var d = dist[px][py];
1081
1082 // paint if in area
1083 if (g_Map.area[px][py] == area)
1084 {
1085 var w=0;
1086 var i=0;
1087
1088 for (; i < this.widths.length; i++)
1089 {
1090 w += this.widths[i];
1091 if (w >= d)
1092 {
1093 break;
1094 }
1095 }
1096 this.terrains[i].place(px, py);
1097 }
1098
1099 // enqueue neighbours
1100 for (var dx=-1; dx<=1; dx++)
1101 {
1102 var nx = px+dx;
1103 for (var dy=-1; dy<=1; dy++)
1104 {
1105 var ny = py+dy;
1106
1107 if (g_Map.validT(nx, ny) && g_Map.area[nx][ny] && g_Map.area[nx][ny] == area && !saw[nx][ny])
1108 {
1109 saw[nx][ny] = 1;
1110 dist[nx][ny] = d+1;
1111 pointQ.push(new Point(nx, ny));
1112 }
1113 }
1114 }
1115 }
1116};
1117
1118/////////////////////////////////////////////////////////////////////////////
1119// MultiPainter
1120/////////////////////////////////////////////////////////////////////////////
1121
1122function MultiPainter(painters)
1123{
1124 this.painters = painters;
1125}
1126
1127MultiPainter.prototype.paint = function(area)
1128{
1129 for (var i=0; i < this.painters.length; i++)
1130 {
1131 this.painters[i].paint(area);
1132 }
1133};
1134
1135/////////////////////////////////////////////////////////////////////////////
1136// SmoothElevationPainter
1137/////////////////////////////////////////////////////////////////////////////
1138
1139function SmoothElevationPainter(type, elevation, blendRadius)
1140{
1141 this.type = type;
1142 this.elevation = elevation;
1143 this.blendRadius = blendRadius;
1144
1145 if (type != ELEVATION_SET && type != ELEVATION_MODIFY)
1146 error("SmoothElevationPainter: invalid type '"+type+"'");
1147}
1148
1149SmoothElevationPainter.prototype.checkInArea = function(area, x, y)
1150{
1151 if (g_Map.validT(x, y))
1152 {
1153 return (g_Map.area[x][y] && g_Map.area[x][y] == area);
1154 }
1155 else
1156 {
1157 return false;
1158 }
1159};
1160var mapSize;
1161SmoothElevationPainter.prototype.paint = function(area)
1162{
1163 var pointQ = [];
1164 var pts = area.points;
1165 var heightPts = [];
1166
1167 mapSize = getMapSize()+1;
1168
1169 var saw = new Array(mapSize);
1170 var dist = new Array(mapSize);
1171 var gotHeightPt = new Array(mapSize);
1172 var newHeight = new Array(mapSize);
1173
1174 // init typed arrays
1175 for (var i = 0; i < mapSize; ++i)
1176 {
1177 saw[i] = new Array(mapSize); // bool / uint8
1178 dist[i] = new Uint16Array(mapSize); // uint16
1179 gotHeightPt[i] = new Array(mapSize); // bool / uint8
1180 newHeight[i] = new Float32Array(mapSize); // float32
1181 }
1182
1183 var length = pts.length;
1184
1185 // get a list of all points
1186 for (var i=0; i < length; i++)
1187 {
1188 var x = pts[i].x;
1189 var y = pts[i].y;
1190
1191 for (var dx=-1; dx <= 2; dx++)
1192 {
1193 var nx = x+dx;
1194 for (var dy=-1; dy <= 2; dy++)
1195 {
1196 var ny = y+dy;
1197
1198 if (g_Map.validH(nx, ny) && !gotHeightPt[nx][ny])
1199 {
1200 gotHeightPt[nx][ny] = 1;
1201 heightPts.push(new Point(nx, ny));
1202 newHeight[nx][ny] = g_Map.height[nx][ny];
1203 }
1204 }
1205 }
1206 }
1207
1208 // push edge points
1209 for (var i=0; i < length; i++)
1210 {
1211 var x = pts[i].x, y = pts[i].y;
1212 for (var dx=-1; dx <= 2; dx++)
1213 {
1214 var nx = x+dx;
1215 for (var dy=-1; dy <= 2; dy++)
1216 {
1217 var ny = y+dy;
1218
1219 if (g_Map.validH(nx, ny)
1220 && !this.checkInArea(area, nx, ny)
1221 && !this.checkInArea(area, nx-1, ny)
1222 && !this.checkInArea(area, nx, ny-1)
1223 && !this.checkInArea(area, nx-1, ny-1)
1224 && !saw[nx][ny])
1225 {
1226 saw[nx][ny]= 1;
1227 dist[nx][ny] = 0;
1228 pointQ.push(new Point(nx, ny));
1229 }
1230 }
1231 }
1232 }
1233
1234 // do BFS inwards to find distances to edge
1235 while(pointQ.length)
1236 {
1237 var pt = pointQ.shift();
1238 var px = pt.x;
1239 var py = pt.y;
1240 var d = dist[px][py];
1241
1242 // paint if in area
1243 if (g_Map.validH(px, py)
1244 && (this.checkInArea(area, px, py) || this.checkInArea(area, px-1, py)
1245 || this.checkInArea(area, px, py-1) || this.checkInArea(area, px-1, py-1)))
1246 {
1247 if (d <= this.blendRadius)
1248 {
1249 var a = (d-1) / this.blendRadius;
1250 if (this.type == ELEVATION_SET)
1251 {
1252 newHeight[px][py] = a*this.elevation + (1-a)*g_Map.height[px][py];
1253 }
1254 else
1255 { // type == MODIFY
1256 newHeight[px][py] += a*this.elevation;
1257 }
1258 }
1259 else
1260 { // also happens when blendRadius == 0
1261 if (this.type == ELEVATION_SET)
1262 {
1263 newHeight[px][py] = this.elevation;
1264 }
1265 else
1266 { // type == MODIFY
1267 newHeight[px][py] += this.elevation;
1268 }
1269 }
1270 }
1271
1272 // enqueue neighbours
1273 for (var dx=-1; dx <= 1; dx++)
1274 {
1275 var nx = px+dx;
1276 for (var dy=-1; dy <= 1; dy++)
1277 {
1278 var ny = py+dy;
1279
1280 if (g_Map.validH(nx, ny)
1281 && (this.checkInArea(area, nx, ny) || this.checkInArea(area, nx-1, ny)
1282 || this.checkInArea(area, nx, ny-1) || this.checkInArea(area, nx-1, ny-1))
1283 && !saw[nx][ny])
1284 {
1285 saw[nx][ny] = 1;
1286 dist[nx][ny] = d+1;
1287 pointQ.push(new Point(nx, ny));
1288 }
1289 }
1290 }
1291 }
1292
1293 length = heightPts.length;
1294
1295 // smooth everything out
1296 for (var i = 0; i < length; ++i)
1297 {
1298 var pt = heightPts[i];
1299 var px = pt.x;
1300 var py = pt.y;
1301
1302 if ((this.checkInArea(area, px, py) || this.checkInArea(area, px-1, py)
1303 || this.checkInArea(area, px, py-1) || this.checkInArea(area, px-1, py-1)))
1304 {
1305 var sum = 8 * newHeight[px][py];
1306 var count = 8;
1307
1308 for (var dx=-1; dx <= 1; dx++)
1309 {
1310 var nx = px+dx;
1311 for (var dy=-1; dy <= 1; dy++)
1312 {
1313 var ny = py+dy;
1314
1315 if (g_Map.validH(nx, ny))
1316 {
1317 sum += newHeight[nx][ny];
1318 count++;
1319 }
1320 }
1321 }
1322
1323 g_Map.height[px][py] = sum/count;
1324 }
1325 }
1326};
1327
1328/////////////////////////////////////////////////////////////////////////////
1329// TerrainPainter
1330/////////////////////////////////////////////////////////////////////////////
1331
1332function TerrainPainter(terrain)
1333{
1334 this.terrain = createTerrain(terrain);
1335}
1336
1337TerrainPainter.prototype.paint = function(area)
1338{
1339 var length = area.points.length;
1340 for (var i=0; i < length; i++)
1341 {
1342 var pt = area.points[i];
1343 this.terrain.place(pt.x, pt.y);
1344 }
1345};
1346
1347/////////////////////////////////////////////////////////////////////////////
1348// TileClassPainter
1349/////////////////////////////////////////////////////////////////////////////
1350
1351function TileClassPainter(tileClass)
1352{
1353 this.tileClass = tileClass;
1354}
1355
1356TileClassPainter.prototype.paint = function(area)
1357{
1358 var length = area.points.length;
1359 for (var i=0; i < length; i++)
1360 {
1361 var pt = area.points[i];
1362 this.tileClass.add(pt.x, pt.y);
1363 }
1364};
1365
1366/////////////////////////////////////////////////////////////////////////////////////////
1367// ClumpPlacer
1368/////////////////////////////////////////////////////////////////////////////////////////
1369
1370function ClumpPlacer(size, coherence, smoothness, failFraction, x, y)
1371{
1372 this.size = size;
1373 this.coherence = coherence;
1374 this.smoothness = smoothness;
1375 this.failFraction = (failFraction !== undefined ? failFraction : 0);
1376 this.x = (x !== undefined ? x : -1);
1377 this.y = (y !== undefined ? y : -1);
1378}
1379
1380ClumpPlacer.prototype.place = function(constraint)
1381{
1382 if (!g_Map.validT(this.x, this.y) || !constraint.allows(this.x, this.y))
1383 {
1384 return false;
1385 }
1386
1387 var retVec = [];
1388
1389 var size = getMapSize();
1390 var gotRet = new Array(size);
1391 for (var i = 0; i < size; ++i)
1392 {
1393 gotRet[i] = new Array(size); // bool / uint8
1394 }
1395
1396 var radius = Math.sqrt(this.size / PI);
1397 var perim = 4 * radius * 2 * PI;
1398 var intPerim = Math.ceil(perim);
1399
1400 var ctrlPts = 1 + Math.floor(1.0/Math.max(this.smoothness,1.0/intPerim));
1401
1402 if (ctrlPts > radius * 2 * PI)
1403 ctrlPts = Math.floor(radius * 2 * PI) + 1;
1404
1405 var noise = new Float32Array(intPerim); //float32
1406 var ctrlCoords = new Float32Array(ctrlPts+1); //float32
1407 var ctrlVals = new Float32Array(ctrlPts+1); //float32
1408
1409 for (var i=0; i < ctrlPts; i++)
1410 {
1411 ctrlCoords[i] = i * perim / ctrlPts;
1412 ctrlVals[i] = 2.0*randFloat();
1413 }
1414
1415 var c = 0;
1416 var looped = 0;
1417 for (var i=0; i < intPerim; i++)
1418 {
1419 if (ctrlCoords[(c+1) % ctrlPts] < i && !looped)
1420 {
1421 c = (c+1) % ctrlPts;
1422 if (c == ctrlPts-1)
1423 looped = 1;
1424 }
1425
1426 var t = (i - ctrlCoords[c]) / ((looped ? perim : ctrlCoords[(c+1)%ctrlPts]) - ctrlCoords[c]);
1427 var v0 = ctrlVals[(c+ctrlPts-1)%ctrlPts];
1428 var v1 = ctrlVals[c];
1429 var v2 = ctrlVals[(c+1)%ctrlPts];
1430 var v3 = ctrlVals[(c+2)%ctrlPts];
1431 var P = (v3 - v2) - (v0 - v1);
1432 var Q = (v0 - v1) - P;
1433 var R = v2 - v0;
1434 var S = v1;
1435
1436 noise[i] = P*t*t*t + Q*t*t + R*t + S;
1437 }
1438
1439 var failed = 0;
1440 for (var p=0; p < intPerim; p++)
1441 {
1442 var th = 2 * PI * p / perim;
1443 var r = radius * (1 + (1-this.coherence)*noise[p]);
1444 var s = Math.sin(th);
1445 var c = Math.cos(th);
1446 var xx=this.x;
1447 var yy=this.y;
1448
1449 for (var k=0; k < Math.ceil(r); k++)
1450 {
1451 var i = Math.floor(xx);
1452 var j = Math.floor(yy);
1453 if (g_Map.validT(i, j) && constraint.allows(i, j))
1454 {
1455 if (!gotRet[i][j])
1456 { // Only include each point once
1457 gotRet[i][j] = 1;
1458 retVec.push(new Point(i, j));
1459 }
1460 }
1461 else
1462 {
1463 failed++;
1464 }
1465 xx += s;
1466 yy += c;
1467 }
1468 }
1469
1470 return ((failed > this.size*this.failFraction) ? undefined : retVec);
1471};
1472
1473/////////////////////////////////////////////////////////////////////////////////////////
1474// RectPlacer
1475/////////////////////////////////////////////////////////////////////////////////////////
1476
1477function RectPlacer(x1, y1, x2, y2)
1478{
1479 this.x1 = x1;
1480 this.y1 = y1;
1481 this.x2 = x2;
1482 this.y2 = y2;
1483
1484 if (x1 > x2 || y1 > y2)
1485 error("RectPlacer: incorrect bounds on rect");
1486}
1487
1488RectPlacer.prototype.place = function(constraint)
1489{
1490 var ret = [];
1491
1492 var x2 = this.x2;
1493 var y2 = this.y2;
1494
1495 for (var x=this.x1; x < x2; x++)
1496 {
1497 for (var y=this.y1; y < y2; y++)
1498 {
1499 if (g_Map.validT(x, y) && constraint.allows(x, y))
1500 {
1501 ret.push(new Point(x, y));
1502 }
1503 else
1504 {
1505 return undefined;
1506 }
1507 }
1508 }
1509
1510 return ret;
1511
1512};
1513
1514/////////////////////////////////////////////////////////////////////////////////////////
1515// ObjectGroupPlacer
1516/////////////////////////////////////////////////////////////////////////////////////////
1517
1518function ObjectGroupPlacer() {}
1519
1520/////////////////////////////////////////////////////////////////////////////////////////
1521// SimpleGroup
1522/////////////////////////////////////////////////////////////////////////////////////////
1523
1524function SimpleObject(type, minCount, maxCount, minDistance, maxDistance, minAngle, maxAngle)
1525{
1526 this.type = type;
1527 this.minCount = minCount;
1528 this.maxCount = maxCount;
1529 this.minDistance = minDistance;
1530 this.maxDistance = maxDistance;
1531 this.minAngle = (minAngle !== undefined ? minAngle : 0);
1532 this.maxAngle = (maxAngle !== undefined ? maxAngle : 2*PI);
1533
1534 if (minCount > maxCount)
1535 error("SimpleObject: minCount must be less than or equal to maxCount");
1536
1537 if (minDistance > maxDistance)
1538 error("SimpleObject: minDistance must be less than or equal to maxDistance");
1539
1540 if (minAngle > maxAngle)
1541 error("SimpleObject: minAngle must be less than or equal to maxAngle");
1542}
1543
1544SimpleObject.prototype.place = function(cx, cy, player, avoidSelf, constraint)
1545{
1546 var failCount = 0;
1547 var count = randInt(this.minCount, this.maxCount);
1548 var resultObjs = [];
1549
1550 for (var i=0; i < count; i++)
1551 {
1552 while(true)
1553 {
1554 var distance = randFloat(this.minDistance, this.maxDistance);
1555 var direction = randFloat(0, 2*PI);
1556
1557 var x = cx + 0.5 + distance * Math.cos(direction);
1558 var y = cy + 0.5 + distance * Math.sin(direction);
1559 var fail = false; // reset place failure flag
1560
1561 if (x < 0 || y < 0 || x > g_Map.size || y > g_Map.size)
1562 {
1563 fail = true;
1564 }
1565 else
1566 {
1567 if (avoidSelf)
1568 {
1569 var length = resultObjs.length;
1570 for (var i = 0; (i < length) && !fail; i++)
1571 {
1572 var dx = x - resultObjs[i].x;
1573 var dy = y - resultObjs[i].y;
1574
1575 if ((dx*dx + dy*dy) < 1)
1576 {
1577 fail = true;
1578 }
1579 }
1580 }
1581
1582 if (!fail)
1583 {
1584 if (!constraint.allows(Math.floor(x), Math.floor(y)))
1585 {
1586 fail = true;
1587 }
1588 else
1589 { // if we got here, we're good
1590 var angle = randFloat(this.minAngle, this.maxAngle);
1591 resultObjs.push(new Entity(this.type, player, x, y, angle));
1592 break;
1593 }
1594 }
1595 }
1596
1597 if (fail)
1598 {
1599 failCount++;
1600 if (failCount > 20) // TODO: Make this adjustable
1601 {
1602 return undefined;
1603 }
1604 }
1605 }
1606 }
1607
1608 return resultObjs;
1609};
1610
1611function SimpleGroup(elements, avoidSelf, tileClass, x, y)
1612{
1613 this.elements = elements;
1614 this.tileClass = (tileClass !== undefined ? getTileClass(tileClass) : null);
1615 this.avoidSelf = (avoidSelf !== undefined ? avoidSelf : false);
1616 this.x = (x !== undefined ? x : -1);
1617 this.y = (y !== undefined ? y : -1);
1618}
1619
1620SimpleGroup.prototype.place = function(player, constraint)
1621{
1622 var resultObjs = [];
1623
1624 // Try placement of objects
1625 var length = this.elements.length;
1626 for (var i=0; i < length; i++)
1627 {
1628 var objs = this.elements[i].place(this.x, this.y, player, this.avoidSelf, constraint);
1629 if (objs === undefined)
1630 { // Failure
1631 return false;
1632 }
1633 else
1634 {
1635 resultObjs = resultObjs.concat(objs);
1636 }
1637 }
1638
1639 // Add placed objects to map
1640 length = resultObjs.length;
1641 for (var i=0; i < length; i++)
1642 {
1643 g_Map.addObjects(resultObjs[i]);
1644
1645 if (this.tileClass !== null)
1646 { // Round object position to integer
1647 this.tileClass.add(Math.floor(resultObjs[i].x), Math.floor(resultObjs[i].y));
1648 }
1649 }
1650
1651 return true;
1652};
1653
1654function Point(x, y)
1655{
1656 this.x = (x !== undefined ? x : 0);
1657 this.y = (y !== undefined ? y : 0);
1658}
1659
1660/*
1661 * Return a random floating point number using Math.random library
1662 *
1663 * If no parameter given, the returned float is in the interval [0, 1)
1664 * If two parameters are given, they are minval and maxval, and the returned float is in the interval [minval, maxval)
1665 */
1666function randFloat()
1667{
1668 if (arguments.length == 0)
1669 {
1670 return Math.random();
1671 }
1672 else if (arguments.length == 2)
1673 {
1674 var minVal = arguments[0];
1675 var maxVal = arguments[1];
1676
1677 return minVal + randFloat() * (maxVal - minVal);
1678 }
1679 else
1680 {
1681 error("randFloat() received invalid number of arguments: "+arguments.length);
1682 return undefined;
1683 }
1684}
1685
1686/*
1687 * Return a random integer using Math.random library
1688 *
1689 * If one parameter given, it's maxval, and the returned integer is in the interval [0, maxval)
1690 * If two parameters are given, they are minval and maxval, and the returned integer is in the interval [minval, maxval]
1691 */
1692function randInt()
1693{
1694 if (arguments.length == 1)
1695 {
1696 var maxVal = arguments[0];
1697 return Math.floor(Math.random() * maxVal);
1698 }
1699 else if (arguments.length == 2)
1700 {
1701 var minVal = arguments[0];
1702 var maxVal = arguments[1];
1703
1704 return minVal + randInt(maxVal - minVal + 1);
1705 }
1706 else
1707 {
1708 error("randInt() received invalid number of arguments: "+arguments.length);
1709 return undefined;
1710 }
1711}
1712//////////////////////////////////////////////////////////////////////
1713// Terrain
1714//////////////////////////////////////////////////////////////////////
1715
1716function Terrain() {}
1717
1718Terrain.prototype.place = function(x, y)
1719{
1720 // Clear old array
1721 g_Map.terrainObjects[x][y] = [];
1722
1723 this.placeNew(x, y);
1724};
1725
1726Terrain.prototype.placeNew = function() {};
1727
1728//////////////////////////////////////////////////////////////////////
1729// SimpleTerrain
1730//////////////////////////////////////////////////////////////////////
1731
1732function SimpleTerrain(texture, treeType)
1733{
1734 if (texture === undefined)
1735 error("SimpleTerrain: texture not defined");
1736
1737 this.texture = texture;
1738 this.treeType = treeType;
1739}
1740
1741SimpleTerrain.prototype = new Terrain();
1742SimpleTerrain.prototype.constructor = SimpleTerrain;
1743SimpleTerrain.prototype.placeNew = function(x, y)
1744{
1745 if (this.treeType !== undefined)
1746 g_Map.terrainObjects[x][y].push(new Entity(this.treeType, 0, x+0.5, y+0.5, randFloat()*PI));
1747
1748 g_Map.texture[x][y] = g_Map.getID(this.texture);
1749};
1750
1751//////////////////////////////////////////////////////////////////////
1752// RandomTerrain
1753//////////////////////////////////////////////////////////////////////
1754
1755function RandomTerrain(terrains)
1756{
1757 if (!(terrains instanceof Array) || !terrains.length)
1758 error("Invalid terrains array");
1759
1760 this.terrains = terrains;
1761}
1762
1763RandomTerrain.prototype = new Terrain();
1764RandomTerrain.prototype.constructor = RandomTerrain;
1765RandomTerrain.prototype.placeNew = function(x, y)
1766{
1767 this.terrains[randInt(this.terrains.length)].placeNew(x, y);
1768};
1769//////////////////////////////////////////////////////////////////////
1770// RangeOp
1771//////////////////////////////////////////////////////////////////////
1772
1773function RangeOp(size)
1774{
1775 // Get smallest power of 2 which is greater than or equal to size
1776 this.nn = 1;
1777 while (this.nn < size) {
1778 this.nn *= 2;
1779 }
1780
1781 this.vals = new Int16Array(2*this.nn); // int16
1782}
1783
1784RangeOp.prototype.set = function(pos, amt)
1785{
1786 this.add(pos, amt - this.vals[this.nn + pos]);
1787};
1788
1789RangeOp.prototype.add = function(pos, amt)
1790{
1791 for(var s = this.nn; s >= 1; s /= 2)
1792 {
1793 this.vals[s + pos] += amt;
1794 pos = Math.floor(pos/2);
1795 }
1796};
1797
1798RangeOp.prototype.get = function(start, end)
1799{
1800 var ret = 0;
1801 var i;
1802 var nn = this.nn;
1803
1804 // Count from start to end by powers of 2
1805 for (i = 1; start+i <= end; i *= 2)
1806 {
1807 if (start & i)
1808 { // For each bit in start
1809 ret += this.vals[nn/i + Math.floor(start/i)];
1810 start += i;
1811 }
1812 }
1813
1814 //
1815 while(i >= 1)
1816 {
1817 if(start+i <= end)
1818 {
1819 ret += this.vals[nn/i + Math.floor(start/i)];
1820 start += i;
1821 }
1822 i /= 2;
1823 }
1824
1825 return ret;
1826};
1827
1828
1829//////////////////////////////////////////////////////////////////////
1830// TileClass
1831//////////////////////////////////////////////////////////////////////
1832
1833function TileClass(size)
1834{
1835 this.size = size;
1836 this.inclusionCount = new Array(size);
1837 this.rangeCount = new Array(size);
1838
1839 for (var i=0; i < size; ++i)
1840 {
1841 this.inclusionCount[i] = new Int16Array(size); //int16
1842 this.rangeCount[i] = new RangeOp(size);
1843 }
1844}
1845
1846TileClass.prototype.add = function(x, y)
1847{
1848 if (!this.inclusionCount[x][y])
1849 {
1850 this.rangeCount[y].add(x, 1);
1851 }
1852
1853 this.inclusionCount[x][y]++;
1854};
1855
1856TileClass.prototype.remove = function(x, y)
1857{
1858 this.inclusionCount[x][y]--;
1859 if(!this.inclusionCount[x][y])
1860 {
1861 this.rangeCount[y].add(x, -1);
1862 }
1863};
1864
1865TileClass.prototype.countInRadius = function(cx, cy, radius, returnMembers)
1866{
1867 var members = 0;
1868 var nonMembers = 0;
1869 var size = this.size;
1870
1871 var ymax = cy+radius;
1872
1873 for (var y = cy-radius; y <= ymax; y++)
1874 {
1875 var iy = Math.floor(y);
1876 if(iy >= 0 && iy < size)
1877 {
1878 var dy = y - cy;
1879 var dx = Math.sqrt(radius*radius - dy*dy);
1880
1881 var lowerX = Math.floor(cx - dx);
1882 var upperX = Math.floor(cx + dx);
1883
1884 var minX = (lowerX > 0 ? lowerX : 0);
1885 var maxX = (upperX < size ? upperX+1 : size);
1886
1887 var total = maxX - minX;
1888 var mem = this.rangeCount[iy].get(minX, maxX);
1889
1890 members += mem;
1891 nonMembers += total - mem;
1892 }
1893 }
1894
1895 if (returnMembers)
1896 return members;
1897 else
1898 return nonMembers;
1899};
1900
1901TileClass.prototype.countMembersInRadius = function(cx, cy, radius)
1902{
1903 return this.countInRadius(cx, cy, radius, true);
1904};
1905
1906TileClass.prototype.countNonMembersInRadius = function(cx, cy, radius)
1907{
1908 return this.countInRadius(cx, cy, radius, false);
1909};
1910/////////////////////////////////////////////////////////////////////
1911// Vector2D
1912/////////////////////////////////////////////////////////////////////
1913
1914// TODO: Type errors if v not instanceof Vector classes
1915// TODO: Possible implement in C++
1916
1917function Vector2D(x, y)
1918{
1919 if (arguments.length == 2)
1920 {
1921 this.set(x, y);
1922 }
1923 else
1924 {
1925 this.set(0, 0);
1926 }
1927}
1928
1929Vector2D.prototype.set = function(x, y)
1930{
1931 this.x = x;
1932 this.y = y;
1933};
1934
1935Vector2D.prototype.add = function(v)
1936{
1937 return new Vector2D(this.x + v.x, this.y + v.y);
1938};
1939
1940Vector2D.prototype.sub = function(v)
1941{
1942 return new Vector2D(this.x - v.x, this.y - v.y);
1943};
1944
1945Vector2D.prototype.mult = function(f)
1946{
1947 return new Vector2D(this.x * f, this.y * f);
1948};
1949
1950Vector2D.prototype.div = function(f)
1951{
1952 return new Vector2D(this.x / f, this.y / f);
1953};
1954
1955Vector2D.prototype.dot = function(v)
1956{
1957 return this.x * v.x + this.y * v.y;
1958};
1959
1960Vector2D.prototype.lengthSquared = function()
1961{
1962 return this.dot(this);
1963};
1964
1965Vector2D.prototype.length = function()
1966{
1967 return Math.sqrt(this.lengthSquared());
1968};
1969
1970Vector2D.prototype.normalize = function()
1971{
1972 var mag = this.length();
1973
1974 this.x /= mag;
1975 this.y /= mag;
1976};
1977
1978/////////////////////////////////////////////////////////////////////
1979// Vector3D
1980/////////////////////////////////////////////////////////////////////
1981
1982function Vector3D(x, y, z)
1983{
1984 if (arguments.length == 3)
1985 {
1986 this.set(x, y, z);
1987 }
1988 else
1989 {
1990 this.set(0, 0, 0);
1991 }
1992}
1993
1994Vector3D.prototype.set = function(x, y, z)
1995{
1996 this.x = x;
1997 this.y = y;
1998 this.z = z;
1999};
2000
2001Vector3D.prototype.add = function(v)
2002{
2003 return new Vector3D(this.x + v.x, this.y + v.y, this.z + v.z);
2004};
2005
2006Vector3D.prototype.sub = function(v)
2007{
2008 return new Vector3D(this.x - v.x, this.y - v.y, this.z - v.z);
2009};
2010
2011Vector3D.prototype.mult = function(f)
2012{
2013 return new Vector3D(this.x * f, this.y * f, this.z * f);
2014};
2015
2016Vector3D.prototype.div = function(f)
2017{
2018 return new Vector3D(this.x / f, this.y / f, this.z / f);
2019};
2020
2021Vector3D.prototype.dot = function(v)
2022{
2023 return this.x * v.x + this.y * v.y + this.z * v.z;
2024};
2025
2026Vector3D.prototype.lengthSquared = function()
2027{
2028 return this.dot(this);
2029};
2030
2031Vector3D.prototype.length = function()
2032{
2033 return Math.sqrt(this.lengthSquared());
2034};
2035
2036Vector3D.prototype.normalize = function()
2037{
2038 var mag = this.length();
2039
2040 this.x /= mag;
2041 this.y /= mag;
2042 this.z /= mag;
2043};
2044
2045var settings = {
2046 Size : 13,
2047 BaseTerrain: "grass1_spring",
2048 BaseHeight: 0,
2049 PlayerData : [ {Civ: "hele"}, {Civ: "hele"}, {Civ: "hele"}, {Civ: "hele"}]
2050};
2051InitMapGen(settings);
2052
2053// terrain textures
2054const tSand = "desert_dirt_rough";
2055const tDunes = "desert_sand_dunes_100";
2056const tFineSand = "desert_sand_smooth";
2057const tCliff = "desert_cliff_badlands";
2058const tGrassSand75 = "desert_grass_a";
2059const tGrassSand50 = "desert_grass_a_sand";
2060const tGrassSand25 = "desert_grass_a_stones";
2061const tDirt = "desert_dirt_rough";
2062const tDirtCracks = "desert_dirt_cracks";
2063const tShore = "desert_sand_wet";
2064const tWater = "desert_shore_stones";
2065const tWaterDeep = "desert_shore_stones_wet";
2066
2067// gaia entities
2068const oBerryBush = "gaia/flora_bush_berry";
2069const oSheep = "gaia/fauna_sheep";
2070const oDeer = "gaia/fauna_deer";
2071const oMine = "gaia/geology_stone_desert_small";
2072const oTree = "gaia/flora_tree_medit_fan_palm";
2073
2074// decorative props
2075const aBush = "actor|props/flora/bush_dry_a.xml";
2076const aDecorativeRock = "actor|geology/gray1.xml";
2077
2078var tForest = tGrassSand75 + TERRAIN_SEPARATOR + oTree;
2079
2080// initialize map
2081
2082log("Initializing map...");
2083
2084var numPlayers = getNumPlayers();
2085var mapSize = getMapSize();
2086
2087// create tile classes
2088
2089var clPlayer = createTileClass();
2090var clHill1 = createTileClass();
2091var clHill2 = createTileClass();
2092var clHill3 = createTileClass();
2093var clForest = createTileClass();
2094var clWater = createTileClass();
2095var clPatch = createTileClass();
2096var clRock = createTileClass();
2097var clFood = createTileClass();
2098var clBaseResource = createTileClass();
2099
2100// place players
2101
2102var playerX = new Array(numPlayers);
2103var playerY = new Array(numPlayers);
2104var playerAngle = new Array(numPlayers);
2105
2106var startAngle = randFloat() * 2 * PI;
2107for (var i=0; i < numPlayers; i++)
2108{
2109 playerAngle[i] = startAngle + i*2*PI/numPlayers;
2110 playerX[i] = 0.5 + 0.39*cos(playerAngle[i]);
2111 playerY[i] = 0.5 + 0.39*sin(playerAngle[i]);
2112}
2113
2114for (var i=0; i < numPlayers; i++)
2115{
2116 log("Creating base for player " + (i + 1) + "...");
2117
2118 // some constants
2119 var radius = 20;
2120
2121 // get the x and y in tiles
2122 var fx = fractionToTiles(playerX[i]);
2123 var fy = fractionToTiles(playerY[i]);
2124 var ix = round(fx);
2125 var iy = round(fy);
2126
2127 // calculate size based on the radius
2128 var size = PI * radius * radius;
2129
2130 // create the hill
2131 var placer = new ClumpPlacer(size, 0.9, 0.5, 0, ix, iy);
2132 createArea(placer, paintClass(clPlayer), null);
2133
2134 // create the central road patch
2135 placer = new ClumpPlacer(PI*2*2, 0.6, 0.3, 0.5, ix, iy);
2136 var painter = new TerrainPainter(tDirt);
2137 createArea(placer, painter, null);
2138
2139 // create the TC and citizens
2140 var civ = getCivCode(i);
2141 var group = new SimpleGroup(
2142 [ // elements (type, count, distance)
2143 new SimpleObject("structures/"+civ+"_civil_centre", 1,1, 0,0),
2144 new SimpleObject("units/"+civ+"_support_female_citizen", 3,3, 5,5)
2145 ],
2146 true, null, ix, iy
2147 );
2148 createObjectGroup(group, i+1);
2149
2150 // create berry bushes
2151 var bbAngle = randFloat()*2*PI;
2152 var bbDist = 10;
2153 var bbX = round(fx + bbDist * cos(bbAngle));
2154 var bbY = round(fy + bbDist * sin(bbAngle));
2155 group = new SimpleGroup(
2156 [new SimpleObject(oSheep, 5,5, 0,2)],
2157 true, clBaseResource, bbX, bbY
2158 );
2159 createObjectGroup(group, 0);
2160
2161 // create mines
2162 var mAngle = bbAngle;
2163 while(abs(mAngle - bbAngle) < PI/3) {
2164 mAngle = randFloat()*2*PI;
2165 }
2166 var mDist = 12;
2167 var mX = round(fx + mDist * cos(mAngle));
2168 var mY = round(fy + mDist * sin(mAngle));
2169 group = new SimpleGroup(
2170 [new SimpleObject(oMine, 3,3, 0,2)],
2171 true, clBaseResource, mX, mY
2172 );
2173 createObjectGroup(group, 0);
2174
2175 // create starting straggler trees
2176 group = new SimpleGroup(
2177 [new SimpleObject(oTree, 2,2, 6,12)],
2178 true, null, ix, iy
2179 );
2180 createObjectGroup(group, 0, avoidClasses(clBaseResource,1));
2181}
2182
2183// create patches
2184log("Creating sand patches...");
2185var placer = new ClumpPlacer(30, 0.2, 0.1, 0);
2186var painter = new LayeredPainter([[tSand, tFineSand], tFineSand], [1]);
2187createAreas(placer, [painter, paintClass(clPatch)],
2188 avoidClasses(clPatch, 5),
2189 (mapSize*mapSize)/600
2190);
2191
2192log("Creating dirt patches...");
2193placer = new ClumpPlacer(10, 0.2, 0.1, 0);
2194painter = new TerrainPainter([tSand, tDirt]);
2195createAreas(placer, [painter, paintClass(clPatch)],
2196 avoidClasses(clPatch, 5),
2197 (mapSize*mapSize)/600
2198);
2199
2200// create the oasis
2201log("Creating water...");
2202placer = new ClumpPlacer(1200, 0.6, 0.1, 0, mapSize/2, mapSize/2);
2203painter = new LayeredPainter([[tSand, tForest], tShore, tWaterDeep], [6,1]);
2204elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, -10, 5);
2205createArea(placer, [painter, elevationPainter, paintClass(clForest)], null);
2206
2207// create hills
2208log("Creating level 1 hills...");
2209placer = new ClumpPlacer(150, 0.25, 0.1, 0.3);
2210var terrainPainter = new LayeredPainter(
2211 [tCliff, tSand], // terrains
2212 [1] // widths
2213);
2214var elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1);
2215createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill1)],
2216 avoidClasses(clForest, 2, clPlayer, 0, clHill1, 16),
2217 (mapSize*mapSize)/3800, 100
2218);
2219
2220log("Creating small level 1 hills...");
2221placer = new ClumpPlacer(60, 0.25, 0.1, 0.3);
2222terrainPainter = new LayeredPainter(
2223 [tCliff, tSand], // terrains
2224 [1] // widths
2225);
2226elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1);
2227createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill1)],
2228 avoidClasses(clForest, 2, clPlayer, 0, clHill1, 3),
2229 (mapSize*mapSize)/2800, 100
2230);
2231
2232log("Creating level 2 hills...");
2233placer = new ClumpPlacer(60, 0.2, 0.1, 0.9);
2234terrainPainter = new LayeredPainter(
2235 [tCliff, tSand], // terrains
2236 [1] // widths
2237);
2238elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1);
2239createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill2)],
2240 [avoidClasses(clHill2, 1), new StayInTileClassConstraint(clHill1, 0)],
2241 (mapSize*mapSize)/2800, 200
2242);
2243
2244log("Creating level 3 hills...");
2245placer = new ClumpPlacer(25, 0.2, 0.1, 0.9);
2246terrainPainter = new LayeredPainter(
2247 [tCliff, tSand], // terrains
2248 [1] // widths
2249);
2250elevationPainter = new SmoothElevationPainter(ELEVATION_MODIFY, 16, 1);
2251createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill3)],
2252 [avoidClasses(clHill3, 1), new StayInTileClassConstraint(clHill2, 0)],
2253 (mapSize*mapSize)/9000, 300
2254);
2255
2256// create forests
2257log("Creating forests...");
2258placer = new ClumpPlacer(25, 0.15, 0.1, 0.3);
2259painter = new TerrainPainter([tSand, tForest]);
2260createAreas(placer, [painter, paintClass(clForest)],
2261 avoidClasses(clWater, 0, clPlayer, 1, clForest, 20, clHill1, 0),
2262 (mapSize*mapSize)/4000, 50
2263);
2264
2265// create mines
2266log("Creating mines...");
2267group = new SimpleGroup([new SimpleObject(oMine, 4,6, 0,2)], true, clRock);
2268createObjectGroups(group, 0,
2269 [avoidClasses(clWater, 2, clForest, 2, clPlayer, 0, clRock, 13),
2270 new BorderTileClassConstraint(clHill1, 0, 4)],
2271 (mapSize*mapSize)/4000, 100
2272);
2273
2274// create decorative rocks for hills
2275log("Creating decorative rocks...");
2276group = new SimpleGroup([new SimpleObject(aDecorativeRock, 1,1, 0,0)], true);
2277createObjectGroups(group, undefined,
2278 new BorderTileClassConstraint(clHill1, 0, 3),
2279 (mapSize*mapSize)/2000, 100
2280);
2281
2282// create deer
2283log("Creating deer...");
2284group = new SimpleGroup([new SimpleObject(oDeer, 5,7, 0,4)], true, clFood);
2285createObjectGroups(group, 0,
2286 avoidClasses(clWater, 0, clForest, 0, clPlayer, 0, clHill1, 0, clFood, 25),
2287 (mapSize*mapSize)/5000, 50
2288);
2289
2290// create sheep
2291log("Creating sheep...");
2292group = new SimpleGroup([new SimpleObject(oSheep, 1,3, 0,2)], true, clFood);
2293createObjectGroups(group, 0,
2294 avoidClasses(clWater, 0, clForest, 0, clPlayer, 0, clHill1, 0, clFood, 15),
2295 (mapSize*mapSize)/5000, 50
2296);
2297
2298// create straggler trees
2299log("Creating straggler trees...");
2300group = new SimpleGroup([new SimpleObject(oTree, 1,1, 0,0)], true);
2301createObjectGroups(group, 0,
2302 avoidClasses(clWater, 0, clForest, 0, clHill1, 0, clPlayer, 0),
2303 mapSize*mapSize/1500
2304);
2305
2306// create bushes
2307log("Creating bushes...");
2308group = new SimpleGroup([new SimpleObject(aBush, 2,3, 0,2)]);
2309createObjectGroups(group, undefined,
2310 avoidClasses(clWater, 3, clHill1, 0, clPlayer, 0, clForest, 0),
2311 mapSize*mapSize/1000
2312);
2313
2314// create bushes
2315log("Creating more decorative rocks...");
2316group = new SimpleGroup([new SimpleObject(aDecorativeRock, 1,2, 0,2)]);
2317createObjectGroups(group, undefined,
2318 avoidClasses(clWater, 3, clHill1, 0, clPlayer, 0, clForest, 0),
2319 mapSize*mapSize/1000
2320);
2321
2322var output = SaveMap();
2323
2324var endTime = dateNow();
2325
2326log("Processing time: "+(endTime-startTime)/1000.0+" seconds");