├── .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 |
25 | 26 |
27 |
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 | } --------------------------------------------------------------------------------