From e5f198a7737eb2ef7c12c2a27c6c101ac40509b4 Mon Sep 17 00:00:00 2001 From: Isaac Bowen Date: Sat, 3 Feb 2024 17:05:54 -0600 Subject: [PATCH] allow smaller gaps --- dist/bundle.js | 2 +- src/main.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/bundle.js b/dist/bundle.js index 37f1169..a17d19d 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -26,7 +26,7 @@ eval("/*!\n * matter-js 0.19.0 by @liabru\n * http://brm.io/matter-js/\n * Licen /***/ ((__unused_webpack_module, exports, __webpack_require__) => { "use strict"; -eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nconst matter_js_1 = __webpack_require__(/*! matter-js */ \"./node_modules/matter-js/build/matter.js\");\nclass Simulation {\n constructor(elementId) {\n this.timeScale = 0.2;\n this.speedLimit = 5;\n this.fieldOfViewDegrees = 180;\n this.viewDistance = 500;\n this.circleSize = 5;\n this.bodies = [];\n // Create an engine\n this.engine = matter_js_1.Engine.create();\n this.engine.timing.timeScale = this.timeScale;\n this.engine.gravity.x = 0; // zero gravity\n this.engine.gravity.y = 0; // zero gravity\n // Create a renderer\n const canvasElement = document.getElementById(elementId);\n this.render = matter_js_1.Render.create({\n element: canvasElement,\n engine: this.engine,\n options: {\n width: window.innerWidth,\n height: window.innerHeight,\n wireframes: false\n }\n });\n // Additional setup...\n this.addBodies();\n this.manageBodyDynamics();\n this.manageBodyShape();\n matter_js_1.Runner.run(this.engine);\n matter_js_1.Render.run(this.render);\n // Handle window resize\n window.addEventListener('resize', () => this.handleResize());\n }\n handleResize() {\n // Update the render dimensions to match the window size\n this.render.canvas.width = window.innerWidth;\n this.render.canvas.height = window.innerHeight;\n this.render.options.width = window.innerWidth;\n this.render.options.height = window.innerHeight;\n // Update the engine bounds\n this.engine.world.bounds.max.x = window.innerWidth;\n this.engine.world.bounds.max.y = window.innerHeight;\n // Center the view (optional)\n matter_js_1.Render.lookAt(this.render, {\n min: { x: 0, y: 0 },\n max: { x: window.innerWidth, y: window.innerHeight }\n });\n }\n addBodies() {\n const centerX = this.render.canvas.width / 2;\n const centerY = this.render.canvas.height / 2;\n const size = this.circleSize;\n const layerDistance = 7 * size;\n let layer = 0;\n while (this.bodies.length < 60) { // Adjust for a complete hexagonal pattern\n const bodiesInLayer = layer === 0 ? 1 : 6 * layer;\n const angleStep = Math.PI * 2 / bodiesInLayer;\n for (let i = 0; i < bodiesInLayer; i++) {\n const angle = angleStep * i;\n const x = centerX + (layerDistance * layer) * Math.cos(angle);\n const y = centerY + (layerDistance * layer) * Math.sin(angle);\n const body = matter_js_1.Bodies.circle(x, y, size, {\n angle: angle + Math.PI / 2\n });\n body.render.fillStyle = 'transparent';\n body.render.strokeStyle = '#aaa';\n body.render.lineWidth = 2;\n body.render.opacity = 1;\n matter_js_1.Body.setStatic(body, true);\n matter_js_1.World.add(this.engine.world, body);\n this.bodies.push(body); // Add to bodies array\n }\n layer++;\n }\n setInterval(() => {\n const body = this.bodies.shift();\n matter_js_1.Body.setStatic(body, !body.isStatic);\n this.bodies.push(body);\n }, 100 / this.engine.timing.timeScale);\n }\n manageBodyDynamics() {\n matter_js_1.Events.on(this.engine, 'beforeUpdate', () => {\n this.bodies.forEach((body) => {\n if (body.isStatic)\n return;\n // this gets used all over the place; calculate it once per update\n body.neighborsInView = this.getVisibleBodies(body);\n const forces = [];\n forces.push(matter_js_1.Vector.mult(this.getCurrentForce(body), 0.5));\n forces.push(matter_js_1.Vector.mult(this.getEdgeRepulsionForce(body, 100, 0.1), 5));\n forces.push(matter_js_1.Vector.mult(this.getSocialForce(body, 30), 0.2));\n forces.push(matter_js_1.Vector.mult(this.getAntiSocialForce(body, 50, 10), 0.05));\n const netForce = forces.reduce((acc, force) => matter_js_1.Vector.add(acc, force), { x: 0, y: 0 });\n // Apply the force as adjusted acceleration\n matter_js_1.Body.applyForce(body, body.position, { x: netForce.x * 0.001, y: netForce.y * 0.001 });\n // Enforce speed limit\n const currentSpeed = matter_js_1.Vector.magnitude(body.velocity);\n if (currentSpeed > this.speedLimit) {\n const scaledVelocity = matter_js_1.Vector.normalise(body.velocity);\n matter_js_1.Body.setVelocity(body, { x: scaledVelocity.x * this.speedLimit, y: scaledVelocity.y * this.speedLimit });\n }\n });\n });\n }\n manageBodyShape() {\n matter_js_1.Events.on(this.engine, 'beforeUpdate', () => {\n this.engine.world.bodies.forEach(body => {\n body.render.visible = body.isStatic;\n });\n });\n matter_js_1.Events.on(this.render, 'afterRender', (event) => {\n const { context } = this.render;\n this.engine.world.bodies.forEach(body => {\n if (body.render.visible)\n return;\n const speed = matter_js_1.Vector.magnitude(body.velocity);\n const maxSpeed = 20; // Adjust as necessary\n const elongationFactor = Math.min(speed / maxSpeed, 1);\n const length = this.circleSize + (this.circleSize * elongationFactor * 0.8);\n const width = this.circleSize * (1 - (elongationFactor * 0.5));\n context.save();\n context.translate(body.position.x, body.position.y);\n context.rotate(Math.atan2(body.velocity.y, body.velocity.x));\n context.scale(length / this.circleSize, width / this.circleSize);\n context.beginPath();\n context.arc(0, 0, this.circleSize, 0, 2 * Math.PI);\n context.fillStyle = '#F35';\n context.fill();\n context.restore();\n });\n });\n }\n getCurrentForce(body) {\n // Calculate the vector from the body to the center of the canvas\n const toCenter = matter_js_1.Vector.sub({ x: this.render.canvas.width / 2, y: this.render.canvas.height / 2 }, body.position);\n // Calculate the distance from the body to the center of the canvas\n const distanceToCenter = matter_js_1.Vector.magnitude(toCenter);\n // Define a scaling factor based on the distance to the center\n // The maximum distance possible (diagonal of the canvas) will result in a scaling factor of 1 (full force)\n // The minimum distance possible (0) will result in a scaling factor of 0 (no force)\n const maxDistance = Math.sqrt(Math.pow(this.render.canvas.width / 2, 2) + Math.pow(this.render.canvas.height / 2, 2));\n const forceScale = distanceToCenter / maxDistance * 2;\n // Calculate a perpendicular vector to create a circular motion (counter-clockwise)\n const perpendicular = matter_js_1.Vector.perp(toCenter);\n // Scale the force vector based on the distance to the center\n const scaledForce = matter_js_1.Vector.mult(matter_js_1.Vector.normalise(perpendicular), forceScale);\n return scaledForce;\n }\n getEdgeRepulsionForce(body, radius, baseForce) {\n // Calculate distance to the nearest edge on the X and Y axes\n const distanceToNearestEdgeX = Math.min(body.position.x, this.render.canvas.width - body.position.x);\n const distanceToNearestEdgeY = Math.min(body.position.y, this.render.canvas.height - body.position.y);\n // Use the new helper method to calculate repulsion force\n const forceX = this.calculateRepulsionForce(distanceToNearestEdgeX, radius, baseForce) * (body.position.x < this.render.canvas.width / 2 ? 1 : -1);\n const forceY = this.calculateRepulsionForce(distanceToNearestEdgeY, radius, baseForce) * (body.position.y < this.render.canvas.height / 2 ? 1 : -1);\n return { x: forceX, y: forceY };\n }\n getSocialForce(body, radius) {\n // aim for a gap of 10 degrees to 90 degrees\n const minGap = (10 * Math.PI) / 180;\n const maxGap = (90 * Math.PI) / 180;\n if (body.neighborsInView.length === 0) {\n return { x: 0, y: 0 }; // No force if no living neighbors\n }\n let angles = body.neighborsInView.map(other => {\n const toOther = matter_js_1.Vector.sub(other.position, body.position);\n return Math.atan2(toOther.y, toOther.x);\n }).sort((a, b) => a - b);\n angles.push(angles[0] + Math.PI * 2); // Include wrap-around angle\n let largestGap = 0;\n let gapMidpoint = 0;\n let gapRadius = 0;\n for (let i = 0; i < angles.length - 1; i++) {\n let gap = angles[i + 1] - angles[i];\n if (gap > largestGap && gap > minGap && gap < maxGap) {\n const midpointAngle = angles[i] + gap / 2;\n const distanceToNearestBody = this.getDistanceToNearestBody(body, midpointAngle, radius);\n // Half the distance to the nearest body in the direction of the gap midpoint will be the radius\n const circleRadius = distanceToNearestBody / 2;\n if (this.isCircleClear(body, midpointAngle, circleRadius, body.neighborsInView)) {\n largestGap = gap;\n gapMidpoint = midpointAngle;\n gapRadius = circleRadius;\n }\n }\n }\n if (largestGap > minGap && gapRadius > 0) {\n return this.steerTowards(body, gapMidpoint);\n }\n return { x: 0, y: 0 }; // No significant gap found, or the gap is not clear\n }\n getDistanceToNearestBody(body, angle, searchRadius) {\n let minDistance = searchRadius;\n this.engine.world.bodies.forEach(other => {\n if (other !== body && !other.isStatic) {\n const toOther = matter_js_1.Vector.sub(other.position, body.position);\n const distance = matter_js_1.Vector.magnitude(toOther);\n if (distance < minDistance) {\n const angleToOther = Math.atan2(toOther.y, toOther.x);\n if (Math.abs(angle - angleToOther) <= (10 * Math.PI) / 180) { // Check if within a 10-degree cone\n minDistance = distance;\n }\n }\n }\n });\n return minDistance;\n }\n isCircleClear(body, angle, radius, visibleBodies) {\n const circleCenter = matter_js_1.Vector.add(body.position, matter_js_1.Vector.mult({ x: Math.cos(angle), y: Math.sin(angle) }, radius));\n return !visibleBodies.some(other => {\n const distance = matter_js_1.Vector.magnitude(matter_js_1.Vector.sub(circleCenter, other.position));\n return distance < radius; // Check if any body is within the circle\n });\n }\n getAntiSocialForce(body, radius, baseForce) {\n const speedScalingFactor = Math.min(Math.max(matter_js_1.Vector.magnitude(body.velocity), 1), 2); // Scale with speed, within bounds\n let repulsionForce = { x: 0, y: 0 };\n body.neighborsInView.forEach(other => {\n if (other === body)\n return;\n const distanceVector = matter_js_1.Vector.sub(body.position, other.position);\n const distance = matter_js_1.Vector.magnitude(distanceVector);\n if (distance < radius) {\n const repulsionMagnitude = this.calculateRepulsionForce(distance, radius, baseForce) * speedScalingFactor;\n const normalizedDistanceVector = matter_js_1.Vector.normalise(distanceVector);\n const repulsion = matter_js_1.Vector.mult(normalizedDistanceVector, repulsionMagnitude);\n repulsionForce = matter_js_1.Vector.add(repulsionForce, repulsion);\n }\n });\n return repulsionForce;\n }\n getVisibleBodies(body) {\n const fieldOfViewRadians = (this.fieldOfViewDegrees * Math.PI) / 180; // Convert degrees to radians\n const searchRadius = this.viewDistance;\n // Define a bounding box around the body based on the search radius\n const boundingBox = {\n min: { x: body.position.x - searchRadius, y: body.position.y - searchRadius },\n max: { x: body.position.x + searchRadius, y: body.position.y + searchRadius }\n };\n // Use Query.region to get all bodies within the bounding box\n const bodiesInRegion = matter_js_1.Query.region(this.engine.world.bodies, boundingBox);\n // Filter these bodies by actual distance and field of view\n return bodiesInRegion.filter(other => {\n if (other === body)\n return false;\n const toOther = matter_js_1.Vector.sub(other.position, body.position);\n const distance = matter_js_1.Vector.magnitude(toOther);\n const bodyDirection = matter_js_1.Vector.create(Math.cos(body.angle), Math.sin(body.angle));\n const angleToOther = matter_js_1.Vector.angle(bodyDirection, toOther);\n // Check if within field of view and within the actual circular search radius\n return angleToOther <= fieldOfViewRadians / 2 && distance <= searchRadius;\n }).sort((a, b) => {\n // Sort by distance to the body of interest\n const distanceA = matter_js_1.Vector.magnitude(matter_js_1.Vector.sub(body.position, a.position));\n const distanceB = matter_js_1.Vector.magnitude(matter_js_1.Vector.sub(body.position, b.position));\n return distanceA - distanceB;\n });\n }\n steerTowards(body, angle) {\n // Calculate a target point in the direction of the angle, at some distance away from the body\n const targetPointDistance = 100; // Arbitrary distance to define the target point\n const targetPoint = {\n x: body.position.x + Math.cos(angle) * targetPointDistance,\n y: body.position.y + Math.sin(angle) * targetPointDistance\n };\n // Calculate the vector from the body's current position to the target point\n const desiredDirection = matter_js_1.Vector.sub(targetPoint, body.position);\n // Normalize the vector to get a unit vector in the desired direction\n return matter_js_1.Vector.normalise(desiredDirection); // This vector can be scaled as needed\n }\n calculateRepulsionForce(currentDistance, comfortableDistance, baseForce) {\n if (currentDistance < comfortableDistance) {\n const intensity = (comfortableDistance - currentDistance) / comfortableDistance;\n return baseForce * Math.pow(intensity, 2); // Quadratic increase for stronger effect near threshold\n }\n return 0;\n }\n}\n// When the page is fully loaded, start the simulation\nwindow.addEventListener('load', () => {\n new Simulation('simulationCanvas');\n});\n\n\n//# sourceURL=webpack://flow/./src/main.ts?"); +eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nconst matter_js_1 = __webpack_require__(/*! matter-js */ \"./node_modules/matter-js/build/matter.js\");\nclass Simulation {\n constructor(elementId) {\n this.timeScale = 0.2;\n this.speedLimit = 5;\n this.fieldOfViewDegrees = 180;\n this.viewDistance = 500;\n this.circleSize = 5;\n this.bodies = [];\n // Create an engine\n this.engine = matter_js_1.Engine.create();\n this.engine.timing.timeScale = this.timeScale;\n this.engine.gravity.x = 0; // zero gravity\n this.engine.gravity.y = 0; // zero gravity\n // Create a renderer\n const canvasElement = document.getElementById(elementId);\n this.render = matter_js_1.Render.create({\n element: canvasElement,\n engine: this.engine,\n options: {\n width: window.innerWidth,\n height: window.innerHeight,\n wireframes: false\n }\n });\n // Additional setup...\n this.addBodies();\n this.manageBodyDynamics();\n this.manageBodyShape();\n matter_js_1.Runner.run(this.engine);\n matter_js_1.Render.run(this.render);\n // Handle window resize\n window.addEventListener('resize', () => this.handleResize());\n }\n handleResize() {\n // Update the render dimensions to match the window size\n this.render.canvas.width = window.innerWidth;\n this.render.canvas.height = window.innerHeight;\n this.render.options.width = window.innerWidth;\n this.render.options.height = window.innerHeight;\n // Update the engine bounds\n this.engine.world.bounds.max.x = window.innerWidth;\n this.engine.world.bounds.max.y = window.innerHeight;\n // Center the view (optional)\n matter_js_1.Render.lookAt(this.render, {\n min: { x: 0, y: 0 },\n max: { x: window.innerWidth, y: window.innerHeight }\n });\n }\n addBodies() {\n const centerX = this.render.canvas.width / 2;\n const centerY = this.render.canvas.height / 2;\n const size = this.circleSize;\n const layerDistance = 7 * size;\n let layer = 0;\n while (this.bodies.length < 60) { // Adjust for a complete hexagonal pattern\n const bodiesInLayer = layer === 0 ? 1 : 6 * layer;\n const angleStep = Math.PI * 2 / bodiesInLayer;\n for (let i = 0; i < bodiesInLayer; i++) {\n const angle = angleStep * i;\n const x = centerX + (layerDistance * layer) * Math.cos(angle);\n const y = centerY + (layerDistance * layer) * Math.sin(angle);\n const body = matter_js_1.Bodies.circle(x, y, size, {\n angle: angle + Math.PI / 2\n });\n body.render.fillStyle = 'transparent';\n body.render.strokeStyle = '#aaa';\n body.render.lineWidth = 2;\n body.render.opacity = 1;\n matter_js_1.Body.setStatic(body, true);\n matter_js_1.World.add(this.engine.world, body);\n this.bodies.push(body); // Add to bodies array\n }\n layer++;\n }\n setInterval(() => {\n const body = this.bodies.shift();\n matter_js_1.Body.setStatic(body, !body.isStatic);\n this.bodies.push(body);\n }, 100 / this.engine.timing.timeScale);\n }\n manageBodyDynamics() {\n matter_js_1.Events.on(this.engine, 'beforeUpdate', () => {\n this.bodies.forEach((body) => {\n if (body.isStatic)\n return;\n // this gets used all over the place; calculate it once per update\n body.neighborsInView = this.getVisibleBodies(body);\n const forces = [];\n forces.push(matter_js_1.Vector.mult(this.getCurrentForce(body), 0.5));\n forces.push(matter_js_1.Vector.mult(this.getEdgeRepulsionForce(body, 100, 0.1), 5));\n forces.push(matter_js_1.Vector.mult(this.getSocialForce(body, 30), 0.2));\n forces.push(matter_js_1.Vector.mult(this.getAntiSocialForce(body, 50, 10), 0.05));\n const netForce = forces.reduce((acc, force) => matter_js_1.Vector.add(acc, force), { x: 0, y: 0 });\n // Apply the force as adjusted acceleration\n matter_js_1.Body.applyForce(body, body.position, { x: netForce.x * 0.001, y: netForce.y * 0.001 });\n // Enforce speed limit\n const currentSpeed = matter_js_1.Vector.magnitude(body.velocity);\n if (currentSpeed > this.speedLimit) {\n const scaledVelocity = matter_js_1.Vector.normalise(body.velocity);\n matter_js_1.Body.setVelocity(body, { x: scaledVelocity.x * this.speedLimit, y: scaledVelocity.y * this.speedLimit });\n }\n });\n });\n }\n manageBodyShape() {\n matter_js_1.Events.on(this.engine, 'beforeUpdate', () => {\n this.engine.world.bodies.forEach(body => {\n body.render.visible = body.isStatic;\n });\n });\n matter_js_1.Events.on(this.render, 'afterRender', (event) => {\n const { context } = this.render;\n this.engine.world.bodies.forEach(body => {\n if (body.render.visible)\n return;\n const speed = matter_js_1.Vector.magnitude(body.velocity);\n const maxSpeed = 20; // Adjust as necessary\n const elongationFactor = Math.min(speed / maxSpeed, 1);\n const length = this.circleSize + (this.circleSize * elongationFactor * 0.8);\n const width = this.circleSize * (1 - (elongationFactor * 0.5));\n context.save();\n context.translate(body.position.x, body.position.y);\n context.rotate(Math.atan2(body.velocity.y, body.velocity.x));\n context.scale(length / this.circleSize, width / this.circleSize);\n context.beginPath();\n context.arc(0, 0, this.circleSize, 0, 2 * Math.PI);\n context.fillStyle = '#F35';\n context.fill();\n context.restore();\n });\n });\n }\n getCurrentForce(body) {\n // Calculate the vector from the body to the center of the canvas\n const toCenter = matter_js_1.Vector.sub({ x: this.render.canvas.width / 2, y: this.render.canvas.height / 2 }, body.position);\n // Calculate the distance from the body to the center of the canvas\n const distanceToCenter = matter_js_1.Vector.magnitude(toCenter);\n // Define a scaling factor based on the distance to the center\n // The maximum distance possible (diagonal of the canvas) will result in a scaling factor of 1 (full force)\n // The minimum distance possible (0) will result in a scaling factor of 0 (no force)\n const maxDistance = Math.sqrt(Math.pow(this.render.canvas.width / 2, 2) + Math.pow(this.render.canvas.height / 2, 2));\n const forceScale = distanceToCenter / maxDistance * 2;\n // Calculate a perpendicular vector to create a circular motion (counter-clockwise)\n const perpendicular = matter_js_1.Vector.perp(toCenter);\n // Scale the force vector based on the distance to the center\n const scaledForce = matter_js_1.Vector.mult(matter_js_1.Vector.normalise(perpendicular), forceScale);\n return scaledForce;\n }\n getEdgeRepulsionForce(body, radius, baseForce) {\n // Calculate distance to the nearest edge on the X and Y axes\n const distanceToNearestEdgeX = Math.min(body.position.x, this.render.canvas.width - body.position.x);\n const distanceToNearestEdgeY = Math.min(body.position.y, this.render.canvas.height - body.position.y);\n // Use the new helper method to calculate repulsion force\n const forceX = this.calculateRepulsionForce(distanceToNearestEdgeX, radius, baseForce) * (body.position.x < this.render.canvas.width / 2 ? 1 : -1);\n const forceY = this.calculateRepulsionForce(distanceToNearestEdgeY, radius, baseForce) * (body.position.y < this.render.canvas.height / 2 ? 1 : -1);\n return { x: forceX, y: forceY };\n }\n getSocialForce(body, radius) {\n // aim for a gap of 5 degrees to 90 degrees\n const minGap = (5 * Math.PI) / 180;\n const maxGap = (90 * Math.PI) / 180;\n if (body.neighborsInView.length === 0) {\n return { x: 0, y: 0 }; // No force if no living neighbors\n }\n let angles = body.neighborsInView.map(other => {\n const toOther = matter_js_1.Vector.sub(other.position, body.position);\n return Math.atan2(toOther.y, toOther.x);\n }).sort((a, b) => a - b);\n angles.push(angles[0] + Math.PI * 2); // Include wrap-around angle\n let largestGap = 0;\n let gapMidpoint = 0;\n let gapRadius = 0;\n for (let i = 0; i < angles.length - 1; i++) {\n let gap = angles[i + 1] - angles[i];\n if (gap > largestGap && gap > minGap && gap < maxGap) {\n const midpointAngle = angles[i] + gap / 2;\n const distanceToNearestBody = this.getDistanceToNearestBody(body, midpointAngle, radius);\n // Half the distance to the nearest body in the direction of the gap midpoint will be the radius\n const circleRadius = distanceToNearestBody / 2;\n if (this.isCircleClear(body, midpointAngle, circleRadius, body.neighborsInView)) {\n largestGap = gap;\n gapMidpoint = midpointAngle;\n gapRadius = circleRadius;\n }\n }\n }\n if (largestGap > minGap && gapRadius > 0) {\n return this.steerTowards(body, gapMidpoint);\n }\n return { x: 0, y: 0 }; // No significant gap found, or the gap is not clear\n }\n getDistanceToNearestBody(body, angle, searchRadius) {\n let minDistance = searchRadius;\n this.engine.world.bodies.forEach(other => {\n if (other !== body && !other.isStatic) {\n const toOther = matter_js_1.Vector.sub(other.position, body.position);\n const distance = matter_js_1.Vector.magnitude(toOther);\n if (distance < minDistance) {\n const angleToOther = Math.atan2(toOther.y, toOther.x);\n if (Math.abs(angle - angleToOther) <= (10 * Math.PI) / 180) { // Check if within a 10-degree cone\n minDistance = distance;\n }\n }\n }\n });\n return minDistance;\n }\n isCircleClear(body, angle, radius, visibleBodies) {\n const circleCenter = matter_js_1.Vector.add(body.position, matter_js_1.Vector.mult({ x: Math.cos(angle), y: Math.sin(angle) }, radius));\n return !visibleBodies.some(other => {\n const distance = matter_js_1.Vector.magnitude(matter_js_1.Vector.sub(circleCenter, other.position));\n return distance < radius; // Check if any body is within the circle\n });\n }\n getAntiSocialForce(body, radius, baseForce) {\n const speedScalingFactor = Math.min(Math.max(matter_js_1.Vector.magnitude(body.velocity), 1), 2); // Scale with speed, within bounds\n let repulsionForce = { x: 0, y: 0 };\n body.neighborsInView.forEach(other => {\n if (other === body)\n return;\n const distanceVector = matter_js_1.Vector.sub(body.position, other.position);\n const distance = matter_js_1.Vector.magnitude(distanceVector);\n if (distance < radius) {\n const repulsionMagnitude = this.calculateRepulsionForce(distance, radius, baseForce) * speedScalingFactor;\n const normalizedDistanceVector = matter_js_1.Vector.normalise(distanceVector);\n const repulsion = matter_js_1.Vector.mult(normalizedDistanceVector, repulsionMagnitude);\n repulsionForce = matter_js_1.Vector.add(repulsionForce, repulsion);\n }\n });\n return repulsionForce;\n }\n getVisibleBodies(body) {\n const fieldOfViewRadians = (this.fieldOfViewDegrees * Math.PI) / 180; // Convert degrees to radians\n const searchRadius = this.viewDistance;\n // Define a bounding box around the body based on the search radius\n const boundingBox = {\n min: { x: body.position.x - searchRadius, y: body.position.y - searchRadius },\n max: { x: body.position.x + searchRadius, y: body.position.y + searchRadius }\n };\n // Use Query.region to get all bodies within the bounding box\n const bodiesInRegion = matter_js_1.Query.region(this.engine.world.bodies, boundingBox);\n // Filter these bodies by actual distance and field of view\n return bodiesInRegion.filter(other => {\n if (other === body)\n return false;\n const toOther = matter_js_1.Vector.sub(other.position, body.position);\n const distance = matter_js_1.Vector.magnitude(toOther);\n const bodyDirection = matter_js_1.Vector.create(Math.cos(body.angle), Math.sin(body.angle));\n const angleToOther = matter_js_1.Vector.angle(bodyDirection, toOther);\n // Check if within field of view and within the actual circular search radius\n return angleToOther <= fieldOfViewRadians / 2 && distance <= searchRadius;\n }).sort((a, b) => {\n // Sort by distance to the body of interest\n const distanceA = matter_js_1.Vector.magnitude(matter_js_1.Vector.sub(body.position, a.position));\n const distanceB = matter_js_1.Vector.magnitude(matter_js_1.Vector.sub(body.position, b.position));\n return distanceA - distanceB;\n });\n }\n steerTowards(body, angle) {\n // Calculate a target point in the direction of the angle, at some distance away from the body\n const targetPointDistance = 100; // Arbitrary distance to define the target point\n const targetPoint = {\n x: body.position.x + Math.cos(angle) * targetPointDistance,\n y: body.position.y + Math.sin(angle) * targetPointDistance\n };\n // Calculate the vector from the body's current position to the target point\n const desiredDirection = matter_js_1.Vector.sub(targetPoint, body.position);\n // Normalize the vector to get a unit vector in the desired direction\n return matter_js_1.Vector.normalise(desiredDirection); // This vector can be scaled as needed\n }\n calculateRepulsionForce(currentDistance, comfortableDistance, baseForce) {\n if (currentDistance < comfortableDistance) {\n const intensity = (comfortableDistance - currentDistance) / comfortableDistance;\n return baseForce * Math.pow(intensity, 2); // Quadratic increase for stronger effect near threshold\n }\n return 0;\n }\n}\n// When the page is fully loaded, start the simulation\nwindow.addEventListener('load', () => {\n new Simulation('simulationCanvas');\n});\n\n\n//# sourceURL=webpack://flow/./src/main.ts?"); /***/ }) diff --git a/src/main.ts b/src/main.ts index 1e8bfad..85e4040 100644 --- a/src/main.ts +++ b/src/main.ts @@ -206,8 +206,8 @@ class Simulation { } getSocialForce(body: BodyWithVisibleNeighbors, radius: number) { - // aim for a gap of 10 degrees to 90 degrees - const minGap = (10 * Math.PI) / 180; + // aim for a gap of 5 degrees to 90 degrees + const minGap = (5 * Math.PI) / 180; const maxGap = (90 * Math.PI) / 180; if (body.neighborsInView.length === 0) {