/*******************************************************************************
*
* @file World.js A file with the classes for the World elements
*
* @author Omar Essilfie-Quaye <omareq08+githubio@gmail.com>
* @version 1.0
* @date 12-February-2025
* @link https://omareq.github.io/tanks/
* @link https://omareq.github.io/tanks/docs/
*
*******************************************************************************
*
* GNU General Public License V3.0
* --------------------------------
*
* Copyright (C) 2025 Omar Essilfie-Quaye
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*****************************************************************************/
"use strict";
/**
* TankGame namespace object
*/
var TankGame = TankGame || {};
TankGame.World = {};
/**
* Class Terrain describes the terrain object that represents the ground. This
* is passed into the game engine and can be damaged by projectiles.
*
* @see TankGame.GameEngine
* @see TankGame.Projectile
*/
TankGame.World.Terrain = class {
/**
* constructor of the terrain
*
* @param {number} width - the width of the screen
* @param {number} minHeight - the minimum height of the ground in screen coordinates
* @param {number} maxHeight - the maximum height of the ground in screen coordinates
* @param {number} noiseDetail - used to scale the period of the perlin noise
*/
constructor(width, minHeight, maxHeight, noiseDetail=1) {
this.width = width;
this.groundHeight = [];
const heightRange = maxHeight - minHeight;
for(let i = 0; i < width; i++) {
let nextGroundHeight = heightRange * noise(i * noiseDetail) + minHeight;
this.groundHeight.push(nextGroundHeight);
}
this.color = color(155, 155, 155);
this.stepSize = 10;
this.drawRetro = true;
};
/**
* Function to add a crater in the ground where an explosion occurred.
*
* @param {p5.Vector} pos - position of the explosion
* @param {number} radius - radius of the explosion
*
* @throws {Error} param pos should be instance of p5.Vector
*/
addCrater(pos, radius) {
if(!(pos instanceof p5.Vector)) {
let err = "pos is not an instance of p5.vector";
throw(err);
}
if(pos.x < 0 || pos.x > width) {
let err = "Can't remove circle that is out of bounds: ";
err += " pos.x - " + pos.x;
console.warn(err);
return;
}
//TODO: Checks that inputs are valid
const index = floor(pos.x);
const r = floor(radius);
for(let i = index - r; i < index + r; i++) {
// (x - a) ^2 + (y - b) ^2 = r^2
// (y - b)^2 = r^2 - (x - a)^2
// y = \sqrt(r^2 - (x - a)^2) + b
const newY = sqrt(r**2 - (i - pos.x)**2) + pos.y;
if(this.groundHeight[i] < newY) {
this.groundHeight[i] = newY;
}
}
return;
};
/**
* Draw triangular strips for the ground. This creates a smoother appearance
* than rectangles but is more computationally intensive.
*/
drawTriangleStrip() {
beginShape(TRIANGLE_STRIP);
vertex(0, height);
for(let i = 0; i < this.groundHeight.length; i+= this.stepSize) {
vertex(i, this.groundHeight[i]);
vertex(i + this.stepSize / 2, height);
}
vertex(this.width, this.groundHeight[this.groundHeight.length - 1]);
vertex(this.width, height);
endShape();
};
/**
* Draw equal rectangles to represent the ground. This creates a retro
* appearance which is desirable and is performance friendly.
*/
drawRect() {
for(let i = 0; i < this.groundHeight.length; i+= this.stepSize) {
//TODO: Try dynamically assigning width of rectangle based on terrain gradient
rect(i, this.groundHeight[i], this.stepSize, height - this.groundHeight[i]);
}
};
/**
* Draw the ground using the desired method
*/
draw() {
stroke(this.color);
fill(this.color);
if(this.drawRetro) {
this.drawRect();
} else {
this.drawTriangleStrip();
}
};
};
// wind