648 lines
23 KiB
JavaScript
648 lines
23 KiB
JavaScript
// Entity classes for game objects
|
||
|
||
function Star(canvas) {
|
||
this.canvas = canvas
|
||
this.reset();
|
||
this.baseBrightness = 0.6 + Math.random() * 0.4;
|
||
this.brightness = this.baseBrightness;
|
||
this.twinkleSpeed = Math.random() * 0.003 + 0.001;
|
||
this.twinkleAmount = 0.1 + Math.random() * 0.1;
|
||
|
||
var colorRand = Math.random();
|
||
for (var i = 0; i < CONFIG.STAR_COLORS.length; i++) {
|
||
if (colorRand < CONFIG.STAR_COLORS[i].threshold) {
|
||
this.color = CONFIG.STAR_COLORS[i].rgb;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
Star.prototype.reset = function() {
|
||
this.x = Math.random() * this.canvas.width;
|
||
this.y = Math.random() * this.canvas.height;
|
||
this.size = Math.random() * 1;
|
||
};
|
||
|
||
Star.prototype.update = function() {
|
||
this.brightness += this.twinkleSpeed;
|
||
if (this.brightness > this.baseBrightness + this.twinkleAmount ||
|
||
this.brightness < this.baseBrightness - this.twinkleAmount) {
|
||
this.twinkleSpeed = -this.twinkleSpeed;
|
||
}
|
||
};
|
||
|
||
Star.prototype.draw = function(ctx) {
|
||
ctx.fillStyle = 'rgba(' + this.color + ', ' + this.brightness + ')';
|
||
ctx.beginPath();
|
||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
};
|
||
|
||
function Asteroid(type, blackHole, canvas) {
|
||
this.canvas = canvas
|
||
if (!type) type = 'small';
|
||
this.type = type;
|
||
|
||
var angle = Math.random() * Math.PI * 2;
|
||
var distance = Math.max(canvas.width, canvas.height) * 0.7;
|
||
|
||
this.x = this.canvas.width / 2 + Math.cos(angle) * distance;
|
||
this.y = this.canvas.height / 2 + Math.sin(angle) * distance;
|
||
|
||
this.angle = Math.atan2(this.y - blackHole.y, this.x - blackHole.x);
|
||
this.distance = Math.sqrt(
|
||
Math.pow(this.x - blackHole.x, 2) +
|
||
Math.pow(this.y - blackHole.y, 2)
|
||
);
|
||
this.orbitSpeed = 0.005 + Math.random() * 0.003;
|
||
this.decayRate = 0.08 + Math.random() * 0.05;
|
||
|
||
this.blackHole = blackHole;
|
||
|
||
this.initializeTypeSpecificProperties();
|
||
|
||
this.rotation = Math.random() * Math.PI * 2;
|
||
this.rotationSpeed = (Math.random() - 0.5) * 0.1;
|
||
}
|
||
|
||
Asteroid.prototype.initializeTypeSpecificProperties = function() {
|
||
// Visual size (unchanged)
|
||
if (this.type === 'comet') {
|
||
this.size = 3 + Math.random() * 3;
|
||
} else if (this.type === 'mtype') {
|
||
this.size = 15 + Math.random() * 3;
|
||
this.orbitSpeed *= 0.2;
|
||
this.decayRate *= 0.3;
|
||
this.planetColors = CONFIG.MTYPE_COLORS[Math.floor(Math.random() * CONFIG.MTYPE_COLORS.length)];
|
||
|
||
// Generate spots (dark brownish surface features)
|
||
this.spots = [];
|
||
var spotCount = 0 + Math.floor(Math.random() * 9); // 0-9 spots
|
||
for (var i = 0; i < spotCount; i++) {
|
||
var angle = Math.random() * Math.PI * 2;
|
||
var dist = Math.random() * this.size * 0.7;
|
||
this.spots.push({
|
||
x: Math.cos(angle) * dist,
|
||
y: Math.sin(angle) * dist,
|
||
radius: 1 + Math.random() * (this.size * 0.3),
|
||
color: CONFIG.MTYPE_SPOT_COLORS[Math.floor(Math.random() * CONFIG.MTYPE_SPOT_COLORS.length)]
|
||
});
|
||
}
|
||
// Generate sparkles
|
||
this.sparkles = [];
|
||
for (var s = 0; s < 20; s++) { // more sparkles if you want
|
||
var angle = Math.random() * Math.PI * 2;
|
||
var radius = Math.random() * this.size * 0.9;
|
||
this.sparkles.push({
|
||
x: Math.cos(angle) * radius,
|
||
y: Math.sin(angle) * radius,
|
||
phase: Math.random() * Math.PI * 2 // random phase for variety
|
||
});
|
||
}
|
||
} else if (this.type === 'giant') {
|
||
this.size = 10 + Math.random() * 5;
|
||
this.orbitSpeed *= 0.3;
|
||
this.decayRate *= 0.4;
|
||
this.planetColors = CONFIG.GIANT_COLORS[Math.floor(Math.random() * CONFIG.GIANT_COLORS.length)];
|
||
// Initialize rings
|
||
this.rings = [];
|
||
var ringCount = Math.floor(Math.random() * 4); // 0-3 rings
|
||
for (var i = 0; i < ringCount; i++) {
|
||
this.rings.push({
|
||
color: CONFIG.GIANT_RING_COLORS[Math.floor(Math.random() * CONFIG.GIANT_RING_COLORS.length)],
|
||
thickness: 1 + Math.random() * 9, // random thickness
|
||
radiusOffset: 10 + i * 5 + Math.random() * 5, // spread rings outward
|
||
alpha: 0.3 + Math.random() * 0.4 // stored permanently
|
||
});
|
||
}
|
||
} else if (this.type === 'planet') {
|
||
this.size = 6 + Math.random() * 4;
|
||
this.orbitSpeed *= 0.5;
|
||
this.decayRate *= 0.6;
|
||
this.planetColors = CONFIG.PLANET_COLORS[Math.floor(Math.random() * CONFIG.PLANET_COLORS.length)];
|
||
} else if (this.type === 'large') {
|
||
this.size = 3 + Math.random() * 1;
|
||
} else if (this.type === 'medium') {
|
||
this.size = 2 + Math.random() * 1;
|
||
} else {
|
||
this.size = 1 + Math.random() * 1;
|
||
}
|
||
|
||
// Physical mass (kg)
|
||
var range = CONFIG.ASTEROID_MASS_RANGES[this.type] || CONFIG.ASTEROID_MASS_RANGES.small;
|
||
this.massKg = range[0] + Math.random() * (range[1] - range[0]);
|
||
};
|
||
|
||
Asteroid.prototype.update = function() {
|
||
this.angle += this.orbitSpeed;
|
||
this.distance -= this.decayRate;
|
||
this.x = this.blackHole.x + Math.cos(this.angle) * this.distance;
|
||
this.y = this.blackHole.y + Math.sin(this.angle) * this.distance;
|
||
this.rotation += this.rotationSpeed;
|
||
};
|
||
|
||
Asteroid.prototype.draw = function(ctx) {
|
||
if (this.type === 'comet') {
|
||
this.drawComet(ctx);
|
||
} else if (this.type === 'mtype') {
|
||
this.drawMType(ctx, performance.now() * 0.002);
|
||
} else if (this.type === 'giant') {
|
||
// Initialize per-giant tilt if not already done
|
||
if (this._ringTiltBase === undefined) {
|
||
this._ringTiltBase = (Math.random() - 0.5) * 0.3; // ±0.15 rad
|
||
this._tiltOscillationSpeed = 0.001 + Math.random() * 0.002;
|
||
this._tiltScale = 0.1 + Math.random() * 0.69; // vertical squash
|
||
}
|
||
|
||
const tilt = this._ringTiltBase + Math.sin(Date.now() * this._tiltOscillationSpeed) * 0.05;
|
||
|
||
if (this.rings && this.rings.length > 0) {
|
||
// Draw back halves of rings
|
||
this.rings.forEach((ring) => {
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
ctx.rotate(tilt);
|
||
ctx.scale(1, this._tiltScale);
|
||
|
||
ctx.strokeStyle = ring.color.replace(/;?\s*$/, '') // just in case
|
||
ctx.strokeStyle = ring.color.startsWith('rgba')
|
||
? ring.color.replace(/rgba\(([^)]+),[^)]+\)/, `rgba($1,${ring.alpha})`)
|
||
: `rgba(${parseInt(ring.color.slice(1,3),16)},${parseInt(ring.color.slice(3,5),16)},${parseInt(ring.color.slice(5,7),16)},${ring.alpha})`;
|
||
|
||
ctx.lineWidth = ring.thickness;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size + ring.radiusOffset, Math.PI, 2 * Math.PI); // back half
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
});
|
||
}
|
||
|
||
// Draw the giant planet
|
||
this.drawGiant(ctx);
|
||
|
||
if (this.rings && this.rings.length > 0) {
|
||
// Draw front halves of rings
|
||
this.rings.forEach((ring) => {
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
ctx.rotate(tilt);
|
||
ctx.scale(1, this._tiltScale);
|
||
|
||
ctx.strokeStyle = ring.color.startsWith('rgba')
|
||
? ring.color.replace(/rgba\(([^)]+),[^)]+\)/, `rgba($1,${rings.alpha})`)
|
||
: `rgba(${parseInt(ring.color.slice(1,3),16)},${parseInt(ring.color.slice(3,5),16)},${parseInt(ring.color.slice(5,7),16)},${ring.alpha})`;
|
||
|
||
ctx.lineWidth = ring.thickness;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size + ring.radiusOffset, 0, Math.PI); // front half
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
});
|
||
}
|
||
|
||
} else if (this.type === 'planet') {
|
||
this.drawPlanet(ctx);
|
||
} else {
|
||
this.drawAsteroid(ctx);
|
||
}
|
||
};
|
||
|
||
Asteroid.prototype.drawComet = function(ctx) {
|
||
var orbitTangentAngle = this.angle + Math.PI / 2;
|
||
var tailLength = this.size * 5;
|
||
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
ctx.rotate(orbitTangentAngle + Math.PI);
|
||
|
||
var gradient = ctx.createLinearGradient(0, 0, tailLength, 0);
|
||
gradient.addColorStop(0, 'rgba(80, 140, 200, 0.5)');
|
||
gradient.addColorStop(0.5, 'rgba(60, 100, 150, 0.25)');
|
||
gradient.addColorStop(1, 'rgba(40, 80, 120, 0)');
|
||
|
||
ctx.fillStyle = gradient;
|
||
ctx.fillRect(0, -this.size * 0.4, tailLength, this.size * 0.8);
|
||
ctx.restore();
|
||
|
||
var nucleusSize = this.size * 0.5;
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
ctx.fillStyle = '#5a7a8a';
|
||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
||
ctx.lineWidth = 0.5;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, nucleusSize, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.stroke();
|
||
|
||
ctx.fillStyle = 'rgba(80, 120, 140, 0.2)';
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, nucleusSize * 1.3, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.restore();
|
||
};
|
||
|
||
Asteroid.prototype.drawMType = function(ctx, time) {
|
||
var massRange = CONFIG.ASTEROID_MASS_RANGES.mtype;
|
||
var massNormalized = (this.massKg - massRange[0]) / (massRange[1] - massRange[0]); // 0-1
|
||
|
||
// Pulsing multiplier for glow
|
||
var pulse = 0.2 * Math.sin(time * 2 + this.massKg) + 1;
|
||
|
||
// Glow
|
||
var glowIntensity = (0.5 + massNormalized * 0.8) * pulse;
|
||
var glowSize = this.size * (1.5 + massNormalized * 0.8) * pulse;
|
||
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
|
||
// Outer glow (soft)
|
||
var outerGlow = ctx.createRadialGradient(0, 0, this.size * 0.5, 0, 0, glowSize);
|
||
outerGlow.addColorStop(0, 'rgba(255, 200, 150,' + (glowIntensity * 0.4) + ')');
|
||
outerGlow.addColorStop(0.5, 'rgba(200, 120, 80,' + (glowIntensity * 0.2) + ')');
|
||
outerGlow.addColorStop(1, 'rgba(140, 80, 50, 0)');
|
||
ctx.fillStyle = outerGlow;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, glowSize, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Main body
|
||
var bodyGradient = ctx.createRadialGradient(
|
||
-this.size * 0.1, -this.size * 0.1, 0,
|
||
0, 0, this.size
|
||
);
|
||
bodyGradient.addColorStop(0, this.planetColors.light);
|
||
bodyGradient.addColorStop(0.5, this.planetColors.mid);
|
||
bodyGradient.addColorStop(1, this.planetColors.dark);
|
||
ctx.fillStyle = bodyGradient;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Gritty surface texture (tiny semi-random dots)
|
||
for (var i = 0; i < this.size * 4; i++) {
|
||
ctx.fillStyle = 'rgba(0,0,0,' + (Math.random() * 0.05) + ')';
|
||
var rx = (Math.random() - 0.5) * this.size * 2;
|
||
var ry = (Math.random() - 0.5) * this.size * 2;
|
||
if (rx*rx + ry*ry <= this.size*this.size) ctx.fillRect(rx, ry, 1, 1);
|
||
}
|
||
|
||
// Swirling bands (subtle, irregular)
|
||
ctx.strokeStyle = 'rgba(0,0,0,0.06)';
|
||
ctx.lineWidth = 1;
|
||
var amplitude = this.size * 0.06;
|
||
var frequency = 0.25;
|
||
for (var band = -this.size * 0.7; band < this.size * 0.7; band += this.size * 0.15) {
|
||
ctx.beginPath();
|
||
for (var x = -this.size; x <= this.size; x += 1) {
|
||
var y = band + Math.sin(x * frequency + band + time) * amplitude
|
||
+ (Math.random()-0.5) * 1.5; // gritty randomness
|
||
if (x === -this.size) ctx.moveTo(x, y);
|
||
else ctx.lineTo(x, y);
|
||
}
|
||
ctx.stroke();
|
||
}
|
||
|
||
// Spots
|
||
for (var i = 0; i < this.spots.length; i++) {
|
||
var spot = this.spots[i];
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
|
||
ctx.clip();
|
||
|
||
ctx.fillStyle = spot.color;
|
||
ctx.beginPath();
|
||
ctx.arc(spot.x, spot.y, spot.radius, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.restore();
|
||
}
|
||
|
||
// Inner core glow
|
||
var coreGlow = ctx.createRadialGradient(0, 0, 0, 0, 0, this.size * 0.5 * pulse);
|
||
coreGlow.addColorStop(0, 'rgba(255, 230, 160,' + (glowIntensity * 0.5) + ')');
|
||
coreGlow.addColorStop(1, 'rgba(255, 150, 80, 0)');
|
||
ctx.fillStyle = coreGlow;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size * 0.5 * pulse, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Subtle edge glow instead of hard stroke
|
||
var edgeGlow = ctx.createRadialGradient(0, 0, this.size * 0.9, 0, 0, this.size);
|
||
edgeGlow.addColorStop(0, 'rgba(255,200,120,0.15)');
|
||
edgeGlow.addColorStop(1, 'rgba(255,200,120,0)');
|
||
ctx.fillStyle = edgeGlow;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Sparkles (tiny random twinkles)
|
||
for (var i = 0; i < this.sparkles.length; i++) {
|
||
var s = this.sparkles[i];
|
||
var sparkleFrequency = 0.1;
|
||
var sparkleIntensity = 0.4 + 0.4 * Math.sin(time * sparkleFrequency + s.phase);
|
||
ctx.fillStyle = 'rgba(255,255,200,' + sparkleIntensity + ')';
|
||
ctx.fillRect(s.x, s.y, 1.5, 1.5);
|
||
}
|
||
|
||
ctx.restore();
|
||
};
|
||
|
||
Asteroid.prototype.drawGiant = function(ctx) {
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
|
||
var gradient = ctx.createRadialGradient(-this.size * 0.25, -this.size * 0.25, 0, 0, 0, this.size);
|
||
gradient.addColorStop(0, this.planetColors.light);
|
||
gradient.addColorStop(0.5, this.planetColors.mid);
|
||
gradient.addColorStop(1, this.planetColors.dark);
|
||
|
||
ctx.fillStyle = gradient;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
|
||
ctx.lineWidth = 1;
|
||
for (var band = -this.size * 0.6; band < this.size * 0.6; band += this.size * 0.15) {
|
||
ctx.beginPath();
|
||
var bandWidth = Math.sqrt(this.size * this.size - band * band);
|
||
ctx.moveTo(-bandWidth, band);
|
||
ctx.lineTo(bandWidth, band);
|
||
ctx.stroke();
|
||
}
|
||
|
||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
|
||
ctx.lineWidth = 0.5;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
|
||
ctx.restore();
|
||
};
|
||
|
||
Asteroid.prototype.drawPlanet = function(ctx) {
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
|
||
var gradient = ctx.createRadialGradient(-this.size * 0.2, -this.size * 0.2, 0, 0, 0, this.size);
|
||
gradient.addColorStop(0, this.planetColors.light);
|
||
gradient.addColorStop(0.5, this.planetColors.mid);
|
||
gradient.addColorStop(1, this.planetColors.dark);
|
||
|
||
ctx.fillStyle = gradient;
|
||
ctx.beginPath();
|
||
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
||
ctx.lineWidth = 0.5;
|
||
ctx.stroke();
|
||
|
||
ctx.restore();
|
||
};
|
||
|
||
Asteroid.prototype.drawAsteroid = function(ctx) {
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
ctx.rotate(this.rotation);
|
||
|
||
var asteroidColor = CONFIG.ASTEROID_COLORS[this.type] || CONFIG.ASTEROID_COLORS.small;
|
||
|
||
ctx.fillStyle = asteroidColor;
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, -this.size);
|
||
ctx.lineTo(this.size * 0.8, this.size * 0.6);
|
||
ctx.lineTo(-this.size * 0.8, this.size * 0.6);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
ctx.restore();
|
||
};
|
||
|
||
Asteroid.prototype.isDestroyed = function() {
|
||
return this.distance < this.blackHole.radius;
|
||
};
|
||
|
||
function BlackHole(x, y, radius) {
|
||
this.x = x;
|
||
this.y = y;
|
||
this.radius = radius;
|
||
this.pulse = 0;
|
||
this.pulseColor = '#000000';
|
||
};
|
||
|
||
BlackHole.prototype.draw = function(ctx, consumptionRate) {
|
||
// Calculate jet intensity based on consumption rate
|
||
var jetMin = 1e7; // Start triggering at 10 Mt/s (Comets)
|
||
var jetMax = 1e22; // max intensity
|
||
var minVisible = 0.1; // 10% intensity floor
|
||
|
||
var targetIntensity = 0;
|
||
|
||
if (consumptionRate && consumptionRate > jetMin) {
|
||
var logMin = 10; // log10(1e10)
|
||
var logMax = 22; // log10(1e22)
|
||
var logRate = Math.log10(consumptionRate);
|
||
|
||
// Log-scaled 0..1
|
||
targetIntensity = Math.max(0, Math.min(
|
||
(logRate - logMin) / (logMax - logMin),
|
||
1
|
||
));
|
||
|
||
// Perceptual tweaks
|
||
targetIntensity = Math.pow(targetIntensity, 0.35); // boost low end
|
||
targetIntensity = minVisible + (1 - minVisible) * targetIntensity;
|
||
}
|
||
|
||
// Smooth transition for jet intensity
|
||
if (!this.jetIntensity) this.jetIntensity = 0;
|
||
|
||
var transitionSpeed = 0.05; // Lower = slower fade (0.01 = very slow, 0.05 = fast)
|
||
|
||
if (targetIntensity > this.jetIntensity) {
|
||
// Fade in
|
||
this.jetIntensity = Math.min(this.jetIntensity + transitionSpeed, targetIntensity);
|
||
} else if (targetIntensity < this.jetIntensity) {
|
||
// Fade out
|
||
this.jetIntensity = Math.max(this.jetIntensity - transitionSpeed, targetIntensity);
|
||
}
|
||
|
||
// Draw jets first (behind the black hole)
|
||
if (this.jetIntensity > 0) {
|
||
this.drawJets(ctx, this.jetIntensity);
|
||
}
|
||
|
||
// Existing black hole drawing code
|
||
if (this.pulse > 0) {
|
||
this.pulse -= 0.02;
|
||
if (this.pulse < 0) this.pulse = 0;
|
||
}
|
||
if (this.pulse === 0) {
|
||
this.pulseColor = '#000000';
|
||
}
|
||
|
||
ctx.fillStyle = '#000000';
|
||
ctx.beginPath();
|
||
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
var visualPulse = Math.min(this.pulse, 1);
|
||
var glowIntensity = 0.2 + (visualPulse * 0.3);
|
||
var glowRadius = this.radius * (1.01 + visualPulse * 0.1);
|
||
|
||
var gradient = ctx.createRadialGradient(
|
||
this.x, this.y, this.radius * 0.97,
|
||
this.x, this.y, glowRadius
|
||
);
|
||
|
||
var r, g, b;
|
||
if (this.pulseColor.startsWith('#')) {
|
||
var hex = this.pulseColor;
|
||
r = parseInt(hex.substr(1, 2), 16);
|
||
g = parseInt(hex.substr(3, 2), 16);
|
||
b = parseInt(hex.substr(5, 2), 16);
|
||
} else {
|
||
r = 60; g = 60; b = 80;
|
||
}
|
||
|
||
// Increase saturation by 3x when pulsing
|
||
var saturated = saturateColor(r, g, b, 3);
|
||
r = saturated.r;
|
||
g = saturated.g;
|
||
b = saturated.b;
|
||
|
||
gradient.addColorStop(0, 'rgba(' + r + ', ' + g + ', ' + b + ', 0)');
|
||
gradient.addColorStop(1, 'rgba(' + r + ', ' + g + ', ' + b + ', ' + glowIntensity + ')');
|
||
|
||
ctx.fillStyle = gradient;
|
||
ctx.beginPath();
|
||
ctx.arc(this.x, this.y, glowRadius, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
};
|
||
|
||
BlackHole.prototype.consumeAsteroid = function(asteroid) {
|
||
// Calculate pulse contribution (same as before)
|
||
var normalized = (Math.log10(asteroid.massKg) - 3) / 6;
|
||
var pulseContribution = Math.max(0.1, normalized);
|
||
this.pulse = Math.min(this.pulse + pulseContribution, 9);
|
||
|
||
// Determine new pulse color based on asteroid type/size
|
||
var newColor;
|
||
if (asteroid.planetColors) {
|
||
newColor = asteroid.planetColors.mid;
|
||
} else if (asteroid.type === 'comet') {
|
||
newColor = '#5a7a8a';
|
||
} else if (asteroid.size === 'large') {
|
||
newColor = '#f8f8f8';
|
||
} else if (asteroid.size === 'medium') {
|
||
newColor = '#888888';
|
||
} else {
|
||
newColor = '#444444';
|
||
}
|
||
|
||
// Blend with existing pulseColor instead of replacing
|
||
if (!this.pulseColor) this.pulseColor = newColor;
|
||
else this.pulseColor = this.blendColors(this.pulseColor, newColor, 0.3);
|
||
};
|
||
|
||
// Helper function to blend two hex colors by weight (0–1)
|
||
BlackHole.prototype.blendColors = function(c1, c2, weight) {
|
||
var d2h = d => d.toString(16).padStart(2, '0');
|
||
var h2d = h => parseInt(h, 16);
|
||
|
||
// Remove #
|
||
c1 = c1.replace('#','');
|
||
c2 = c2.replace('#','');
|
||
|
||
var r = Math.round(h2d(c1.substring(0,2)) * (1-weight) + h2d(c2.substring(0,2)) * weight);
|
||
var g = Math.round(h2d(c1.substring(2,4)) * (1-weight) + h2d(c2.substring(2,4)) * weight);
|
||
var b = Math.round(h2d(c1.substring(4,6)) * (1-weight) + h2d(c2.substring(4,6)) * weight);
|
||
|
||
return '#' + d2h(r) + d2h(g) + d2h(b);
|
||
};
|
||
|
||
BlackHole.prototype.drawJets = function(ctx, intensity) {
|
||
// Jet configuration (easily adjustable)
|
||
var config = {
|
||
color: 'rgba(100, 150, 255, 0.8)', // Blue-white jet color
|
||
coreColor: 'rgba(200, 220, 255, 0.9)', // Bright core
|
||
length: this.radius * 8, // Jet length (8x black hole radius)
|
||
width: this.radius * 0.2, // Jet width at base
|
||
coreWidth: this.radius * 0.05, // Bright core width
|
||
particleCount: 15, // Number of particles per jet
|
||
particleSize: 2, // Size of individual particles
|
||
rotation: 0, // Rotation angle (for spinning jets)
|
||
flickerSpeed: 0.1 // How fast jets flicker
|
||
};
|
||
|
||
// Intensity affects opacity and size (0-1 scale)
|
||
var alpha = Math.min(intensity, 1);
|
||
var sizeMultiplier = 0.5 + (intensity * 0.5); // 50% to 100% size based on intensity
|
||
|
||
// Draw both jets (top and bottom)
|
||
for (var direction = -1; direction <= 1; direction += 2) {
|
||
ctx.save();
|
||
ctx.translate(this.x, this.y);
|
||
ctx.rotate(config.rotation);
|
||
|
||
// Outer jet cone (gradient)
|
||
var jetGradient = ctx.createLinearGradient(
|
||
0, 0,
|
||
0, direction * config.length * sizeMultiplier
|
||
);
|
||
|
||
var jetColor = config.color.replace(/[\d.]+\)/, (alpha * 0.6) + ')');
|
||
jetGradient.addColorStop(0, jetColor);
|
||
jetGradient.addColorStop(0.3, jetColor.replace(/[\d.]+\)/, (alpha * 0.4) + ')'));
|
||
jetGradient.addColorStop(1, 'rgba(100, 150, 255, 0)');
|
||
|
||
ctx.fillStyle = jetGradient;
|
||
ctx.beginPath();
|
||
ctx.moveTo(-config.width * sizeMultiplier, 0);
|
||
ctx.lineTo(0, direction * config.length * sizeMultiplier);
|
||
ctx.lineTo(config.width * sizeMultiplier, 0);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
|
||
// Inner bright core
|
||
var coreGradient = ctx.createLinearGradient(
|
||
0, 0,
|
||
0, direction * config.length * sizeMultiplier * 0.7
|
||
);
|
||
|
||
var coreColor = config.coreColor.replace(/[\d.]+\)/, alpha + ')');
|
||
coreGradient.addColorStop(0, coreColor);
|
||
coreGradient.addColorStop(0.5, coreColor.replace(/[\d.]+\)/, (alpha * 0.5) + ')'));
|
||
coreGradient.addColorStop(1, 'rgba(200, 220, 255, 0)');
|
||
|
||
ctx.fillStyle = coreGradient;
|
||
ctx.beginPath();
|
||
ctx.moveTo(-config.coreWidth * sizeMultiplier, 0);
|
||
ctx.lineTo(0, direction * config.length * sizeMultiplier * 0.7);
|
||
ctx.lineTo(config.coreWidth * sizeMultiplier, 0);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
|
||
// Particles along the jet (for dynamic effect)
|
||
ctx.fillStyle = config.coreColor.replace(/[\d.]+\)/, alpha + ')');
|
||
for (var i = 0; i < config.particleCount; i++) {
|
||
var particlePos = (i / config.particleCount) * config.length * sizeMultiplier;
|
||
var particleOffset = (Math.sin(Date.now() * 0.01 + i) * config.width * 0.3) * sizeMultiplier;
|
||
var particleSize = config.particleSize * (1 - i / config.particleCount); // Smaller as they go out
|
||
|
||
ctx.beginPath();
|
||
ctx.arc(
|
||
particleOffset,
|
||
direction * particlePos,
|
||
particleSize * alpha,
|
||
0,
|
||
Math.PI * 2
|
||
);
|
||
ctx.fill();
|
||
}
|
||
|
||
ctx.restore();
|
||
}
|
||
};
|