266 lines
9.0 KiB
JavaScript
266 lines
9.0 KiB
JavaScript
// Physics objects (square, circle, triangle)
|
|
// Constants are already defined in constants.js
|
|
|
|
// Physics object properties
|
|
const PHYSICS_OBJECT_COLORS = ['#FF5733', '#33FF57', '#3357FF', '#F3FF33', '#FF33F3'];
|
|
|
|
// Physics constants
|
|
const GRAVITY_ACCELERATION = 0.2;
|
|
const BOUNCE_FACTOR = 0.7;
|
|
const FRICTION = 0.98;
|
|
const ROTATION_SPEED = 0.05;
|
|
|
|
// Store physics objects
|
|
const physicsObjects = [];
|
|
|
|
// Physics object class
|
|
class PhysicsObject {
|
|
constructor(type, x, y, size) {
|
|
this.type = type;
|
|
this.x = x;
|
|
this.y = y;
|
|
this.size = size || 10;
|
|
this.vx = 0;
|
|
this.vy = 0;
|
|
this.rotation = 0;
|
|
this.angularVelocity = (Math.random() - 0.5) * ROTATION_SPEED;
|
|
this.color = PHYSICS_OBJECT_COLORS[Math.floor(Math.random() * PHYSICS_OBJECT_COLORS.length)];
|
|
this.isStatic = false;
|
|
this.lastUpdate = performance.now();
|
|
}
|
|
|
|
update() {
|
|
const now = performance.now();
|
|
const deltaTime = Math.min(50, now - this.lastUpdate); // Cap at 50ms to prevent huge jumps
|
|
this.lastUpdate = now;
|
|
|
|
if (this.isStatic) return false;
|
|
|
|
// Apply gravity
|
|
this.vy += GRAVITY_ACCELERATION;
|
|
|
|
// Calculate new position
|
|
const newX = this.x + this.vx;
|
|
const newY = this.y + this.vy;
|
|
|
|
// Check for collisions with world elements
|
|
const collisionResult = this.checkCollisions(newX, newY);
|
|
|
|
if (collisionResult.collision) {
|
|
// Handle collision response
|
|
if (collisionResult.horizontal) {
|
|
this.vx *= -BOUNCE_FACTOR;
|
|
}
|
|
if (collisionResult.vertical) {
|
|
this.vy *= -BOUNCE_FACTOR;
|
|
}
|
|
|
|
// Apply friction
|
|
this.vx *= FRICTION;
|
|
this.vy *= FRICTION;
|
|
|
|
// If object is almost stopped, make it static
|
|
if (Math.abs(this.vx) < 0.1 && Math.abs(this.vy) < 0.1 && Math.abs(this.angularVelocity) < 0.01) {
|
|
this.isStatic = true;
|
|
}
|
|
} else {
|
|
// No collision, update position
|
|
this.x = newX;
|
|
this.y = newY;
|
|
}
|
|
|
|
// Update rotation
|
|
this.rotation += this.angularVelocity;
|
|
this.angularVelocity *= FRICTION;
|
|
|
|
return true;
|
|
}
|
|
|
|
checkCollisions(newX, newY) {
|
|
const result = {
|
|
collision: false,
|
|
horizontal: false,
|
|
vertical: false
|
|
};
|
|
|
|
// Check points around the object based on its type
|
|
const checkPoints = this.getCollisionCheckPoints(newX, newY);
|
|
|
|
for (const point of checkPoints) {
|
|
const pixel = getPixel(Math.floor(point.x), Math.floor(point.y));
|
|
if (pixel !== EMPTY && pixel !== WATER &&
|
|
pixel !== SQUARE && pixel !== CIRCLE && pixel !== TRIANGLE) {
|
|
result.collision = true;
|
|
|
|
// Determine collision direction
|
|
if (point.type === 'horizontal') {
|
|
result.horizontal = true;
|
|
} else if (point.type === 'vertical') {
|
|
result.vertical = true;
|
|
} else {
|
|
// Corner collision, check both directions
|
|
result.horizontal = true;
|
|
result.vertical = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
getCollisionCheckPoints(x, y) {
|
|
const points = [];
|
|
const halfSize = this.size / 2;
|
|
|
|
if (this.type === SQUARE) {
|
|
// For a square, check corners and edges
|
|
const corners = [
|
|
{ x: x - halfSize, y: y - halfSize },
|
|
{ x: x + halfSize, y: y - halfSize },
|
|
{ x: x - halfSize, y: y + halfSize },
|
|
{ x: x + halfSize, y: y + halfSize }
|
|
];
|
|
|
|
// Add rotated corners
|
|
for (const corner of corners) {
|
|
const rotatedCorner = this.rotatePoint(corner.x, corner.y, x, y, this.rotation);
|
|
points.push({ x: rotatedCorner.x, y: rotatedCorner.y, type: 'corner' });
|
|
}
|
|
|
|
// Add edge midpoints
|
|
points.push({ x: x, y: y - halfSize, type: 'vertical' });
|
|
points.push({ x: x, y: y + halfSize, type: 'vertical' });
|
|
points.push({ x: x - halfSize, y: y, type: 'horizontal' });
|
|
points.push({ x: x + halfSize, y: y, type: 'horizontal' });
|
|
|
|
} else if (this.type === CIRCLE) {
|
|
// For a circle, check points around the circumference
|
|
const numPoints = 12;
|
|
for (let i = 0; i < numPoints; i++) {
|
|
const angle = (i / numPoints) * Math.PI * 2;
|
|
points.push({
|
|
x: x + Math.cos(angle) * halfSize,
|
|
y: y + Math.sin(angle) * halfSize,
|
|
type: angle < Math.PI / 4 || angle > Math.PI * 7/4 || (angle > Math.PI * 3/4 && angle < Math.PI * 5/4) ? 'horizontal' : 'vertical'
|
|
});
|
|
}
|
|
|
|
} else if (this.type === TRIANGLE) {
|
|
// For a triangle, check vertices and edges
|
|
const vertices = [
|
|
{ x: x, y: y - halfSize },
|
|
{ x: x - halfSize, y: y + halfSize },
|
|
{ x: x + halfSize, y: y + halfSize }
|
|
];
|
|
|
|
// Add rotated vertices
|
|
for (const vertex of vertices) {
|
|
const rotatedVertex = this.rotatePoint(vertex.x, vertex.y, x, y, this.rotation);
|
|
points.push({ x: rotatedVertex.x, y: rotatedVertex.y, type: 'corner' });
|
|
}
|
|
|
|
// Add edge midpoints
|
|
const midpoints = [
|
|
{ x: (vertices[0].x + vertices[1].x) / 2, y: (vertices[0].y + vertices[1].y) / 2, type: 'edge' },
|
|
{ x: (vertices[1].x + vertices[2].x) / 2, y: (vertices[1].y + vertices[2].y) / 2, type: 'horizontal' },
|
|
{ x: (vertices[2].x + vertices[0].x) / 2, y: (vertices[2].y + vertices[0].y) / 2, type: 'edge' }
|
|
];
|
|
|
|
for (const midpoint of midpoints) {
|
|
const rotatedMidpoint = this.rotatePoint(midpoint.x, midpoint.y, x, y, this.rotation);
|
|
points.push({ x: rotatedMidpoint.x, y: rotatedMidpoint.y, type: midpoint.type });
|
|
}
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
rotatePoint(px, py, cx, cy, angle) {
|
|
const s = Math.sin(angle);
|
|
const c = Math.cos(angle);
|
|
|
|
// Translate point back to origin
|
|
px -= cx;
|
|
py -= cy;
|
|
|
|
// Rotate point
|
|
const xnew = px * c - py * s;
|
|
const ynew = px * s + py * c;
|
|
|
|
// Translate point back
|
|
return {
|
|
x: xnew + cx,
|
|
y: ynew + cy
|
|
};
|
|
}
|
|
|
|
render(ctx, offsetX, offsetY) {
|
|
const screenX = (this.x - offsetX) * PIXEL_SIZE;
|
|
const screenY = (this.y - offsetY) * PIXEL_SIZE;
|
|
|
|
ctx.save();
|
|
ctx.translate(screenX, screenY);
|
|
ctx.rotate(this.rotation);
|
|
ctx.fillStyle = this.color;
|
|
|
|
// Draw collision box in debug mode
|
|
if (debugMode) {
|
|
ctx.strokeStyle = '#ff0000';
|
|
ctx.lineWidth = 1;
|
|
|
|
// Draw a circle for the collision radius
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, this.size * PIXEL_SIZE / 2, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
|
|
// Draw a dot at the center
|
|
ctx.fillStyle = '#ffff00';
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 2, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Restore original fill color
|
|
ctx.fillStyle = this.color;
|
|
}
|
|
|
|
if (this.type === SQUARE) {
|
|
const halfSize = this.size / 2 * PIXEL_SIZE;
|
|
ctx.fillRect(-halfSize, -halfSize, this.size * PIXEL_SIZE, this.size * PIXEL_SIZE);
|
|
} else if (this.type === CIRCLE) {
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, this.size / 2 * PIXEL_SIZE, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
} else if (this.type === TRIANGLE) {
|
|
const halfSize = this.size / 2 * PIXEL_SIZE;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, -halfSize);
|
|
ctx.lineTo(-halfSize, halfSize);
|
|
ctx.lineTo(halfSize, halfSize);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
}
|
|
|
|
function createPhysicsObject(type, x, y, size) {
|
|
const obj = new PhysicsObject(type, x, y, size || 10);
|
|
physicsObjects.push(obj);
|
|
return obj;
|
|
}
|
|
|
|
function updatePhysicsObjects() {
|
|
// Update all physics objects
|
|
for (let i = physicsObjects.length - 1; i >= 0; i--) {
|
|
physicsObjects[i].update();
|
|
}
|
|
}
|
|
|
|
function renderPhysicsObjects(ctx, offsetX, offsetY) {
|
|
// Render all physics objects
|
|
for (const obj of physicsObjects) {
|
|
obj.render(ctx, offsetX, offsetY);
|
|
}
|
|
}
|