File size: 12,229 Bytes
eb8c123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
// Global Three.js variables
let scene, camera, renderer, terrainMesh, controls;
let terrainSize = 64;
let isAnimating = false;
let animationId = null;

// Initialize the 3D scene
function initScene() {
    const canvas = document.getElementById('terrain-canvas');
    
    // Scene setup
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1a202c);
    
    // Camera setup
    camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 1000);
    camera.position.set(terrainSize * 0.7, terrainSize * 0.7, terrainSize * 0.7);
    camera.lookAt(0, 0, 0);
    
    // Renderer setup
    renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
    renderer.setSize(canvas.clientWidth, canvas.clientHeight);
    
    // Lighting
    const ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);
    
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);
    
    // Orbit controls
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.25;
    
    // Handle window resize
    window.addEventListener('resize', onWindowResize);
}

// Generate terrain using specified algorithm
function generateTerrain(algorithm) {
    // Remove existing terrain if any
    if (terrainMesh) {
        scene.remove(terrainMesh);
        terrainMesh.geometry.dispose();
        terrainMesh.material.dispose();
    }
    
    // Get parameters from sliders
    terrainSize = parseInt(document.getElementById('size-slider').value);
    const roughness = parseFloat(document.getElementById('roughness-slider').value);
    const heightScale = parseInt(document.getElementById('height-slider').value);
    const waterLevel = parseInt(document.getElementById('water-slider').value) / 100;
    
    // Generate heightmap
    let heightmap = generateHeightmap(algorithm, terrainSize, roughness, heightScale);
    
    // Create geometry from heightmap
    const geometry = createGeometryFromHeightmap(heightmap, terrainSize, heightScale);
    
    // Create material with water effect
    const material = createTerrainMaterial(heightmap, terrainSize, heightScale, waterLevel);
    
    // Create mesh
    terrainMesh = new THREE.Mesh(geometry, material);
    scene.add(terrainMesh);
    
    // Update camera position based on terrain size
    camera.position.set(terrainSize * 0.7, terrainSize * 0.7, terrainSize * 0.7);
    camera.lookAt(0, 0, 0);
    controls.update();
    
    // Start animation if not already running
    if (!isAnimating) {
        animate();
    }
    
    // Update 2D previews
    updatePreviews(heightmap, algorithm);
}

// Generate heightmap using specified algorithm
function generateHeightmap(algorithm, size, roughness, heightScale) {
    const heightmap = new Array(size * size).fill(0);
    
    // Simplified implementations for demo purposes
    switch(algorithm) {
        case 'diamond-square':
            // Implement diamond-square algorithm
            diamondSquare(heightmap, size, roughness);
            break;
            
        case 'perlin':
            // Implement Perlin noise
            for (let y = 0; y < size; y++) {
                for (let x = 0; x < size; x++) {
                    const value = noise.perlin2(x * roughness / 10, y * roughness / 10);
                    heightmap[y * size + x] = (value + 1) * 0.5 * heightScale;
                }
            }
            break;
            
        case 'simplex':
            // Implement Simplex noise
            for (let y = 0; y < size; y++) {
                for (let x = 0; x < size; x++) {
                    const value = noise.simplex2(x * roughness / 10, y * roughness / 10);
                    heightmap[y * size + x] = (value + 1) * 0.5 * heightScale;
                }
            }
            break;
            
        default:
            console.error('Unknown algorithm:', algorithm);
    }
    
    return heightmap;
}

// Simplified Diamond-Square implementation
function diamondSquare(heightmap, size, roughness) {
    // Initialize corners
    heightmap[0] = Math.random() * roughness;
    heightmap[size - 1] = Math.random() * roughness;
    heightmap[size * (size - 1)] = Math.random() * roughness;
    heightmap[size * size - 1] = Math.random() * roughness;
    
    let step = size - 1;
    let scale = roughness;
    
    while (step > 1) {
        // Diamond step
        for (let y = 0; y < size - 1; y += step) {
            for (let x = 0; x < size - 1; x += step) {
                const avg = (
                    heightmap[y * size + x] +
                    heightmap[y * size + (x + step)] +
                    heightmap[(y + step) * size + x] +
                    heightmap[(y + step) * size + (x + step)]
                ) / 4;
                
                const centerX = x + step / 2;
                const centerY = y + step / 2;
                const centerIndex = centerY * size + centerX;
                heightmap[centerIndex] = avg + (Math.random() * 2 - 1) * scale;
            }
        }
        
        // Square step
        for (let y = 0; y < size; y += step / 2) {
            for (let x = (y + step / 2) % step; x < size; x += step) {
                let sum = 0;
                let count = 0;
                
                // Top neighbor
                if (y - step / 2 >= 0) {
                    sum += heightmap[(y - step / 2) * size + x];
                    count++;
                }
                
                // Bottom neighbor
                if (y + step / 2 < size) {
                    sum += heightmap[(y + step / 2) * size + x];
                    count++;
                }
                
                // Left neighbor
                if (x - step / 2 >= 0) {
                    sum += heightmap[y * size + (x - step / 2)];
                    count++;
                }
                
                // Right neighbor
                if (x + step / 2 < size) {
                    sum += heightmap[y * size + (x + step / 2)];
                    count++;
                }
                
                if (count > 0) {
                    heightmap[y * size + x] = sum / count + (Math.random() * 2 - 1) * scale;
                }
            }
        }
        
        step = Math.floor(step / 2);
        scale *= roughness;
    }
}

// Create Three.js geometry from heightmap
function createGeometryFromHeightmap(heightmap, size, heightScale) {
    const geometry = new THREE.PlaneGeometry(size, size, size - 1, size - 1);
    
    // Apply heightmap to vertices
    for (let i = 0; i < geometry.attributes.position.count; i++) {
        const x = i % size;
        const y = Math.floor(i / size);
        geometry.attributes.position.setZ(i, heightmap[y * size + x]);
    }
    
    geometry.rotateX(-Math.PI / 2);
    geometry.computeVertexNormals();
    
    return geometry;
}

// Create terrain material with water effect
function createTerrainMaterial(heightmap, size, heightScale, waterLevel) {
    // Find min and max heights
    let minHeight = Infinity;
    let maxHeight = -Infinity;
    
    for (const height of heightmap) {
        if (height < minHeight) minHeight = height;
        if (height > maxHeight) maxHeight = height;
    }
    
    const waterHeight = minHeight + (maxHeight - minHeight) * waterLevel;
    
    // Create color gradient
    const texture = new THREE.CanvasTexture(createTerrainTexture(size, minHeight, maxHeight, waterHeight));
    
    return new THREE.MeshStandardMaterial({
        map: texture,
        metalness: 0.1,
        roughness: 0.7,
        flatShading: false
    });
}

// Create terrain texture with elevation colors
function createTerrainTexture(size, minHeight, maxHeight, waterHeight) {
    const canvas = document.createElement('canvas');
    canvas.width = size;
    canvas.height = size;
    const ctx = canvas.getContext('2d');
    
    // Draw gradient based on height
    const imageData = ctx.createImageData(size, size);
    
    for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
            const idx = (y * size + x) * 4;
            const height = heightmap[y * size + x];
            
            // Water
            if (height <= waterHeight) {
                const depth = (waterHeight - height) / (waterHeight - minHeight);
                imageData.data[idx] = 30 + 50 * depth; // R
                imageData.data[idx + 1] = 80 + 100 * depth; // G
                imageData.data[idx + 2] = 150 + 100 * depth; // B
            }
            // Beach/Sand
            else if (height <= waterHeight + (maxHeight - waterHeight) * 0.05) {
                imageData.data[idx] = 210; // R
                imageData.data[idx + 1] = 190; // G
                imageData.data[idx + 2] = 140; // B
            }
            // Grass
            else if (height <= waterHeight + (maxHeight - waterHeight) * 0.3) {
                const t = (height - waterHeight) / ((maxHeight - waterHeight) * 0.3);
                imageData.data[idx] = 50 + 80 * t; // R
                imageData.data[idx + 1] = 100 + 80 * t; // G
                imageData.data[idx + 2] = 50 + 30 * t; // B
            }
            // Rock
            else if (height <= waterHeight + (maxHeight - waterHeight) * 0.7) {
                const t = (height - waterHeight - (maxHeight - waterHeight) * 0.3) / ((maxHeight - waterHeight) * 0.4);
                imageData.data[idx] = 100 + 50 * t; // R
                imageData.data[idx + 1] = 80 + 20 * t; // G
                imageData.data[idx + 2] = 60 + 20 * t; // B
            }
            // Snow
            else {
                const t = (height - waterHeight - (maxHeight - waterHeight) * 0.7) / ((maxHeight - waterHeight) * 0.3);
                imageData.data[idx] = 180 + 75 * t; // R
                imageData.data[idx + 1] = 190 + 65 * t; // G
                imageData.data[idx + 2] = 200 + 55 * t; // B
            }
            
            imageData.data[idx + 3] = 255; // Alpha
        }
    }
    
    ctx.putImageData(imageData, 0, 0);
    return canvas;
}

// Update 2D preview canvases
function updatePreviews(heightmap, algorithm) {
    const size = Math.min(128, terrainSize);
    const previewCanvas = document.getElementById(`${algorithm}-canvas`);
    const ctx = previewCanvas.getContext('2d');
    
    // Scale heightmap to fit preview
    const step = Math.floor(terrainSize / size);
    const imageData = ctx.createImageData(size, size);
    
    for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
            const idx = (y * size + x) * 4;
            const height = heightmap[y * step * terrainSize + x * step];
            const normalizedHeight = Math.floor((height / Math.max(...heightmap)) * 255);
            
            imageData.data[idx] = normalizedHeight; // R
            imageData.data[idx + 1] = normalizedHeight; // G
            imageData.data[idx + 2] = normalizedHeight; // B
            imageData.data[idx + 3] = 255; // Alpha
        }
    }
    
    ctx.putImageData(imageData, 0, 0);
}

// Animation loop
function animate() {
    isAnimating = true;
    animationId = requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
}

// Handle window resize
function onWindowResize() {
    const canvas = document.getElementById('terrain-canvas');
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(canvas.clientWidth, canvas.clientHeight);
}

// Camera controls
function rotateCamera(direction) {
    const angle = direction === 'left' ? -Math.PI / 8 : Math.PI / 8;
    camera.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), angle);
    camera.lookAt(0, 0, 0);
}

function resetCamera() {
    camera.position.set(terrainSize * 0.7, terrainSize * 0.7, terrainSize * 0.7);
    camera.lookAt(0, 0, 0);
    controls.update();
}

// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
    initScene();
    generateTerrain('diamond-square');
});