├── .gitignore
├── .htaccess
├── README.md
├── assets
├── Comfortaa-Bold.otf
├── Comfortaa_Bold.json
├── cartographer.ico
└── style.css
├── index.php
├── scripts
├── private
│ ├── reader.php
│ └── writer.php
└── public
│ ├── biome.js
│ ├── helpers.js
│ ├── init.js
│ ├── main.js
│ ├── point.js
│ ├── request.js
│ └── story.js
└── stories
└── stories.json
/.gitignore:
--------------------------------------------------------------------------------
1 | crypt.php
2 | clean.php
3 | filter/
4 | media/
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | RewriteEngine On
2 | RewriteCond %{HTTPS} !=on
3 | RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE]
4 |
5 | DirectoryIndex index.php
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cartographer
2 |
3 | _Catrographer_ is a procedurally generated persistent multiplayer map featuring biomes, a day/night cycle, and cities.
4 |
5 | Players can explore, leave pins marking their own stories for other players to see, and read others' adventures.
6 |
7 | Play it at [v-os/cartographer](http://exp.v-os.ca/cartographer/).
--------------------------------------------------------------------------------
/assets/Comfortaa-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vi-ctorivanov/Cartographer/83648e513f5d605e618a85ba5f354c07b8b8e40a/assets/Comfortaa-Bold.otf
--------------------------------------------------------------------------------
/assets/cartographer.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vi-ctorivanov/Cartographer/83648e513f5d605e618a85ba5f354c07b8b8e40a/assets/cartographer.ico
--------------------------------------------------------------------------------
/assets/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | background-color: #222;
4 | font-family: 'Comfortaa';
5 | overflow: hidden;
6 | }
7 |
8 | ::-moz-selection {
9 | color: #fff;
10 | background: #222;
11 | }
12 |
13 | ::selection {
14 | color: #fff;
15 | background: #222;
16 | }
17 |
18 | #main {
19 | position: absolute;
20 | top: 50%;
21 | left: 50%;
22 | transform: translate(-50%, -50%);
23 | opacity: 0;
24 | }
25 |
26 | #main.ready {
27 | opacity: 1;
28 | transition: opacity 500ms;
29 | }
30 |
31 | #defaultCanvas0 {
32 | z-index: 0;
33 | }
34 |
35 | #uibox {
36 | position: fixed;
37 | width: 100vw;
38 | height: calc(((100vh - 600px) / 2) - 15px);
39 | min-height: 40px;
40 | bottom: 15px;
41 | z-index: 1;
42 | text-align: center;
43 | }
44 |
45 | .ui {
46 | display: inline-block;
47 | text-align: center;
48 | font-weight: 600;
49 | font-size: 32px;
50 | color: #eee;
51 | -webkit-touch-callout: none;
52 | -webkit-user-select: none;
53 | -khtml-user-select: none;
54 | -moz-user-select: none;
55 | -ms-user-select: none;
56 | user-select: none;
57 | }
58 |
59 | #root {
60 | position: absolute;
61 | top: 20px;
62 | left: 20px;
63 | font-size: 14px;
64 | text-decoration: none;
65 | color: #fff;
66 | z-index: 2;
67 | }
68 |
69 | #root:hover {
70 | background-color: #fff;
71 | color: #000;
72 | }
73 |
74 | #northsymbol,
75 | #eastsymbol,
76 | #northtext,
77 | #easttext {
78 | position: absolute;
79 | width: 48px;
80 | color: #fff;
81 | }
82 |
83 | #northtext {
84 | left: 50%;
85 | transform: translateX(calc(-140px - 24px));
86 | }
87 |
88 | #northsymbol {
89 | left: 50%;
90 | transform: translateX(calc(-70px - 24px));
91 | }
92 |
93 | #easttext {
94 | left: 50%;
95 | transform: translateX(calc(70px - 24px));
96 | }
97 |
98 | #eastsymbol {
99 | left: 50%;
100 | transform: translateX(calc(140px - 24px));
101 | }
102 |
103 | #inputBoxHolder {
104 | position: relative;
105 | width: 100%;
106 | height: calc(((100vh - 600px) / 2) - 15px);
107 | min-height: 40px;
108 | top: 15px;
109 | }
110 |
111 | #inputBox {
112 | position: relative;
113 | width: 600px;
114 | height: 40px;
115 | text-align: center;
116 | font-family: 'Comfortaa';
117 | background-color: transparent;
118 | color: #fff;
119 | border: none;
120 | line-height: normal;
121 | font-size: 28px;
122 | top: calc(100% - 40px);
123 | left: 50%;
124 | transform: translateX(-50%);
125 | z-index: 1;
126 | }
127 |
128 | #inputBox:focus {
129 | outline: none;
130 | }
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Cartographer
14 |
15 |
16 |
17 |
18 |
19 |
20 | cartographer
21 |
22 |
23 |
24 |
28 |
29 | 0.0
30 | °lo
31 | 0.0
32 | °la
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/scripts/private/reader.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scripts/private/writer.php:
--------------------------------------------------------------------------------
1 | 40) return;
28 |
29 | include 'clean.php';
30 | $clean = clean($uText);
31 |
32 | //check if time is formatted properly
33 | $noon = substr($uTime, strlen($uTime) - 2, strlen($uTime));
34 | if ($noon !== 'am' && $noon !== 'pm') return;
35 |
36 | $nums = substr($uTime, 0, strlen($uTime) - 2);
37 | $nums = str_replace(':', '', $nums);
38 | $nums = str_replace('-', '', $nums);
39 | $nums = str_replace('/', '', $nums);
40 | $nums = str_replace(' ', '', $nums);
41 | if (!is_numeric($nums)) return;
42 |
43 | //check if x and y are appropriate numbers
44 | if (!is_numeric($uX) || !is_numeric($uY)) return;
45 | if ($uX < 5 && $uX > -5 && $uY < 5 && $uY > -5) return;
46 | if ($uX > 50000 || $uX < -50000 || $uY > 50000 || $uY < -50000) return;
47 |
48 | //create json object
49 | $newObject = (object) [
50 | 'x' => $uX,
51 | 'y' => $uY,
52 | 'text' => $uText,
53 | 'time' => $uTime
54 | ];
55 |
56 | //feed to stories or moderation file
57 | if ($clean) {
58 | if ($liveUpdate) {
59 | //parse json
60 | $c = file_get_contents('../../stories/stories.json');
61 | $j = json_decode($c, TRUE);
62 | array_push($j['stories'], $newObject);
63 | $j = json_encode($j);
64 |
65 | //append to stories
66 | $f = fopen('../../stories/stories.json','w');
67 | fwrite($f, $j);
68 | fclose($f);
69 | } else {
70 | //append to moderation file
71 | $c = file_get_contents('filter/moderation.txt');
72 | $write = json_encode($newObject, TRUE);
73 | $f = fopen('filter/stories/moderation.txt', 'w');
74 | fwrite($f, $c . $write . ",\n");
75 | fclose($f);
76 | }
77 | }
78 |
79 | echo 'pin processed';
80 | return;
81 | ?>
--------------------------------------------------------------------------------
/scripts/public/biome.js:
--------------------------------------------------------------------------------
1 | function Biome (x, y, pc, vc, cc, wc, wh, hm, ch, lh) {
2 | this.x = x;
3 | this.y = y;
4 |
5 | this.truePeakColor = pc;
6 | this.peakColor = this.truePeakColor;
7 | this.trueValleyColor = vc;
8 | this.valleyColor = this.trueValleyColor;
9 | this.trueCityColor = cc;
10 | this.cityColor = this.trueCityColor;
11 | this.trueWaterColor = wc;
12 | this.waterColor = this.trueWaterColor;
13 |
14 | this.waterHeight = wh;
15 | this.heightMul = hm;
16 | this.cityChance = ch;
17 | this.cloudChance = lh;
18 | this.currentClouds;
19 |
20 | this.clouds = function(c) {
21 | this.currentClouds = p.map(p.noise(c), 0, 1, 0, this.cloudChance);
22 | }
23 | }
24 |
25 | function blendBiomes(b1, b2, q) {
26 | var newPeak = colorLerp(b1.peakColor, b2.peakColor, q);
27 | var newValley = colorLerp(b1.valleyColor, b2.valleyColor, q);
28 | var newCity = colorLerp(b1.cityColor, b2.cityColor, q);
29 | var newWater = colorLerp(b1.waterColor, b2.waterColor, q);
30 | var newWaterHeight = p.lerp(b1.waterHeight, b2.waterHeight, q);
31 | var newHeight = p.lerp(b1.heightMul, b2.heightMul, q);
32 | var newCityChance = 0;
33 | var newCloudChance = p.lerp(b1.cloudChance, b2.cloudChance, q);
34 | return new Biome(0, 0, newPeak, newValley, newCity, newWater, newWaterHeight, newHeight, newCityChance, newCloudChance);
35 | }
36 |
37 | function biomeSunset(biome, s) {
38 | biome.peakColor = p5.Vector.lerp(biome.truePeakColor, p5.Vector.div(biome.truePeakColor, nightDarkness), s);
39 | biome.valleyColor = p5.Vector.lerp(biome.trueValleyColor, p5.Vector.div(biome.trueValleyColor, nightDarkness), s);
40 | biome.waterColor = p5.Vector.lerp(biome.trueWaterColor, p5.Vector.div(biome.trueWaterColor, nightDarkness), s);
41 | biome.cityColor = p5.Vector.lerp(biome.trueCityColor, p5.Vector.mult(biome.trueCityColor, cityNightBrightness), s);
42 | }
43 |
44 | function biomeSunrise(biome, s) {
45 | biome.peakColor = p5.Vector.lerp(p5.Vector.div(biome.truePeakColor, nightDarkness), biome.truePeakColor, s);
46 | biome.valleyColor = p5.Vector.lerp(p5.Vector.div(biome.trueValleyColor, nightDarkness), biome.trueValleyColor, s);
47 | biome.waterColor = p5.Vector.lerp(p5.Vector.div(biome.trueWaterColor, nightDarkness), biome.trueWaterColor, s);
48 | biome.cityColor = p5.Vector.lerp(p5.Vector.mult(biome.trueCityColor, cityNightBrightness), biome.trueCityColor, s);
49 | }
--------------------------------------------------------------------------------
/scripts/public/helpers.js:
--------------------------------------------------------------------------------
1 | function easeValue(val, target, ease) {
2 | val += ((target - val) * ease);
3 | return val;
4 | }
5 |
6 | function easeValueVector(val, target, ease) {
7 | var valX = easeValue(val.x, target.x, ease);
8 | var valY = easeValue(val.y, target.y, ease);
9 | var valZ = easeValue(val.z, target.z, ease);;
10 | return p.createVector(valX, valY, valZ);
11 | }
12 |
13 | function colorLerp(v1, v2, q) {
14 | var x = p.lerp(v1.x, v2.x, q);
15 | var y = p.lerp(v1.y, v2.y, q);
16 | var z = p.lerp(v1.z, v2.z, q);
17 | return p.createVector(x, y, z);
18 | }
19 |
20 | function sortAscending(a, b) {
21 | return a - b;
22 | }
23 |
24 | function getCookie(cname) {
25 | var name = cname + "=";
26 | var decodedCookie = decodeURIComponent(document.cookie);
27 | var ca = decodedCookie.split(';');
28 |
29 | for(var i = 0; i ',
47 | '"': '"',
48 | ''': "'",
49 | '’': "’",
50 | '‘': "‘",
51 | '–': "–",
52 | '—': "—",
53 | '…': "…",
54 | '”': '”'
55 | };
56 |
57 | return text.replace(/\&[\w\d\#]{2,5}\;/g, function(m) { return map[m]; });
58 | }
--------------------------------------------------------------------------------
/scripts/public/init.js:
--------------------------------------------------------------------------------
1 | //p5
2 | const s = ( sketch ) => {};
3 | var p = new p5(s);
4 |
5 | p5.disableFriendlyErrors = true;
6 |
7 | var loader = new THREE.FontLoader();
8 | var customFont;
9 | loader.load('assets/Comfortaa_Bold.json', function (font) {
10 | customFont = font;
11 | updateStories();
12 | });
13 |
14 | var storyData;
15 | var inputBox = document.getElementById('inputBox');
16 |
17 | //rendering
18 | var size = 600;
19 |
20 | //touch support
21 | var currentTouch;
22 | var previousTouch;
23 |
24 | //noise
25 | var xOff = 0;
26 | var yOff = 0;
27 | var res = 0.1;
28 |
29 | //movement speed
30 | var speed = 0.15;
31 | var touchDampen = 0.015;
32 | var currentX = 0;
33 | var currentY = 0;
34 | var movementEase = 0.1;
35 |
36 | //points
37 | var pointCount = 30;
38 | var pointGap = pointCount / 7;
39 | var pointSize = 0.8;
40 | var dotDetail = 4;
41 | var points = [];
42 |
43 | //heights
44 | var heightLayers = 10;
45 | var originSize = 5;
46 | var currentWaterHeight;
47 |
48 | //cities
49 | var cityXoff = 1000;
50 | var cityYoff = 5000;
51 | var cityMul = 0.9;
52 | var cityGap = 7;
53 | var floorHeight = 10;
54 | var floors = 2;
55 |
56 | //clouds
57 | var cloudXoff;
58 | var cloudYoff;
59 | var cloudSpeedMax = 0.04;
60 | var cloudMovementX;
61 | var cloudMovementY;
62 | var windChaos = 0.001;
63 | var cloudHeight = 100;
64 | var cloudThreshold;
65 | var cloudiness = 600;
66 | var cloudinessInc = 0.001;
67 |
68 | //time
69 | var clock = -9;
70 | var clockSpeed = 0.015;
71 |
72 | //colors
73 | var cloudColor;
74 | var gridColor;
75 | var storyTextColor;
76 | var storyPinColor;
77 |
78 | var nightDarkness = 1.7;
79 | var cityNightBrightness = 1.7;
80 |
81 | //biome
82 | var forest;
83 | var desert;
84 | var ocean;
85 | var alien;
86 | var biomeSize = 50;
87 | var oceanHeight = -5;
88 | var biomeBlendThreshold = 0.04;
89 |
90 | //grids
91 | var grid;
92 | var gridHeight = 0;
93 | var gridLine = 0.7;
94 |
95 | //stories
96 | var stories = [];
97 | var storyHeight = 70;
98 | var storyScale = 3;
99 | var fontScale = 3.8;
100 | var textDistance = 25;
101 | var timeDistance = 15;
102 | var storyMapSize = 2;
103 | var storyParallax = 25;
104 | var storyData = [];
105 | var storyReady = true;
106 | var liveUpdate;
107 | var updateInterval = 12000;
108 | var postIntervalLimit = 10000;
109 |
110 | //input
111 | var inputBox;
112 | var pinDistance = 1;
113 | var pinOriginDistance = 5;
114 |
115 | //camera
116 | var camHeight = 150;
117 | var camZoom = 120;
118 | var cameraAngle = 30;
119 |
120 | //three
121 | var scene = new THREE.Scene();
122 | var camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
123 |
124 | var renderer = new THREE.WebGLRenderer({alpha: true, antialias: true});
125 | renderer.setSize(size, size);
126 | document.getElementById('canvasParent').appendChild(renderer.domElement);
127 |
128 | function initializeBiomes() {
129 | //x, y, peak, valley, city, water, water height, height, citychance, cloudchance
130 | forest = new Biome(800, 800, p.createVector(0.45, 0.85, 0.45), p.createVector(0.4, 0.4, 0.05), p.createVector(0.45, 0.45, 0), p.createVector(0, 0.6, 0.9), oceanHeight, 40, 0.25, 0.4);
131 | desert = new Biome(120, 120, p.createVector(1, 0.9, 0.45), p.createVector(0.7, 0.5, 0.2), p.createVector(1, 0.6, 0.3), p.createVector(0.4, 0.6, 0.9), oceanHeight + 3, 25, 0.15, 0.15);
132 | ocean = new Biome(920, 920, p.createVector(1, 1, 0.45), p.createVector(1, 0.8, 0.45), p.createVector(0, 0, 0), p.createVector(0.4, 0.6, 0.9), oceanHeight, 2, 0, 0.5);
133 | alien = new Biome(740, 740, p.createVector(1, 0.45, 0.45), p.createVector(0.5, 0.9, 0.8), p.createVector(0.1, 0.6, 0.7), p.createVector(0.5, 0.9, 0.8), oceanHeight, 40, 0.2, 0.4);
134 | }
135 |
136 | function initializeColors() {
137 | gridColor = p.createVector(0.2, 0.2, 0.2);
138 | storyTextColor = p.createVector(1, 1, 1);
139 | storyPinColor = p.createVector(1, 1, 1);
140 | cloudColor = p.createVector(1, 1, 1);
141 | currentCloud = cloudColor;
142 | }
143 |
144 | function initializeCloudNoise() {
145 | p.noiseSeed(500);
146 |
147 | cloudXoff = p.random(50, 500);
148 | cloudYoff = p.random(50, 500);
149 | cloudMovementX = p.random(50, 500);
150 | cloudMovementY = p.random(50, 500);
151 | }
152 |
153 | function generatePoints() {
154 | for (var i = 0; i < pointCount; i++) {
155 | for (var j = 0; j < pointCount; j++) {
156 | p.append(points, new Point(i * pointGap, 0, j * pointGap, pointSize));
157 | }
158 | }
159 | }
160 |
161 | function generateGrid() {
162 | var material = new THREE.LineBasicMaterial();
163 | material.color = new THREE.Color(gridColor.x, gridColor.y, gridColor.z);
164 |
165 | var geometry = new THREE.Geometry();
166 | geometry.vertices.push(
167 | new THREE.Vector3(points[points.length - 1].pos.x, gridHeight, points[points.length - 1].pos.z),
168 | new THREE.Vector3(points[points.length - 1].pos.x, gridHeight, points[0].pos.z),
169 | new THREE.Vector3(points[0].pos.x, gridHeight, points[0].pos.z),
170 | new THREE.Vector3(points[0].pos.x, gridHeight, points[points.length - 1].pos.z),
171 | new THREE.Vector3(points[points.length - 1].pos.x, gridHeight, points[points.length - 1].pos.z)
172 | );
173 |
174 | var line = new THREE.Line(geometry, material);
175 | scene.add(line);
176 | }
177 |
178 | initializeBiomes();
179 | initializeColors();
180 | initializeCloudNoise();
181 | generatePoints();
182 | generateGrid();
--------------------------------------------------------------------------------
/scripts/public/main.js:
--------------------------------------------------------------------------------
1 | function animate() {
2 | setupCamera();
3 |
4 | manageClouds();
5 | passTime();
6 |
7 | moveMap();
8 | updatePoints();
9 |
10 | for (var i = 0; i < stories.length; i++) {
11 | stories[i].show();
12 | }
13 |
14 | requestAnimationFrame(animate);
15 | renderer.render(scene, camera);
16 | }
17 | animate();
18 |
19 | function setupCamera() {
20 | camera.lookAt(new THREE.Vector3(0, cameraAngle, 0));
21 |
22 | camera.position.x = -camZoom;
23 | camera.position.z = camZoom;
24 | camera.position.y = camHeight;
25 | }
26 |
27 | //disable touch-based screen dragging
28 | function touchMoved() {
29 | return false;
30 | }
31 |
32 | function updatePoints() {
33 | var x = xOff;
34 | var cityX = cityXoff;
35 | var cloudX = cloudXoff;
36 | var forestX = forest.x;
37 | var desertX = desert.x;
38 | var oceanX = ocean.x;
39 | var alienX = alien.x;
40 |
41 | for (var i = 0; i < pointCount; i++) {
42 | x += res;
43 | cityX += res;
44 | cloudX += res;
45 | forestX += res / biomeSize;
46 | desertX += res / biomeSize;
47 | oceanX += res / biomeSize;
48 | alienX += res / biomeSize;
49 |
50 | var y = yOff;
51 | var cityY = cityYoff;
52 | var cloudY = cloudYoff;
53 | var forestY = forest.y;
54 | var desertY = desert.y;
55 | var oceanY = ocean.y;
56 | var alienY = alien.y;
57 |
58 | for (var j = 0; j < pointCount; j++) {
59 | y += res;
60 | cityY += res;
61 | cloudY += res;
62 | forestY += res / biomeSize;
63 | desertY += res / biomeSize;
64 | oceanY += res / biomeSize;
65 | alienY += res / biomeSize;
66 |
67 | var index = i + (j * pointCount);
68 |
69 | //manage biomes
70 | var forestNoise = p.noise(forestX, forestY);
71 | var desertNoise = p.noise(desertX, desertY);
72 | var oceanNoise = p.noise(oceanX, oceanY);
73 | var alienNoise = p.noise(alienX, alienY);
74 |
75 | //find chosen biome(s)
76 | var biomeVal = [forestNoise, desertNoise, oceanNoise, alienNoise];
77 | biomeVal.sort(sortAscending);
78 |
79 | var chosenBiomeValue = biomeVal[biomeVal.length - 1];
80 | var secondaryBiomeValue = biomeVal[biomeVal.length - 2];
81 | var tirtiaryBiomeValue = biomeVal[biomeVal.length - 3];
82 | var chosenBiome;
83 | var secondaryBiome;
84 | var tirtiaryBiome;
85 |
86 | if (chosenBiomeValue == forestNoise) chosenBiome = forest;
87 | else if (chosenBiomeValue == desertNoise) chosenBiome = desert;
88 | else if (chosenBiomeValue == oceanNoise) chosenBiome = ocean;
89 | else if (chosenBiomeValue == alienNoise) chosenBiome = alien;
90 |
91 | if (secondaryBiomeValue == forestNoise) secondaryBiome = forest;
92 | else if (secondaryBiomeValue == desertNoise) secondaryBiome = desert;
93 | else if (secondaryBiomeValue == oceanNoise) secondaryBiome = ocean;
94 | else if (secondaryBiomeValue == alienNoise) secondaryBiome = alien;
95 |
96 | if (tirtiaryBiomeValue == forestNoise) tirtiaryBiome = forest;
97 | else if (tirtiaryBiomeValue == desertNoise) tirtiaryBiome = desert;
98 | else if (tirtiaryBiomeValue == oceanNoise) tirtiaryBiome = ocean;
99 | else if (tirtiaryBiomeValue == alienNoise) tirtiaryBiome = alien;
100 |
101 | //blend biomes
102 | var resultBiome;
103 | var biomeDifference = chosenBiomeValue - secondaryBiomeValue;
104 |
105 | if (p.abs(biomeDifference) < biomeBlendThreshold) resultBiome = blendBiomes(chosenBiome, secondaryBiome, p.map(biomeDifference, -biomeBlendThreshold, biomeBlendThreshold, 1, 0, true));
106 | else resultBiome = chosenBiome;
107 |
108 | points[index].nature(resultBiome.peakColor, resultBiome.valleyColor, resultBiome.cityColor, resultBiome.waterColor, resultBiome.heightMul);
109 |
110 | //make terrain
111 | var terrainNoise = (p.noise(x, y) * -resultBiome.heightMul);
112 | points[index].isBuilding = false;
113 | points[index].isCloud = false;
114 |
115 | //add cities
116 | var newNoise = terrainNoise;
117 | if (p.noise(cityX, cityY) < resultBiome.cityChance) {
118 | for (var k = 0; k < heightLayers; k++) {
119 | if (terrainNoise > cityGap * k) newNoise = (cityGap * cityMul) * k;
120 | }
121 | points[index].isBuilding = true;
122 | }
123 | if (points[index].isBuilding) terrainNoise = newNoise;
124 |
125 | //add clouds
126 | if (p.noise(cloudX, cloudY) < resultBiome.currentClouds) {
127 | terrainNoise = -cloudHeight;
128 | points[index].isBuilding = false;
129 | points[index].isCloud = true;
130 | }
131 |
132 | //make origin
133 | var mDistance = p.dist(0, 0, xOff, yOff);
134 | if (mDistance < originSize) {
135 | points[index].isBuilding = false;
136 | points[index].isCloud = false;
137 |
138 | currentWaterHeight = p.lerp(resultBiome.waterHeight, gridHeight, p.map(mDistance, 0, originSize, 1, 0));
139 | points[index].update(p.lerp(terrainNoise, gridHeight - 1, p.map(mDistance, 0, originSize, 1, 0)));
140 | } else {
141 | currentWaterHeight = resultBiome.waterHeight;
142 | points[index].update(terrainNoise);
143 | }
144 | }
145 | }
146 | }
147 |
148 | function moveMap() {
149 | var xMovement = false;
150 | var yMovement = false;
151 |
152 | if (p.keyIsDown(p.LEFT_ARROW)) {
153 | currentX = easeValue(currentX, -speed, movementEase);
154 | xMovement = true;
155 | }
156 |
157 | if (p.keyIsDown(p.RIGHT_ARROW)) {
158 | currentX = easeValue(currentX, speed, movementEase);
159 | xMovement = true;
160 | }
161 |
162 | if (p.keyIsDown(p.DOWN_ARROW)) {
163 | currentY = easeValue(currentY, -speed, movementEase);
164 | yMovement = true;
165 | }
166 |
167 | if (p.keyIsDown(p.UP_ARROW)) {
168 | currentY = easeValue(currentY, speed, movementEase);
169 | yMovement = true;
170 | }
171 |
172 | //touch support
173 | if (p.touches[0]) {
174 | if (currentTouch) previousTouch = p.createVector(currentTouch.x, currentTouch.y);
175 | else previousTouch = p.createVector(p.touches[0].x, p.touches[0].y);
176 |
177 | currentTouch = p.createVector(p.touches[0].x, p.touches[0].y);
178 |
179 | if (previousTouch.equals(currentTouch)) {
180 | //
181 | } else {
182 | var horz = (previousTouch.x - currentTouch.x) * touchDampen;
183 | var vert = (currentTouch.y - previousTouch.y) * touchDampen;
184 |
185 | currentX = easeValue(currentX, horz - vert, movementEase);
186 | xMovement = true;
187 | currentY = easeValue(currentY, vert + horz, movementEase);
188 | yMovement = true;
189 | }
190 | } else if (currentTouch) previousTouch = p.createVector(currentTouch.x, currentTouch.y);
191 |
192 | if (!xMovement) currentX = easeValue(currentX, 0, movementEase);
193 | if (!yMovement) currentY = easeValue(currentY, 0, movementEase);
194 |
195 | var movement = p.createVector(currentX, currentY);
196 | movement.limit(speed);
197 |
198 | xOff += movement.x;
199 | yOff += movement.y;
200 | cityXoff += movement.x;
201 | cityYoff += movement.y;
202 | cloudXoff += movement.x;
203 | cloudYoff += movement.y;
204 |
205 | forest.x += movement.x / biomeSize;
206 | forest.y += movement.y / biomeSize;
207 | ocean.x += movement.x / biomeSize;
208 | ocean.y += movement.y / biomeSize;
209 | desert.x += movement.x / biomeSize;
210 | desert.y += movement.y / biomeSize;
211 | alien.x += movement.x / biomeSize;
212 | alien.y += movement.y / biomeSize;
213 |
214 | p.select('#easttext').html(p.nf(xOff / 10, 0, 1));
215 | p.select('#northtext').html(p.nf(yOff / 10, 0, 1));
216 | }
217 |
218 | //clear touch data on touch end
219 | window.addEventListener("touchend", touchEnd, false);
220 |
221 | function touchEnd(evt) {
222 | currentTouch = null;
223 | previousTouch = null;
224 | }
225 |
226 | function handleInput(e) {
227 | if (e.keyCode == 13) {
228 | e.preventDefault();
229 | var input = inputBox.value;
230 | if (input == '') return;
231 |
232 | inputBox.value = null;
233 |
234 | //check for nearby pins
235 | var near = false;
236 | for (var i = 0; i < stories.length; i++) {
237 | if (p.dist(stories[i].pos.x, stories[i].pos.y, xOff, yOff) < pinDistance) near = true;
238 | }
239 |
240 | //create new pin - server save
241 | if (p.dist(xOff, yOff, 0, 0) > pinOriginDistance) {
242 | if (!near) {
243 | if (storyReady) {
244 | var h = p.hour();
245 | var m = p.minute();
246 |
247 | if (m < 10) m = '0' + m;
248 | if (h == 12) h = h + ':' + m + 'pm';
249 | else if (h > 12) h = (h - 12) + ':' + m + 'pm';
250 | else h = h + ':' + m + 'am';
251 |
252 | var time = p.day() + '/' + p.month() + '/' + p.year() + ' - ' + h;
253 |
254 | storyReady = false;
255 |
256 | //send new pin to server, and create local copy if live updates are off
257 | issueRequest(input, time, xOff, yOff);
258 | if (!liveUpdate) p.append(stories, new Story(xOff, yOff, input, time));
259 |
260 | setTimeout(function() {
261 | storyReady = true;
262 | }, postIntervalLimit);
263 | } else {
264 | inputBox.placeholder = 'multiple pins placed too quickly';
265 | setTimeout(function() {
266 | inputBox.placeholder = 'pin';
267 | }, 3000);
268 | }
269 | } else {
270 | inputBox.placeholder = 'pin too close to nearby pins';
271 | setTimeout(function() {
272 | inputBox.placeholder = 'pin';
273 | }, 3000);
274 | }
275 | } else {
276 | inputBox.placeholder = 'pin too close to origin';
277 | setTimeout(function() {
278 | inputBox.placeholder = 'pin';
279 | }, 3000);
280 | }
281 | }
282 | }
283 |
284 | function manageClouds() {
285 | cloudiness += cloudinessInc;
286 |
287 | forest.clouds(cloudiness);
288 | desert.clouds(cloudiness);
289 | ocean.clouds(cloudiness);
290 | alien.clouds(cloudiness);
291 |
292 | cloudMovementX += windChaos;
293 | cloudMovementY += windChaos;
294 | cloudXoff += p.map(p.noise(cloudMovementX), 0, 1, -cloudSpeedMax, cloudSpeedMax);
295 | cloudYoff += p.map(p.noise(cloudMovementY), 0, 1, -cloudSpeedMax, cloudSpeedMax);
296 | }
297 |
298 | function passTime() {
299 | clock += clockSpeed;
300 | if (clock > 12) clock = -12;
301 |
302 | if (clock > 6 && clock < 8) {
303 | var sunset = p.map(clock, 6, 8, 0, 1);
304 | biomeSunset(forest, sunset);
305 | biomeSunset(desert, sunset);
306 | biomeSunset(ocean, sunset);
307 | biomeSunset(alien, sunset);
308 | currentCloud = p5.Vector.lerp(cloudColor, p5.Vector.div(cloudColor, nightDarkness), sunset);
309 |
310 | } else if (clock < -6 && clock > -8) {
311 | var sunrise = p.map(clock, -8, -6, 0, 1);
312 | biomeSunrise(forest, sunrise);
313 | biomeSunrise(desert, sunrise);
314 | biomeSunrise(ocean, sunrise);
315 | biomeSunrise(alien, sunrise);
316 | currentCloud = p5.Vector.lerp(p5.Vector.div(cloudColor, nightDarkness), cloudColor, sunrise);
317 | }
318 | }
319 |
320 | //update stories continuously
321 | setInterval(function() {
322 | if (liveUpdate) updateStories();
323 | }, updateInterval);
--------------------------------------------------------------------------------
/scripts/public/point.js:
--------------------------------------------------------------------------------
1 | function Point (x, y, z, pointSize) {
2 | this.geometry = new THREE.CircleGeometry(pointSize, dotDetail);
3 | this.material = new THREE.MeshBasicMaterial();
4 | this.dot = new THREE.Mesh(this.geometry, this.material);
5 |
6 | //set position so that it is centered on world origin
7 | this.pos = p.createVector(x - ((pointCount * pointGap) / 2) + pointGap, y, z - ((pointCount * pointGap) / 2));
8 | this.dot.position.x = this.pos.x;
9 | this.dot.position.y = this.pos.y;
10 | this.dot.position.z = this.pos.z;
11 |
12 | scene.add(this.dot);
13 |
14 | this.floorDots = [];
15 |
16 | for (var i = 0; i < floors; i++) {
17 | var g = new THREE.CircleGeometry(pointSize, dotDetail);
18 | var m = new THREE.MeshBasicMaterial();
19 | var s = new THREE.Mesh(g, m);
20 | s.position.x = this.pos.x;
21 | s.position.z = this.pos.z;
22 | s.lookAt(camera.position);
23 | p.append(this.floorDots, s);
24 | }
25 |
26 | this.isBuilding = false;
27 | this.isCloud = false;
28 |
29 | this.peakColor;
30 | this.valleyColor;
31 | this.cityColor;
32 | this.waterColor;
33 | this.heightmul;
34 |
35 | this.nature = function(p, v, c, w, h) {
36 | this.peakColor = p;
37 | this.valleyColor = v;
38 | this.cityColor = c;
39 | this.waterColor = w;
40 | this.heightMul = h;
41 | }
42 |
43 | this.update = function(newY) {
44 | this.pos.y = newY;
45 |
46 | if (this.pos.y > currentWaterHeight) this.pos.y = currentWaterHeight;
47 |
48 | //get dot color
49 | if (this.isCloud) {
50 | this.dot.material.color = new THREE.Color(currentCloud.x, currentCloud.y, currentCloud.z);
51 | } else if (this.pos.y == currentWaterHeight) {
52 | this.dot.material.color = new THREE.Color(this.waterColor.x, this.waterColor.y, this.waterColor.z);
53 | } else if (this.isBuilding) {
54 | this.dot.material.color = new THREE.Color(this.cityColor.x, this.cityColor.y, this.cityColor.z);
55 | } else {
56 | var colorR = p.map(this.pos.y, gridHeight - this.heightMul * 0.8, gridHeight - this.heightMul * 0.1, this.peakColor.x, this.valleyColor.x, true);
57 | var colorG = p.map(this.pos.y, gridHeight - this.heightMul * 0.8, gridHeight - this.heightMul * 0.1, this.peakColor.y, this.valleyColor.y, true);
58 | var colorB = p.map(this.pos.y, gridHeight - this.heightMul * 0.8, gridHeight - this.heightMul * 0.1, this.peakColor.z, this.valleyColor.z, true);
59 | this.dot.material.color = new THREE.Color(colorR, colorG, colorB);
60 | }
61 |
62 | //translate height
63 | this.dot.position.y = -this.pos.y;
64 | this.dot.lookAt(camera.position);
65 |
66 | //add floors
67 | if (this.isBuilding) {
68 | for (var i = 0; i < floors; i++) {
69 | this.floorDots[i].position.y = -this.pos.y + ((i + 1) * floorHeight);
70 | this.floorDots[i].material.color = new THREE.Color(this.cityColor.x, this.cityColor.y, this.cityColor.z);
71 | this.floorDots[i].lookAt(camera.position);
72 | scene.add(this.floorDots[i]);
73 | }
74 | } else {
75 | for (var i = 0; i < floors; i++) {
76 | if (this.floorDots[i].parent === scene) scene.remove(this.floorDots[i]);
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/scripts/public/request.js:
--------------------------------------------------------------------------------
1 | var readerPath = 'https://exp.v-os.ca/cartographer/scripts/private/reader.php';
2 |
3 | function updateStories() {
4 | var xhr = new XMLHttpRequest();
5 | xhr.open('POST', readerPath, true);
6 | xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
7 | xhr.onload = function() {
8 | if (xhr.status === 200) {
9 | //update stories
10 | var newStories = xhr.responseText;
11 | storyData = JSON.parse(newStories);
12 |
13 | //add new stories to list
14 | for (var i = stories.length; i < storyData['stories'].length; i++) {
15 | var currentStory = storyData['stories'][i];
16 | p.append(stories, new Story(currentStory['x'], currentStory['y'], currentStory['text'], currentStory['time']));
17 | }
18 | if (document.getElementById('main').className != 'ready') document.getElementById('main').className = 'ready';
19 | }
20 | else {
21 | //handle error
22 | console.log('Had trouble loading stories during this ping.');
23 | }
24 | };
25 | xhr.send(encodeURI('request=text' + '&r=' + Math.random(0, 100000)));
26 | }
27 |
28 | var writerPath = 'https://exp.v-os.ca/cartographer/scripts/private/writer.php';
29 |
30 | function issueRequest(text, time, x, y) {
31 | var xhr = new XMLHttpRequest();
32 | xhr.open('POST', writerPath, true);
33 | xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
34 | xhr.onload = function() {
35 | if (xhr.status === 200) {
36 | //handle response
37 | console.log(xhr.responseText);
38 | if (liveUpdate) updateStories();
39 | }
40 | else {
41 | //handle error
42 | console.log('Did not recieve reply.');
43 | }
44 | };
45 | var k = document.getElementById('k').className;
46 | var t = getCookie('t');
47 | xhr.send(encodeURI('k=' + k + '&t=' + t + '&text=' + text + '&time=' + time + '&x=' + x + '&y=' + y));
48 | }
--------------------------------------------------------------------------------
/scripts/public/story.js:
--------------------------------------------------------------------------------
1 | function Story (x, y, storyText, time) {
2 | this.pos = p.createVector(x, y);
3 | this.loaded = false;
4 | this.time = time;
5 |
6 | var geometry = new THREE.ConeGeometry(storyScale, storyScale * 2.2, 6);
7 | var material = new THREE.MeshBasicMaterial();
8 | material.color = new THREE.Color(storyPinColor.x, storyPinColor.y, storyPinColor.z);
9 | this.cone = new THREE.Mesh(geometry, material);
10 | this.cone.rotation.x = p.PI;
11 | this.text;
12 | this.timeText;
13 |
14 | this.show = function() {
15 | if ((p.abs(xOff - this.pos.x) < storyMapSize) && (p.abs(yOff - this.pos.y) < storyMapSize)) {
16 | if (!this.loaded) {
17 | this.createText();
18 | this.loaded = true;
19 | }
20 |
21 | var distX = xOff - this.pos.x;
22 | var distY = yOff - this.pos.y;
23 |
24 | this.cone.position.x = -distY * storyParallax;
25 | this.cone.position.y = storyHeight;
26 | this.cone.position.z = -distX * storyParallax;
27 |
28 | this.text.position.x = this.cone.position.x;
29 | this.text.position.y = this.cone.position.y + textDistance;
30 | this.text.position.z = this.cone.position.z;
31 | this.text.lookAt(camera.position);
32 |
33 | this.timeText.position.x = this.cone.position.x;
34 | this.timeText.position.y = this.cone.position.y + timeDistance;
35 | this.timeText.position.z = this.cone.position.z;
36 | this.timeText.lookAt(camera.position);
37 |
38 | if (this.cone.parent !== scene) {
39 | scene.add(this.cone);
40 | scene.add(this.text);
41 | scene.add(this.timeText);
42 | }
43 | } else if (this.cone.parent === scene) {
44 | scene.remove(this.cone);
45 | scene.remove(this.text);
46 | scene.remove(this.timeText);
47 | }
48 | }
49 |
50 | this.createText = function() {
51 | var textGeometry = new THREE.TextGeometry(htmlspecialchars_decode(storyText), {
52 | font: customFont,
53 | size: fontScale,
54 | height: 0,
55 | curveSegments: 3,
56 | bevelEnabled: false
57 | });
58 | textGeometry.center();
59 |
60 | var timeGeometry = new THREE.TextGeometry(time, {
61 | font: customFont,
62 | size: fontScale,
63 | height: 0,
64 | curveSegments: 3,
65 | bevelEnabled: false
66 | });
67 | timeGeometry.center();
68 |
69 | var textMaterial = new THREE.MeshBasicMaterial();
70 | textMaterial.color = new THREE.Color(storyTextColor.x, storyTextColor.y, storyTextColor.z);
71 | this.text = new THREE.Mesh(textGeometry, textMaterial);
72 | this.text.rotation.y = -p.PI / 4;
73 | this.timeText = new THREE.Mesh(timeGeometry, textMaterial);
74 | this.timeText.rotation.y = -p.PI / 4;
75 | }
76 | }
--------------------------------------------------------------------------------
/stories/stories.json:
--------------------------------------------------------------------------------
1 | {
2 | "stories": [
3 | {
4 | "x": 0,
5 | "y": 0,
6 | "text": "The origin.",
7 | "time": "0/0/0 - The start of time."
8 | }
9 | ]
10 | }
--------------------------------------------------------------------------------