diff --git a/index.html b/index.html
index 71b02d6..93e33da 100644
--- a/index.html
+++ b/index.html
@@ -44,6 +44,7 @@
+
diff --git a/js/constants.js b/js/constants.js
index 6e5f20c..cf23139 100644
--- a/js/constants.js
+++ b/js/constants.js
@@ -60,6 +60,9 @@ const LEAF = 12;
const FIRE = 13;
const LAVA = 14;
const RABBIT = 15;
+const SQUARE = 16;
+const CIRCLE = 17;
+const TRIANGLE = 18;
// Flammable materials
const FLAMMABLE_MATERIALS = [GRASS, WOOD, SEED, GRASS_BLADE, FLOWER, TREE_SEED, LEAF];
diff --git a/js/elements/physics_objects.js b/js/elements/physics_objects.js
new file mode 100644
index 0000000..8c64522
--- /dev/null
+++ b/js/elements/physics_objects.js
@@ -0,0 +1,247 @@
+// Physics objects (square, circle, triangle)
+const SQUARE = 16;
+const CIRCLE = 17;
+const TRIANGLE = 18;
+
+// 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;
+
+ 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);
+ }
+}
diff --git a/js/physics.js b/js/physics.js
index a9fffd8..4420747 100644
--- a/js/physics.js
+++ b/js/physics.js
@@ -7,6 +7,9 @@ function updatePhysics(timestamp) {
lastPhysicsTime = timestamp || 0;
+ // Update physics objects
+ updatePhysicsObjects();
+
// Get visible chunks
const visibleChunks = getVisibleChunks();
diff --git a/js/render.js b/js/render.js
index 2488a25..c67d51c 100644
--- a/js/render.js
+++ b/js/render.js
@@ -63,6 +63,9 @@ function render() {
// Reset world moved flag after rendering
worldMoved = false;
+ // Render physics objects
+ renderPhysicsObjects(ctx, worldOffsetX, worldOffsetY);
+
// Draw cursor position and update debug info
if (currentMouseX !== undefined && currentMouseY !== undefined) {
const worldX = Math.floor(currentMouseX / PIXEL_SIZE) + worldOffsetX;