Thanks to TᴀᴄᴋᴇʀTᴀᴄᴋᴇʀ 🐰 I discovered the existence of chaos games: a method of creating a fractal, using a polygon and an initial point selected at random inside it. The mechanism is exquisitely mathematical and presents many variations and customizations. The results are very interesting: There are many ways to achieve this. For example, TackerTacker made this implementation with C2. I have chosen to use only sprites. By now the technology is mature enough to allow you to manage lots of thousands of elements on a single web page: why not take advantage of it? The animation below shows how Construct 3 holds up 50.000 (fifty thousand) sprites in motion: By optimizing the code, I believe it is possible to improve performance even more. By the way, let’s start with the code of this project, obviously also present on GitHub. The main function is this:

``````// c3Function ChaosGame_GenerateAllPoints(Quantity: number)
const quantity= localVars.Quantity;
const vertex = runtime.objects.Vertex.getAllInstances();
const nameObject = "Points";
const nameLayer = "Points";
const show = true;

const rules = ["normal"];

if (runtime.globalVars.Rule_NotEqualPrevious != "-") {
rules.push("not equal previous");
}
if (runtime.globalVars.Rule_NotEqualPrePrevious != "-") {
rules.push("not equal pre-previous");
}

Game.generateAllPoints({quantity, vertex, nameObject, nameLayer, show, rules});
``````

In the first part I declare some variables and define the rules to be used to draw the various points. The last line contains a reference to the `generateAllPoints` function present in the module `game.js`. Again I think that with a little more work we could further simplify the code but for the moment I’m interested in showing the process I used:

``````function generateAllPoints({
quantity,
vertex,
nameObject,
nameLayer,
show,
rules }) {

let x = choose(vertex).x;
let y = choose(vertex).y;

let previousVertex = [];

for (let i = 0; i < quantity; i++){
const point = PointsIntance.Create(nameObject,nameLayer, x, y);
point.isVisible = show;
point.setOrder(i);
point.setStarterPoint({x, y});
point.moveToStarterPoint();
point.setPreviousVertex(previousVertex);
const randomDestination = point.setRandomDestination(vertex);
previousVertex = point.getPreviousVertex();
point.moveToDestination();
x = randomDestination.x;
y = randomDestination.y;
point.colorPoint();
}
}
``````

Unlike my other experiments I am not working on variables added to object instances in Construct3. Instead, I chose to extend a class (“Sprite”) and work directly on this new class, the PointsInstance. In this way I can easily access a whole series of specific methods useful for my purpose:

• `Create(nameObject,nameLayer, x, y)`: create a new point in C3
• `setOrder(i)`: assigns a progressive index to each point
• `addRule(r)`: adds the rules that the point must respect
• `setRandomDestination(vertex)`: randomly chooses one of the allowed vertices towards which to draw the new point

With just these you can create something like this:

If, on the other hand, I add the ability to manage colors, and maybe increase the number of points up to 50,000, 100,000 or more, I can have a gallery of images like these:

It’s time to take a look at `point.js`, which is the `PointsIntance` class. One line of code is enough to create it:

``````class PointsIntance extends globalThis.ISpriteInstance {
constructor(){
super();
}
}
``````

Then, in the `main.js` file:

``````import PointsIntance from "./point.js";

runOnStartup(async  runtime =>  {
runtime.objects.Point.setInstanceClass(PointsIntance);
})
``````

For a more detailed explanation, I refer to the Construct 3 online guide.

Obviously it is not enough to declare the class, it is also necessary to fill it with methods useful for the purpose. `Create()` creates a new point:

``````static Create(nameObject, nameLayer, x, y) {
return g_runtime.objects[nameObject].createInstance(nameLayer,x, y);
}
``````

`_randomVertex(vertex)` instead chooses a random vertex from those available.

``````_randomVertex(vertex) {

const uniqueSet = new Set(vertex);
const previousVertex = this.previousVertex.length;

if (this.rules.has("not equal previous") && previousVertex >= 1) {

const toDelete = this.previousVertex[previousVertex-1];
uniqueSet.delete(toDelete);
}

if (this.rules.has("not equal pre-previous") && previousVertex >= 2) {
const toDelete = this.previousVertex[previousVertex-2];
uniqueSet.delete(toDelete);
}

const vertexCleaned = [...uniqueSet];
const index = Math.floor(Math.random() * vertexCleaned.length);
const randomVertex = vertexCleaned[index];
return randomVertex;
}
``````

To make things easier I decided to use `Set` instead of an array - that way I’m sure not to duplicate the rules and I can easily check which rules are matched to the point. However, I admit that the idea came to me from an article published on Medium (How to Remove Array Duplicates in ES6) a couple of years ago.

The other methods are for recording the starting position of the point and the ending position. However, one thing is worth noting:

``````setRandomDestination(vertex) {
const randomVertex = this._randomVertex(vertex);

this.previousVertex.push(randomVertex);

this.pointDestinationVertex = randomVertex;

const startX = this.pointStarter.x;
const startY = this.pointStarter.y;

const distanceX = (startX - randomVertex.x)/2;
const distanceY = (startY - randomVertex.y)/2;

const x = startX - distanceX;
const y = startY - distanceY;

this.pointDestination = {x, y};
return {x, y};
}
``````

In `setRandomDestination(vertex)` I calculate the target position of the point with a 1/2 ratio. But interesting effects can be obtained by varying this parameter. For example with

``````const distanceX = (startX - randomVertex.x)*2/3;
const distanceY = (startY - randomVertex.y)*2/3;
``````

you can get a design like this

As I said at the beginning, there are still some possible improvements and we could still add some parameters to the user interface. But for the moment I’ll stop here. I remember that, as usual, the code of this project is available on GitHub:

Tags:

Categorie:

Aggiornato: