diff --git a/examples/fish-school/fish-school.json b/examples/fish-school/fish-school.json index 4588818eb..63172e899 100644 --- a/examples/fish-school/fish-school.json +++ b/examples/fish-school/fish-school.json @@ -94,9 +94,6 @@ "disableInputWhenNotFocused": true, "mangledName": "Fish_32school_32interactions", "name": "Fish school interactions", - "oglFOV": 90, - "oglZFar": 500, - "oglZNear": 1, "r": 106, "standardSortMethod": true, "stopSoundsOnStartup": true, @@ -123,7 +120,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "5142863b-9264-4c1c-b7dc-b783f3cf2488", "width": 0, @@ -139,7 +135,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "eb7f8338-f67a-4b1a-a5ef-b670a3390ae8", "width": 0, @@ -155,7 +150,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "f1b60c74-c774-4b86-9db8-7df675f0f194", "width": 0, @@ -171,7 +165,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Directions", "persistentUuid": "afe05012-ae59-4a25-8674-3422b2775632", "width": 0, @@ -187,7 +180,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "4cd2c247-78fb-4d15-b064-b40407d46235", "width": 0, @@ -203,7 +195,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "0a8def84-394f-4acc-a6b4-dc6f2e2a2d6e", "width": 0, @@ -219,7 +210,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "7d62cfea-2269-4635-8b30-a265febd6725", "width": 0, @@ -235,7 +225,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "bdcf8b8e-6953-441a-a5c2-8c06704141ab", "width": 0, @@ -251,7 +240,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "2be65773-4dc4-4df9-a344-fb83154deae4", "width": 0, @@ -267,7 +255,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "Jellyfish", "persistentUuid": "4546a74d-2ef9-4244-a937-ca1b98e96148", "width": 0, @@ -629,9 +616,6 @@ "disableInputWhenNotFocused": true, "mangledName": "Fish_32school_32configuration", "name": "Fish school configuration", - "oglFOV": 90, - "oglZFar": 500, - "oglZNear": 1, "r": 106, "standardSortMethod": true, "stopSoundsOnStartup": true, @@ -658,7 +642,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "CohesionWeightSlider", "persistentUuid": "2e299379-30aa-4d1c-8850-d66767de298e", "width": 0, @@ -674,7 +657,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "CohesionWeightLabel", "persistentUuid": "a02041db-7cda-4cec-947f-b0f7eccff226", "width": 0, @@ -690,7 +672,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "SeparationWeightSlider", "persistentUuid": "8e908828-62aa-4ca7-b659-ff22b5012c54", "width": 0, @@ -706,7 +687,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "AlignmentWeightSlider", "persistentUuid": "6727ab35-69dc-441c-9620-f9d836f3e861", "width": 0, @@ -722,7 +702,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "SeparationWeightLabel", "persistentUuid": "f1f5531b-65d7-4ba8-a3b8-d3f98fc620c9", "width": 0, @@ -738,7 +717,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "AlignmentWeightLabel", "persistentUuid": "8ac31c5e-cc1b-4de0-a8d7-e63ef9963623", "width": 0, @@ -754,7 +732,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "FishCountSlider", "persistentUuid": "5470c75c-b777-4db7-bf02-e48afa490924", "width": 0, @@ -770,7 +747,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "FishCountLabel", "persistentUuid": "2be1df7e-5c23-4a4a-924c-dab1b5f9d900", "width": 0, @@ -786,7 +762,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "CohesionRadiusLabel2", "persistentUuid": "7d96e07a-65a2-43dd-b8f0-a13f41c93783", "width": 0, @@ -802,7 +777,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "SeparationRadiusLabel", "persistentUuid": "5c63b355-fd27-4904-b8b0-5193d79f329b", "width": 0, @@ -818,7 +792,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "AlignmentRadiusLabel", "persistentUuid": "fff875f4-95a4-4f6b-bbab-c24874b7902c", "width": 0, @@ -834,7 +807,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "CohesionRadiusSlider", "persistentUuid": "17487005-2abd-411c-b6b2-f2e5c36f681f", "width": 0, @@ -850,7 +822,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "SeparationRadiusSlider", "persistentUuid": "4c42c8de-4f77-4ec7-8999-ef0f39281ce2", "width": 0, @@ -866,7 +837,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "AlignmentRadiusSlider", "persistentUuid": "006a7e35-30ed-443d-ba7e-2b42d215bcb3", "width": 0, @@ -882,7 +852,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "FishSpeedLabel", "persistentUuid": "0d88873a-ea26-4977-bd58-96fe2d66f6ef", "width": 0, @@ -898,7 +867,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "FishAccelerationLabel", "persistentUuid": "cd50aa04-cc8f-4c21-b15b-a4ac0bab592d", "width": 0, @@ -914,7 +882,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "FishSpeedSlider", "persistentUuid": "d1bc3964-00b0-4487-98de-67a52ca19129", "width": 0, @@ -930,7 +897,6 @@ "customSize": false, "height": 0, "layer": "", - "locked": false, "name": "FishAccelerationSlider", "persistentUuid": "9ee6f291-d47f-467f-97b2-0f2889a303b4", "width": 0, @@ -6367,7 +6333,8 @@ } ] } - ] + ], + "eventsBasedObjects": [] }, { "author": "Tristan Rhodes (tristan@victrisgames.com), Entropy", @@ -7631,12 +7598,13 @@ } ] } - ] + ], + "eventsBasedObjects": [] }, { "author": "", - "category": "", - "description": "Simulates swarms or flocks movement following the separation, alignment, cohesion principles. The flock can be attracted to a location or avoid some obstacles.", + "category": "Movement", + "description": "Simulates swarms or flocks movement following the separation, alignment, cohesion principles. The flock can be attracted to a location or avoid some obstacles.\n\nThe [Fish School example](https://editor.gdevelop.io/?project=example://fish-school) shows how properties impact on the movement.", "extensionNamespace": "", "fullName": "Boids movement (experimental)", "helpPath": "https://en.wikipedia.org/wiki/Boids", @@ -7644,7 +7612,11 @@ "name": "BoidsMovement", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Glyphster Pack/Master/SVG/Restaurant/Restaurant_restaurant_seafood_animal_fish.svg", "shortDescription": "Simulates flocks movement.", - "version": "0.1.0", + "version": "0.1.2", + "origin": { + "identifier": "BoidsMovement", + "name": "gdevelop-extension-store" + }, "tags": [ "flock", "swarm", @@ -7694,7 +7666,7 @@ "events": [ { "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": "\ngdjs.__boidsExtension = gdjs.__boidsExtension || {};\n\n/**\n * A vector\n * @param {float} x - coordinate of the vectors starting position\n * @param {float} y - coordinate of the vectors starting position\n * @constructor\n */\ngdjs.__boidsExtension.Vector = /** @class */ (function () {\n \n /**\n * Create a vector.\n * @param {number} x\n * @param {number} y\n */\n function Vector(x, y) {\n this.x = x;\n this.y = y;\n }\n\n /**\n * @param {number} x\n * @param {number} y\n * @returns {Vector} this object\n */\n Vector.prototype.set = function (x, y) {\n this.x = x;\n this.y = y;\n return this;\n }\n\n /**\n * Adds a velocity to this vector\n * @param {Vector} referenceVector gets the velocity to be added\n * @returns {Vector} this object\n */\n Vector.prototype.add = function (referenceVector) {\n this.x += referenceVector.x;\n this.y += referenceVector.y;\n\n return this;\n }\n\n /**\n * Normalize the vector\n * @returns {Vector}\n */\n Vector.prototype.normalize = function () {\n var len = this.magnitude();\n if (len !== 0) this.multiply(1 / len);\n return this;\n }\n\n /**\n * Multiply vectors verticies\n * @param {float} amount\n * @returns {Vector}\n */\n Vector.prototype.multiply = function (amount) {\n this.x *= amount;\n this.y *= amount;\n return this;\n }\n\n /**\n * Divide vectors verticies\n * @param {float} amount\n * @returns {Vector}\n */\n Vector.prototype.divide = function (amount) {\n this.x /= amount;\n this.y /= amount;\n\n return this;\n }\n\n /**\n * Subtracts vectors verticies from a reference vector\n * @param {Vector} referenceVector\n * @returns {Vector}\n */\n Vector.prototype.subtract = function (referenceVector) {\n this.x -= referenceVector.x;\n this.y -= referenceVector.y;\n\n return this;\n }\n\n /**\n * Limits Vectors speed\n * @returns {Vector}\n */\n Vector.prototype.limit = function limit(max) {\n var mSq = this.magnitudeSquare();\n if (mSq > max * max) {\n this.divide(Math.sqrt(mSq)).multiply(max);\n }\n return this;\n }\n\n /**\n * Calculate the vectors direction\n * @returns {float}\n */\n Vector.prototype.heading = function () {\n let h = Math.atan2(this.y, this.x);\n return h;\n }\n\n /**\n * Calculate the vectors magnitude squared\n * @returns {float}\n */\n Vector.prototype.magnitudeSquare = function () {\n var x = this.x;\n var y = this.y;\n return x * x + y * y;\n };\n\n /**\n * Calculate the vectors magnitude\n * @returns {float}\n */\n Vector.prototype.magnitude = function () {\n return Math.sqrt(this.magnitudeSquare());\n }\n \n return Vector;\n}());\n\n/**\n * A boid agent\n * Methods for Separation, Cohesion, Alignment added\n * Inspired from The Nature of Code - Daniel Shiffman\n * http://natureofcode.com\n * \n * Original source code:\n * https://github.com/nature-of-code/noc-examples-processing/blob/master/chp06_agents/NOC_6_09_Flocking/Boid.pde\n */\ngdjs.__boidsExtension.Boid = /** @class */ (function () {\n\n /**\n * Create a boid agent.\n * @parameter {gdjs.RuntimeBehavior} behavior\n */\n function Boid(behavior) {\n this.behavior = behavior;\n this.acceleration = new gdjs.__boidsExtension.Vector(0, 0);\n this.velocity = new gdjs.__boidsExtension.Vector(gdjs.randomFloatInRange(-1, 1), gdjs.randomFloatInRange(-1, 1));\n this.separationDirection = new gdjs.__boidsExtension.Vector(0, 0);\n this.alignmentDirection = new gdjs.__boidsExtension.Vector(0, 0);\n this.cohesionDirection = new gdjs.__boidsExtension.Vector(0, 0);\n this.customIntentsDirectionSum = new gdjs.__boidsExtension.Vector(0, 0);\n this.separateWorkingVector = new gdjs.__boidsExtension.Vector(0, 0);\n }\n\n /**\n * Add a custom intent.\n * @param {Boid} boid\n * @param {number} directionX\n * @param {number} directionY\n * @param {number} weight\n */\n Boid.prototype.addIntent = function (directionX, directionY, weight) {\n\n if (directionX === 0 && directionY === 0) {\n return;\n }\n\n const length = Math.hypot(directionX, directionY);\n const unitX = directionX / length;\n const unitY = directionY / length;\n\n this.customIntentsDirectionSum.x += unitX * weight;\n this.customIntentsDirectionSum.y += unitY * weight;\n }\n\n /**\n * Vector between 2 boids.\n * @param {Boid} boid\n * @param {Vector} vector the vector to return the result\n * @returns {Vector} vector\n */\n Boid.prototype.getVectorTo = function (boid, vector) {\n const object = this.behavior.owner;\n const otherObject = boid.behavior.owner;\n let x = otherObject.x - object.x;\n let y = otherObject.y - object.y;\n vector.set(x, y);\n return vector;\n }\n\n /**\n * Applys the three rules of boids\n */\n Boid.prototype.flock = function () {\n const separationDirection = this.separate();\n const alignmentDirection = this.align();\n const cohesionDirection = this.cohesion();\n\n separationDirection.multiply(this.behavior.SeparationWeight());\n alignmentDirection.multiply(this.behavior.AlignmentWeight());\n cohesionDirection.multiply(this.behavior.CohesionWeight());\n\n const direction = separationDirection.add(alignmentDirection).add(cohesionDirection).add(this.customIntentsDirectionSum);\n this.acceleration.add(this.steer(direction));\n\n this.customIntentsDirectionSum.set(0, 0);\n }\n\n /**\n * Apply the acceleration from the steer force.\n */\n Boid.prototype.move = function () {\n const object = this.behavior.owner;\n const timeDelta = object.getElapsedTime(runtimeScene) / 1000;\n this.acceleration.multiply(timeDelta);\n\n const previousVelocityX = this.velocity.x;\n const previousVelocityY = this.velocity.y;\n\n this.velocity.add(this.acceleration);\n this.velocity.limit(this.behavior.MaxSpeed());\n this.acceleration.set(0, 0);\n\n if (this.behavior.ShouldRotate()) {\n let theta = this.velocity.heading() * 180 / Math.PI;\n object.setAngle(theta);\n }\n // Verlet integration\n object.setX(object.x + (previousVelocityX + this.velocity.x) / 2 * timeDelta);\n object.setY(object.y + (previousVelocityY + this.velocity.y) / 2 * timeDelta);\n }\n\n /**\n * Calculates and applies steering force towards a direction\n * @param desiredDirection {Vector}\n * @return {Vector} alignment force\n */\n Boid.prototype.steer = function (desiredDirection) {\n desiredDirection.normalize();\n desiredDirection.multiply(this.behavior.MaxSpeed());\n let steer = desiredDirection.subtract(this.velocity);\n steer.limit(this.behavior.MaxAcceleration());\n return steer;\n }\n\n /**\n * Separation: checks for nearby boids.\n * @return {Vector} separation direction\n */\n Boid.prototype.separate = function () {\n /** @type {BoidsManager} */\n const manager = runtimeScene.__boidsExtension.boidsManager;\n const separationRadius = this.behavior.SeparationRadius();\n this.separationDirection.set(0, 0);\n let diff = this.separateWorkingVector;\n const object = this.behavior.owner;\n\n const nearbyBoids = manager.getAllBoidsAround(this, separationRadius);\n for (let i = 0; i < nearbyBoids.length; i++) {\n const boid = nearbyBoids[i];\n const otherObject = boid.behavior.owner;\n const distance = object.getDistanceToObject(otherObject);\n if (distance > 0) {\n diff = boid.getVectorTo(this, diff);\n diff.normalize();\n diff.divide(distance);\n this.separationDirection.add(diff);\n }\n }\n if (this.separationDirection.magnitudeSquare() > 0) {\n this.separationDirection.normalize();\n }\n return this.separationDirection;\n }\n\n /**\n * Alignment: calculate the average velocity for every nearby boid in the system\n * @return {Vector} alignment direction\n */\n Boid.prototype.align = function () {\n /** @type {BoidsManager} */\n const manager = runtimeScene.__boidsExtension.boidsManager;\n const alignmentRadius = this.behavior.AlignmentRadius();\n this.alignmentDirection.set(0, 0);\n let count = 0;\n const object = this.behavior.owner;\n\n const nearbyBoids = manager.getAllBoidsAround(this, alignmentRadius);\n for (let i = 0; i < nearbyBoids.length; i++) {\n const boid = nearbyBoids[i];\n this.alignmentDirection.add(boid.velocity);\n count++;\n }\n if (count > 0) {\n this.alignmentDirection.normalize();\n }\n return this.alignmentDirection;\n }\n\n /**\n * Cohesion: calculate direction vector towards the average location of all nearby boids\n * @return {Vector} cohesion direction\n */\n Boid.prototype.cohesion = function () {\n /** @type {BoidsManager} */\n const manager = runtimeScene.__boidsExtension.boidsManager;\n const cohesionRadius = this.behavior.CohesionRadius();\n let count = 0;\n let sumX = 0;\n let sumY = 0;\n const object = this.behavior.owner;\n\n const nearbyBoids = manager.getAllBoidsAround(this, cohesionRadius);\n for (let i = 0; i < nearbyBoids.length; i++) {\n const boid = nearbyBoids[i];\n const otherObject = boid.behavior.owner;\n sumX += otherObject.getCenterXInScene();\n sumY += otherObject.getCenterYInScene();\n count++;\n }\n if (count > 0) {\n sumX /= count;\n sumY /= count;\n }\n // A vector pointing from the position to the target\n this.cohesionDirection.set(\n sumX - object.getCenterXInScene(),\n sumY - object.getCenterYInScene()\n )\n return this.cohesionDirection.normalize();\n }\n\n return Boid;\n}());\n\n/**\n * A boids manager\n */\ngdjs.__boidsExtension.BoidsManager = /** @class */ (function () {\n /**\n * Create a manager.\n */\n function BoidsManager() {\n /**\n * @type {Map}\n */\n this.boids = new Map();\n this.boidsRBush = new rbush();\n }\n\n /**\n * Create and register a boid.\n * @param {gdjs.RuntimeBehavior} behavior\n * @returns {Boid} the created Boid\n */\n BoidsManager.prototype.add = function (behavior) {\n const boid = new gdjs.__boidsExtension.Boid(behavior);\n this.boids.set(behavior.owner.id, boid);\n this.addToRBush(boid);\n return boid;\n }\n\n /**\n * Add a boid to the R-Tree.\n * @param {Boid} boid\n */\n BoidsManager.prototype.addToRBush = function (boid) {\n if (boid.currentRBushAABB) {\n boid.currentRBushAABB.updateAABBFromOwner();\n }\n else {\n boid.currentRBushAABB = new gdjs.BehaviorRBushAABB(\n boid.behavior\n );\n }\n this.boidsRBush.insert(boid.currentRBushAABB);\n }\n\n /**\n * Unregister the boid.\n * @param {gdjs.RuntimeBehavior} behavior\n */\n BoidsManager.prototype.remove = function (behavior) {\n this.boids.delete(behavior.owner.id);\n\n this.removeFromRBush(behavior.__boidsExtension.boid.currentRBushAABB);\n }\n\n /**\n * Remove a boid from the R-Tree.\n * @param {Boid} boid\n */\n BoidsManager.prototype.removeFromRBush = function (boid) {\n this.boidsRBush.remove(boid.currentRBushAABB);\n }\n\n /**\n * Move all instances.\n */\n BoidsManager.prototype.moveAll = function () {\n this.boids.forEach(boid => boid.flock());\n this.boids.forEach(boid => {\n boid.move();\n this.removeFromRBush(boid);\n this.addToRBush(boid);\n });\n }\n\n /**\n * Returns all the boids around the specified boid.\n * @param {Boid} boid\n * @param {number} radius\n * @return {Boid[]} An array with all boids near the object.\n */\n BoidsManager.prototype.getAllBoidsAround = function (boid, radius, results) {\n const object = boid.behavior.owner;\n\n const x = object.getCenterXInScene();\n const y = object.getCenterYInScene();\n const searchArea = gdjs.staticObject(\n gdjs.__boidsExtension.BoidsManager.prototype.getAllBoidsAround\n );\n searchArea.minX = x - radius;\n searchArea.minY = y - radius;\n searchArea.maxX = x + radius;\n searchArea.maxY = y + radius;\n // TODO The R-Tree should not allocate an array.\n const nearbys = this.boidsRBush.search(searchArea);\n\n const radiusSq = radius * radius;\n const result = gdjs.staticArray(\n gdjs.__boidsExtension.BoidsManager.prototype.getAllBoidsAround\n );\n result.length = 0;\n // Cap the number of boids between 20 and 40.\n const step = Math.ceil(nearbys.length / 40);\n for (let i = 0; i < nearbys.length; i += step) {\n const behavior = nearbys[i].behavior;\n const otherObject = behavior.owner;\n if (object === otherObject) {\n continue;\n }\n const distanceSq = object.getSqDistanceToObject(otherObject);\n if (distanceSq < radiusSq) {\n result.push(behavior.__boidsExtension.boid);\n }\n }\n return result;\n }\n\n return BoidsManager;\n}());\n\nruntimeScene.__boidsExtension = runtimeScene.__boidsExtension || {};\nruntimeScene.__boidsExtension.boidsManager = new gdjs.__boidsExtension.BoidsManager();\n", + "inlineCode": "\ngdjs.__boidsExtension = gdjs.__boidsExtension || {};\n\n/**\n * A vector\n * @param {float} x - coordinate of the vectors starting position\n * @param {float} y - coordinate of the vectors starting position\n * @constructor\n */\ngdjs.__boidsExtension.Vector = /** @class */ (function () {\n \n /**\n * Create a vector.\n * @param {number} x\n * @param {number} y\n */\n function Vector(x, y) {\n this.x = x;\n this.y = y;\n }\n\n /**\n * @param {number} x\n * @param {number} y\n * @returns {Vector} this object\n */\n Vector.prototype.set = function (x, y) {\n this.x = x;\n this.y = y;\n return this;\n }\n\n /**\n * Adds a velocity to this vector\n * @param {Vector} referenceVector gets the velocity to be added\n * @returns {Vector} this object\n */\n Vector.prototype.add = function (referenceVector) {\n this.x += referenceVector.x;\n this.y += referenceVector.y;\n\n return this;\n }\n\n /**\n * Normalize the vector\n * @returns {Vector}\n */\n Vector.prototype.normalize = function () {\n var len = this.magnitude();\n if (len !== 0) this.multiply(1 / len);\n return this;\n }\n\n /**\n * Multiply vectors verticies\n * @param {float} amount\n * @returns {Vector}\n */\n Vector.prototype.multiply = function (amount) {\n this.x *= amount;\n this.y *= amount;\n return this;\n }\n\n /**\n * Divide vectors verticies\n * @param {float} amount\n * @returns {Vector}\n */\n Vector.prototype.divide = function (amount) {\n this.x /= amount;\n this.y /= amount;\n\n return this;\n }\n\n /**\n * Subtracts vectors verticies from a reference vector\n * @param {Vector} referenceVector\n * @returns {Vector}\n */\n Vector.prototype.subtract = function (referenceVector) {\n this.x -= referenceVector.x;\n this.y -= referenceVector.y;\n\n return this;\n }\n\n /**\n * Limits Vectors speed\n * @returns {Vector}\n */\n Vector.prototype.limit = function limit(max) {\n var mSq = this.magnitudeSquare();\n if (mSq > max * max) {\n this.divide(Math.sqrt(mSq)).multiply(max);\n }\n return this;\n }\n\n /**\n * Calculate the vectors direction\n * @returns {float}\n */\n Vector.prototype.heading = function () {\n let h = Math.atan2(this.y, this.x);\n return h;\n }\n\n /**\n * Calculate the vectors magnitude squared\n * @returns {float}\n */\n Vector.prototype.magnitudeSquare = function () {\n var x = this.x;\n var y = this.y;\n return x * x + y * y;\n };\n\n /**\n * Calculate the vectors magnitude\n * @returns {float}\n */\n Vector.prototype.magnitude = function () {\n return Math.sqrt(this.magnitudeSquare());\n }\n \n return Vector;\n}());\n\n/**\n * A boid agent\n * Methods for Separation, Cohesion, Alignment added\n * Inspired from The Nature of Code - Daniel Shiffman\n * http://natureofcode.com\n * \n * Original source code:\n * https://github.com/nature-of-code/noc-examples-processing/blob/master/chp06_agents/NOC_6_09_Flocking/Boid.pde\n */\ngdjs.__boidsExtension.Boid = /** @class */ (function () {\n\n /**\n * Create a boid agent.\n * @parameter {gdjs.RuntimeBehavior} behavior\n */\n function Boid(behavior) {\n this.behavior = behavior;\n this.acceleration = new gdjs.__boidsExtension.Vector(0, 0);\n this.velocity = new gdjs.__boidsExtension.Vector(gdjs.randomFloatInRange(-1, 1), gdjs.randomFloatInRange(-1, 1));\n this.separationDirection = new gdjs.__boidsExtension.Vector(0, 0);\n this.alignmentDirection = new gdjs.__boidsExtension.Vector(0, 0);\n this.cohesionDirection = new gdjs.__boidsExtension.Vector(0, 0);\n this.customIntentsDirectionSum = new gdjs.__boidsExtension.Vector(0, 0);\n this.separateWorkingVector = new gdjs.__boidsExtension.Vector(0, 0);\n }\n\n /**\n * Add a custom intent.\n * @param {Boid} boid\n * @param {number} directionX\n * @param {number} directionY\n * @param {number} weight\n */\n Boid.prototype.addIntent = function (directionX, directionY, weight) {\n\n if (directionX === 0 && directionY === 0) {\n return;\n }\n\n const length = Math.hypot(directionX, directionY);\n const unitX = directionX / length;\n const unitY = directionY / length;\n\n this.customIntentsDirectionSum.x += unitX * weight;\n this.customIntentsDirectionSum.y += unitY * weight;\n }\n\n /**\n * Vector between 2 boids.\n * @param {Boid} boid\n * @param {Vector} vector the vector to return the result\n * @returns {Vector} vector\n */\n Boid.prototype.getVectorTo = function (boid, vector) {\n const object = this.behavior.owner;\n const otherObject = boid.behavior.owner;\n let x = otherObject.x - object.x;\n let y = otherObject.y - object.y;\n vector.set(x, y);\n return vector;\n }\n\n /**\n * Applys the three rules of boids\n */\n Boid.prototype.flock = function () {\n const separationDirection = this.separate();\n const alignmentDirection = this.align();\n const cohesionDirection = this.cohesion();\n\n separationDirection.multiply(this.behavior.SeparationWeight());\n alignmentDirection.multiply(this.behavior.AlignmentWeight());\n cohesionDirection.multiply(this.behavior.CohesionWeight());\n\n const direction = separationDirection.add(alignmentDirection).add(cohesionDirection).add(this.customIntentsDirectionSum);\n this.acceleration.add(this.steer(direction));\n\n this.customIntentsDirectionSum.set(0, 0);\n }\n\n /**\n * Apply the acceleration from the steer force.\n */\n Boid.prototype.move = function () {\n const object = this.behavior.owner;\n const timeDelta = object.getElapsedTime(runtimeScene) / 1000;\n this.acceleration.multiply(timeDelta);\n\n const previousVelocityX = this.velocity.x;\n const previousVelocityY = this.velocity.y;\n\n this.velocity.add(this.acceleration);\n this.velocity.limit(this.behavior.MaxSpeed());\n this.acceleration.set(0, 0);\n\n if (this.behavior.ShouldRotate()) {\n let theta = this.velocity.heading() * 180 / Math.PI;\n object.setAngle(theta);\n }\n // Verlet integration\n object.setX(object.x + (previousVelocityX + this.velocity.x) / 2 * timeDelta);\n object.setY(object.y + (previousVelocityY + this.velocity.y) / 2 * timeDelta);\n }\n\n /**\n * Calculates and applies steering force towards a direction\n * @param desiredDirection {Vector}\n * @return {Vector} alignment force\n */\n Boid.prototype.steer = function (desiredDirection) {\n desiredDirection.normalize();\n desiredDirection.multiply(this.behavior.MaxSpeed());\n let steer = desiredDirection.subtract(this.velocity);\n steer.limit(this.behavior.MaxAcceleration());\n return steer;\n }\n\n /**\n * Separation: checks for nearby boids.\n * @return {Vector} separation direction\n */\n Boid.prototype.separate = function () {\n /** @type {BoidsManager} */\n const manager = runtimeScene.__boidsExtension.boidsManager;\n const separationRadius = this.behavior.SeparationRadius();\n this.separationDirection.set(0, 0);\n let diff = this.separateWorkingVector;\n const object = this.behavior.owner;\n\n const nearbyBoids = manager.getAllBoidsAround(this, separationRadius);\n for (let i = 0; i < nearbyBoids.length; i++) {\n const boid = nearbyBoids[i];\n const otherObject = boid.behavior.owner;\n const distance = object.getDistanceToObject(otherObject);\n if (distance > 0) {\n diff = boid.getVectorTo(this, diff);\n diff.normalize();\n diff.divide(distance);\n this.separationDirection.add(diff);\n }\n }\n if (this.separationDirection.magnitudeSquare() > 0) {\n this.separationDirection.normalize();\n }\n return this.separationDirection;\n }\n\n /**\n * Alignment: calculate the average velocity for every nearby boid in the system\n * @return {Vector} alignment direction\n */\n Boid.prototype.align = function () {\n /** @type {BoidsManager} */\n const manager = runtimeScene.__boidsExtension.boidsManager;\n const alignmentRadius = this.behavior.AlignmentRadius();\n this.alignmentDirection.set(0, 0);\n let count = 0;\n const object = this.behavior.owner;\n\n const nearbyBoids = manager.getAllBoidsAround(this, alignmentRadius);\n for (let i = 0; i < nearbyBoids.length; i++) {\n const boid = nearbyBoids[i];\n this.alignmentDirection.add(boid.velocity);\n count++;\n }\n if (count > 0) {\n this.alignmentDirection.normalize();\n }\n return this.alignmentDirection;\n }\n\n /**\n * Cohesion: calculate direction vector towards the average location of all nearby boids\n * @return {Vector} cohesion direction\n */\n Boid.prototype.cohesion = function () {\n /** @type {BoidsManager} */\n const manager = runtimeScene.__boidsExtension.boidsManager;\n const cohesionRadius = this.behavior.CohesionRadius();\n let count = 0;\n let sumX = 0;\n let sumY = 0;\n const object = this.behavior.owner;\n\n const nearbyBoids = manager.getAllBoidsAround(this, cohesionRadius);\n for (let i = 0; i < nearbyBoids.length; i++) {\n const boid = nearbyBoids[i];\n const otherObject = boid.behavior.owner;\n sumX += otherObject.getCenterXInScene();\n sumY += otherObject.getCenterYInScene();\n count++;\n }\n if (count > 0) {\n sumX /= count;\n sumY /= count;\n }\n // A vector pointing from the position to the target\n this.cohesionDirection.set(\n sumX - object.getCenterXInScene(),\n sumY - object.getCenterYInScene()\n )\n return this.cohesionDirection.normalize();\n }\n\n return Boid;\n}());\n\n/**\n * A boids manager\n */\ngdjs.__boidsExtension.BoidsManager = /** @class */ (function () {\n /**\n * Create a manager.\n */\n function BoidsManager() {\n /**\n * @type {Map}\n */\n this.boids = new Map();\n this.boidsRBush = new rbush();\n }\n\n /**\n * Create and register a boid.\n * @param {gdjs.RuntimeBehavior} behavior\n * @returns {Boid} the created Boid\n */\n BoidsManager.prototype.add = function (behavior) {\n const boid = new gdjs.__boidsExtension.Boid(behavior);\n this.boids.set(behavior.owner.id, boid);\n this.addToRBush(boid);\n return boid;\n }\n\n /**\n * Add a boid to the R-Tree.\n * @param {Boid} boid\n */\n BoidsManager.prototype.addToRBush = function (boid) {\n if (boid.currentRBushAABB) {\n boid.currentRBushAABB.updateAABBFromOwner();\n }\n else {\n boid.currentRBushAABB = new gdjs.BehaviorRBushAABB(\n boid.behavior\n );\n }\n this.boidsRBush.insert(boid.currentRBushAABB);\n }\n\n /**\n * Unregister the boid.\n * @param {gdjs.RuntimeBehavior} behavior\n */\n BoidsManager.prototype.remove = function (behavior) {\n this.boids.delete(behavior.owner.id);\n\n this.removeFromRBush(behavior.__boidsExtension.boid.currentRBushAABB);\n }\n\n /**\n * Remove a boid from the R-Tree.\n * @param {Boid} boid\n */\n BoidsManager.prototype.removeFromRBush = function (boid) {\n this.boidsRBush.remove(boid.currentRBushAABB);\n }\n\n /**\n * Move all instances.\n */\n BoidsManager.prototype.moveAll = function () {\n this.boids.forEach(boid => boid.flock());\n this.boids.forEach(boid => {\n boid.move();\n this.removeFromRBush(boid);\n this.addToRBush(boid);\n });\n }\n\n /**\n * Returns all the boids around the specified boid.\n * @param {Boid} boid\n * @param {number} radius\n * @return {Boid[]} An array with all boids near the object.\n */\n BoidsManager.prototype.getAllBoidsAround = function (boid, radius, results) {\n const object = boid.behavior.owner;\n\n const x = object.getCenterXInScene();\n const y = object.getCenterYInScene();\n const searchArea = gdjs.staticObject(\n gdjs.__boidsExtension.BoidsManager.prototype.getAllBoidsAround\n );\n searchArea.minX = x - radius;\n searchArea.minY = y - radius;\n searchArea.maxX = x + radius;\n searchArea.maxY = y + radius;\n // TODO The R-Tree should not allocate an array.\n const nearbys = this.boidsRBush.search(searchArea);\n\n const radiusSq = radius * radius;\n const result = gdjs.staticArray(\n gdjs.__boidsExtension.BoidsManager.prototype.getAllBoidsAround\n );\n result.length = 0;\n // Cap the number of boids between 20 and 40.\n const step = Math.ceil(nearbys.length / 40);\n for (let i = 0; i < nearbys.length; i += step) {\n const behavior = nearbys[i].behavior;\n const otherObject = behavior.owner;\n if (object === otherObject) {\n continue;\n }\n const distanceSq = object.getSqDistanceToObject(otherObject);\n if (distanceSq < radiusSq) {\n result.push(behavior.__boidsExtension.boid);\n }\n }\n return result;\n }\n\n return BoidsManager;\n}());\n", "parameterObjects": "", "useStrict": true, "eventsSheetExpanded": true @@ -7752,7 +7724,7 @@ }, { "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": "\r\nconst object = objects[0];\r\nconst behaviorName = eventsFunctionContext.getBehaviorName(\"Behavior\");\r\nconst behavior = object.getBehavior(behaviorName);\r\nbehavior.__boidsExtension = behavior.__boidsExtension || {};\r\nbehavior.__boidsExtension.boid = runtimeScene.__boidsExtension.boidsManager.add(behavior);", + "inlineCode": "\r\nruntimeScene.__boidsExtension = runtimeScene.__boidsExtension || {};\r\nruntimeScene.__boidsExtension.boidsManager = runtimeScene.__boidsExtension.boidsManager || new gdjs.__boidsExtension.BoidsManager();\r\n\r\nconst object = objects[0];\r\nconst behaviorName = eventsFunctionContext.getBehaviorName(\"Behavior\");\r\nconst behavior = object.getBehavior(behaviorName);\r\nbehavior.__boidsExtension = behavior.__boidsExtension || {};\r\nbehavior.__boidsExtension.boid = runtimeScene.__boidsExtension.boidsManager.add(behavior);", "parameterObjects": "Object", "useStrict": true, "eventsSheetExpanded": true @@ -9740,7 +9712,8 @@ } ] } - ] + ], + "eventsBasedObjects": [] } ], "externalLayouts": [], diff --git a/examples/fish-school/thumbnail.png b/examples/fish-school/thumbnail.png new file mode 100644 index 000000000..11c6bf9cf Binary files /dev/null and b/examples/fish-school/thumbnail.png differ