From 9e836e491caa4674c2ffe6c62f50e1fd0b03ffbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 9 Oct 2022 13:48:01 +0200 Subject: [PATCH] [Fish school] Upgrade the Boids extension. Don't show in changelog --- examples/fish-school/fish-school.json | 57 +++++++------------------- examples/fish-school/thumbnail.png | Bin 0 -> 26492 bytes 2 files changed, 15 insertions(+), 42 deletions(-) create mode 100644 examples/fish-school/thumbnail.png 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 0000000000000000000000000000000000000000..11c6bf9cfce3bcba02bd65efabcb14b21f6b2281 GIT binary patch literal 26492 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 literal 0 HcmV?d00001