finishing the project

This commit is contained in:
DU CASSOULET 2024-01-28 22:44:27 +01:00
parent ba53f23f30
commit e16c31fbbe
16 changed files with 2311 additions and 209 deletions

View File

@ -1,2 +1,2 @@
# Particle Game
# Particle Sandbox

BIN
assets/fonts/Rubik.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 10l5 5 5-5z"/></svg>

After

Width:  |  Height:  |  Size: 171 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>

After

Width:  |  Height:  |  Size: 198 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>

After

Width:  |  Height:  |  Size: 188 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M8 5v14l11-7z"/></svg>

After

Width:  |  Height:  |  Size: 170 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><g><path d="M12,5V2L8,6l4,4V7c3.31,0,6,2.69,6,6c0,2.97-2.17,5.43-5,5.91v2.02c3.95-0.49,7-3.85,7-7.93C20,8.58,16.42,5,12,5z"/><path d="M6,13c0-1.65,0.67-3.15,1.76-4.24L6.34,7.34C4.9,8.79,4,10.79,4,13c0,4.08,3.05,7.44,7,7.93v-2.02 C8.17,18.43,6,15.97,6,13z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 456 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/></svg>

After

Width:  |  Height:  |  Size: 194 B

View File

@ -3,14 +3,83 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="script/main.js"></script>
<title>Particle Sandbox</title>
<script defer src="script/main.js"></script>
<link rel="stylesheet" href="style/main.css" />
<link
rel="shortcut icon"
href="assets/images/ico/favicon.ico"
type="image/x-icon"
/>
</head>
<body>
<canvas></canvas>
<section class="gui">
<div class="content">
<div class="head">
<h1 class="title">Particle Sandbox</h1>
<div class="head-buttons">
<button class="play-pause">
<img
src="/assets/images/svg/play_arrow.svg"
alt="play-pause"
height="16"
width="16"
/>
</button>
<button class="reset">
<img
src="/assets/images/svg/restart.svg"
alt="reset"
height="16"
width="16"
/>
</button>
<button class="minimize">
<img
src="/assets/images/svg/arrow_down.svg"
alt="minimize"
height="16"
width="16"
/>
</button>
</div>
</div>
<div class="category">
<div class="category-head">
<button class="add particle">+</button>
<h2 class="label">Particles</h2>
</div>
<div class="particle-containers"></div>
</div>
<div>
<div class="category-head">
<button class="add interaction">+</button>
<h2 class="label">Interactions</h2>
</div>
<div class="interaction-containers"></div>
</div>
<div class="down-buttons">
<button class="save">
<img
src="assets/images/svg/upload.svg"
alt="upload"
height="16"
width="16"
/>
<p>Export this scene</p>
</button>
<button class="load">
<img
src="assets/images/svg/download.svg"
alt="download"
height="16"
width="16"
/>
<p>Import a scene</p>
</button>
</div>
</div>
</section>
</body>
</html>

View File

@ -1,4 +1,24 @@
window.onload = function () {
var _a, _b, _c, _d, _e, _f, _g;
var SLIDER_MIN = 50;
var SLIDER_MAX = 1000;
var SLIDER_STEP = 50;
var SLIDER_DEFAULT = 350;
var MAX_INTERACTION_NUMBER = 20;
var MIN_INTENSITY = 1;
var MAX_INTENSITY = 1000;
var DEFAULT_INTENSITY = 100;
var INTENSITY_STEP = 1;
var INTENSITY_DIVIDER = 10000;
var DISTANCE_MIN = 10;
var DISTANCE_MAX = 500;
var DISTANCE_STEP = 10;
var DISTANCE_DEFAULT = 200;
var Group1 = 0;
var Group2 = 1;
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var Particle = /** @class */ (function () {
function Particle(x, y, color, radius) {
this.vx = 0;
@ -8,24 +28,236 @@ window.onload = function () {
this.color = color;
this.radius = radius;
}
Particle.prototype.draw = function () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
Particle.prototype.draw = function (ctx) {
ctx.fillStyle = ColorsHex[this.color];
ctx.fillRect(this.x, this.y, this.radius, this.radius);
};
Particle.DEFAULT_RADIUS = 2.5;
Particle.DEFAULT_RADIUS = 5;
return Particle;
}());
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
window.addEventListener("resize", function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
var paused = true;
var particles = [];
var interactions = [];
var ColorsHex = Object.freeze({
red: "#f91d4d",
green: "#0db342",
blue: "#4a4ad7",
yellow: "#f0e246",
magenta: "#d742d7",
cyan: "#42cedb",
});
var colors = {
red: null,
green: null,
blue: null,
yellow: null,
magenta: null,
cyan: null,
};
function reset() {
particles.forEach(function (particle) {
particle.x = randomInt(0, canvas.width);
particle.y = randomInt(0, canvas.height);
});
paused = true;
var img = document.querySelector(".play-pause img");
img.src = "/assets/images/svg/play_arrow.svg";
}
function addColor(color, autoGroup) {
if (autoGroup === void 0) { autoGroup = true; }
if (autoGroup)
colors[color] = makeGroup(350, color, { radius: Particle.DEFAULT_RADIUS });
var container = document.querySelector(".particle-containers");
var particle = document.createElement("div");
particle.classList.add("particle-container");
particle.classList.add(color);
var head = document.createElement("div");
head.classList.add("container-head");
head.innerHTML = "<div class=\"color\"></div><h3 class=\"sublabel\">".concat(color, "</h3><button class=\"close particle\">\u2A2F</button>");
var close = head.querySelector(".close");
close.addEventListener("click", function () {
particles = particles.filter(function (p) { return p.color !== color; });
colors[color] = null;
container.removeChild(particle);
interactions = interactions.filter(function (itn) {
var _a, _b, _c, _d;
if (itn.group1[0].color === color || itn.group2[0].color === color) {
(_d = (_c = (_b = (_a = document
.querySelector(".interaction-container .colors .color.".concat(color))) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.parentElement) === null || _c === void 0 ? void 0 : _c.parentElement) === null || _d === void 0 ? void 0 : _d.remove();
return false;
}
return true;
});
});
var particleSetting = document.createElement("h4");
particleSetting.classList.add("setting");
particleSetting.innerHTML = "Particle number : <span class=\"setting-value\">".concat(SLIDER_DEFAULT.toLocaleString("en-US"), "</span>");
var range = document.createElement("div");
range.classList.add("range");
range.innerHTML = "<p class=\"value\">".concat(SLIDER_MIN.toLocaleString("en-US"), "</p><input class=\"range-content\" type=\"range\" min=\"").concat(SLIDER_MIN, "\" max=\"").concat(SLIDER_MAX, "\" step=\"").concat(SLIDER_STEP, "\" value=\"").concat(SLIDER_DEFAULT, "\"><p class=\"value\">").concat(SLIDER_MAX.toLocaleString("en-US"), "</p>");
var input = range.querySelector("input");
input.addEventListener("input", function () {
var value = parseInt(input.value);
particleSetting.innerHTML = "Particle number : <span class=\"setting-value\"Z>".concat(value.toLocaleString("en-US"), "</span>");
particles = particles.filter(function (p) { return p.color !== color; });
colors[color] = makeGroup(value, color, {
radius: Particle.DEFAULT_RADIUS,
});
interactions = interactions.map(function (itn) {
if (itn.group1[0].color === color) {
itn.group1 = colors[color];
}
else if (itn.group2[0].color === color) {
itn.group2 = colors[color];
}
return itn;
});
});
particle.appendChild(head);
particle.appendChild(particleSetting);
particle.appendChild(range);
container.appendChild(particle);
}
function colorSelector(selected, group, interaction) {
var selector = document.createElement("div");
selector.classList.add("color-selector");
var selectedColor = document.createElement("button");
selectedColor.classList.add("color");
selectedColor.classList.add(selected);
var colorList = document.createElement("div");
colorList.classList.add("color-list");
selectedColor.addEventListener("click", function (e) {
e.stopPropagation();
if (colorList.classList.contains("show")) {
colorList.classList.remove("show");
}
else {
var colorButtons = document.querySelectorAll(".color-list");
colorButtons.forEach(function (colorButton) {
if (colorButton.classList.contains("show")) {
colorButton.classList.remove("show");
}
});
colorList.classList.add("show");
colorList.innerHTML = "";
var _loop_1 = function (color) {
if (!colors[color])
return "continue";
var colorButton = document.createElement("button");
colorButton.classList.add("color");
colorButton.classList.add(color);
colorButton.addEventListener("click", function (e) {
e.stopPropagation();
selectedColor.classList.remove(selected);
selected = color;
selectedColor.classList.add(selected);
colorList.classList.remove("show");
switch (group) {
case Group1:
interaction.group1 = colors[color];
break;
case Group2:
interaction.group2 = colors[color];
break;
}
});
colorList.appendChild(colorButton);
};
for (var color in colors) {
_loop_1(color);
}
}
});
selector.appendChild(selectedColor);
selector.appendChild(colorList);
return selector;
}
function addInteraction(interaction) {
interactions.push(interaction);
var isAttractionForce = true;
var container = document.querySelector(".interaction-containers");
var interactionContainer = document.createElement("div");
interactionContainer.classList.add("interaction-container");
var colors = document.createElement("div");
colors.classList.add("colors");
var color1 = colorSelector(interaction.group1[0].color, 0, interaction);
var color2 = colorSelector(interaction.group2[0].color, 1, interaction);
var liaison = document.createElement("h3");
liaison.classList.add("setting");
if (isAttractionForce) {
liaison.textContent = "is attracted to";
}
else {
liaison.textContent = "is repulsed by";
}
var close = document.createElement("button");
close.classList.add("close");
close.classList.add("interaction");
close.textContent = "";
colors.appendChild(color1);
colors.appendChild(liaison);
colors.appendChild(color2);
colors.appendChild(close);
var forceContainer = document.createElement("div");
forceContainer.classList.add("force-container");
forceContainer.innerHTML = "<h4 class=\"setting\">Force Type</h4><div class=\"force-types\"><button class=\"force-type selected\">Attraction</button><button class=\"force-type\">Repulsion</button></div>";
var forceTypes = forceContainer.querySelectorAll(".force-type");
forceTypes.forEach(function (forceType) {
forceType.addEventListener("click", function () {
forceTypes.forEach(function (ft) { return ft.classList.remove("selected"); });
forceType.classList.add("selected");
isAttractionForce = forceType.textContent === "Attraction";
if (isAttractionForce) {
liaison.textContent = "is attracted to";
interaction.options.g = -Math.abs(interaction.options.g);
}
else {
liaison.textContent = "is repulsed by";
interaction.options.g = Math.abs(interaction.options.g);
}
});
});
var intensityContainer = document.createElement("h4");
intensityContainer.classList.add("setting");
intensityContainer.innerHTML = "Intensity : <span class=\"setting-value\">".concat(DEFAULT_INTENSITY.toLocaleString("en-US"), "</span>");
var intensityRange = document.createElement("div");
intensityRange.classList.add("range");
intensityRange.innerHTML = "<p class=\"value\">".concat(MIN_INTENSITY.toLocaleString("en-US"), "</p><input class=\"range-content\" type=\"range\" min=\"").concat(MIN_INTENSITY, "\" max=\"").concat(MAX_INTENSITY, "\" step=\"").concat(INTENSITY_STEP, "\" value=\"").concat(DEFAULT_INTENSITY, "\"><p class=\"value\">").concat(MAX_INTENSITY.toLocaleString("en-US"), "</p>");
var intensityInput = intensityRange.querySelector("input");
intensityInput.addEventListener("input", function () {
var value = parseInt(intensityInput.value);
intensityContainer.innerHTML = "Intensity : <span class=\"setting-value\">".concat(value.toLocaleString("en-US"), "</span>");
if (isAttractionForce) {
interaction.options.g = -value / INTENSITY_DIVIDER;
}
else {
interaction.options.g = value / INTENSITY_DIVIDER;
}
});
var distanceContainer = document.createElement("h4");
distanceContainer.classList.add("setting");
distanceContainer.innerHTML = "Distance : <span class=\"setting-value\">".concat(DISTANCE_DEFAULT.toLocaleString("en-US"), "</span>");
var distanceRange = document.createElement("div");
distanceRange.classList.add("range");
distanceRange.innerHTML = "<p class=\"value\">".concat(DISTANCE_MIN.toLocaleString("en-US"), "</p><input class=\"range-content\" type=\"range\" min=\"").concat(DISTANCE_MIN, "\" max=\"").concat(DISTANCE_MAX, "\" step=\"").concat(DISTANCE_STEP, "\" value=\"").concat(DISTANCE_DEFAULT, "\"><p class=\"value\">").concat(DISTANCE_MAX.toLocaleString("en-US"), "</p>");
var distanceInput = distanceRange.querySelector("input");
distanceInput.addEventListener("input", function () {
var value = parseInt(distanceInput.value);
distanceContainer.innerHTML = "Distance : <span class=\"setting-value\">".concat(value.toLocaleString("en-US"), "</span>");
interaction.options.distance = value;
});
close.addEventListener("click", function () {
interactions = interactions.filter(function (itn) { return itn !== interaction; });
container.removeChild(interactionContainer);
});
interactionContainer.appendChild(colors);
interactionContainer.appendChild(forceContainer);
interactionContainer.appendChild(intensityContainer);
interactionContainer.appendChild(intensityRange);
interactionContainer.appendChild(distanceContainer);
interactionContainer.appendChild(distanceRange);
container.appendChild(interactionContainer);
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
@ -40,11 +272,10 @@ window.onload = function () {
return group;
}
function interaction(group1, group2, options) {
var _a, _b, _c;
var _a, _b;
if (options === void 0) { options = {}; }
options.g = (_a = options.g) !== null && _a !== void 0 ? _a : 0.1;
options.maxDistance = (_b = options.maxDistance) !== null && _b !== void 0 ? _b : 100;
options.minDistance = (_c = options.minDistance) !== null && _c !== void 0 ? _c : 0;
var g = (_a = options.g) !== null && _a !== void 0 ? _a : 0.1;
var distance = (_b = options.distance) !== null && _b !== void 0 ? _b : 100;
for (var i = 0; i < group1.length; i++) {
var fx = 0;
var fy = 0;
@ -54,33 +285,181 @@ window.onload = function () {
var dx = a.x - b.x;
var dy = a.y - b.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d > 0 && d < options.maxDistance && d > options.minDistance) {
var F = options.g / d;
if (d > 0 && d < distance) {
var F = g / d;
fx += F * dx;
fy += F * dy;
}
a.vx = (a.vx + fx) * 0.5;
a.vy = (a.vy + fy) * 0.5;
a.x += a.vx * 0.005;
a.y += a.vy * 0.005;
a.x += a.vx * 0.01;
a.y += a.vy * 0.01;
}
}
}
var blue = makeGroup(5000, "#0000ff", { radius: 1 });
var red = makeGroup(1000, "#ff0000", { radius: 2.5 });
var green = makeGroup(50, "#00ff00", { radius: 4 });
(_a = document.querySelector(".add.particle")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", function () {
var color = null;
for (var c in colors) {
if (!colors[c]) {
color = c;
break;
}
}
if (color)
addColor(color);
});
(_b = document.querySelector(".add.interaction")) === null || _b === void 0 ? void 0 : _b.addEventListener("click", function () {
if (interactions.length >= MAX_INTERACTION_NUMBER)
return;
var color1 = null;
var color2 = null;
for (var c in colors) {
if (colors[c]) {
color1 = c;
break;
}
}
if (!color1)
return;
for (var c in colors) {
if (colors[c] && c !== color1) {
color2 = c;
break;
}
}
if (!color2)
color2 = color1;
var group1 = colors[color1];
var group2 = colors[color2];
if (!group1 || !group2)
return;
var intensity = DEFAULT_INTENSITY;
var distance = 100;
var options = {
distance: distance,
g: intensity / INTENSITY_DIVIDER,
};
var interaction = {
group1: group1,
group2: group2,
options: options,
};
addInteraction(interaction);
});
(_c = document.querySelector(".play-pause")) === null || _c === void 0 ? void 0 : _c.addEventListener("click", function () {
paused = !paused;
var img = document.querySelector(".play-pause img");
img.src = paused
? "/assets/images/svg/play_arrow.svg"
: "/assets/images/svg/pause.svg";
});
(_d = document.querySelector(".reset")) === null || _d === void 0 ? void 0 : _d.addEventListener("click", function () {
reset();
});
(_e = document.querySelector(".save")) === null || _e === void 0 ? void 0 : _e.addEventListener("click", function () {
var element = document.createElement("a");
element.style.display = "none";
element.download = "ps".concat(Date.now(), ".json");
element.setAttribute("href", "data:text/plain;charset=utf-8," +
encodeURIComponent(JSON.stringify([
interactions.map(function (itn) { return ({
group1: itn.group1[0].color,
group2: itn.group2[0].color,
options: itn.options,
}); }),
Object.entries(colors)
.filter(function (_a) {
var _ = _a[0], v = _a[1];
return v;
})
.map(function (_a) {
var k = _a[0], _ = _a[1];
return ({
color: k,
number: colors[k].length,
});
}),
])));
element.click();
element.remove();
});
(_f = document.querySelector(".load")) === null || _f === void 0 ? void 0 : _f.addEventListener("click", function () {
var element = document.createElement("input");
element.style.display = "none";
element.type = "file";
element.accept = ".json";
element.addEventListener("change", function (e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.addEventListener("load", function (e) {
var _a = JSON.parse(e.target.result), itn = _a[0], groups = _a[1];
interactions = [];
particles = [];
var particleContainer = document.querySelector(".particle-containers");
var interactionContainer = document.querySelector(".interaction-containers");
particleContainer.innerHTML = "";
interactionContainer.innerHTML = "";
for (var _i = 0, groups_1 = groups; _i < groups_1.length; _i++) {
var group = groups_1[_i];
addColor(group.color, false);
colors[group.color] = makeGroup(group.number, group.color, {
radius: Particle.DEFAULT_RADIUS,
});
}
for (var _b = 0, itn_1 = itn; _b < itn_1.length; _b++) {
var it = itn_1[_b];
var group1 = colors[it.group1];
var group2 = colors[it.group2];
if (!group1 || !group2)
continue;
addInteraction({
group1: group1,
group2: group2,
options: it.options,
});
}
});
reader.readAsText(file);
});
element.click();
element.remove();
});
(_g = document.querySelector(".minimize")) === null || _g === void 0 ? void 0 : _g.addEventListener("click", function () {
var gui = document.querySelector(".gui .content");
gui.classList.toggle("minimized");
});
window.addEventListener("resize", function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
reset();
});
window.addEventListener("click", function () {
document.querySelectorAll(".color-list").forEach(function (colorList) {
if (colorList.classList.contains("show")) {
colorList.classList.remove("show");
}
});
});
addColor("red");
addColor("green");
addInteraction({
group1: colors.red,
group2: colors.green,
options: {
distance: DISTANCE_DEFAULT,
g: -DEFAULT_INTENSITY / INTENSITY_DIVIDER,
},
});
function animate() {
interaction(red, red, { g: -0.05, maxDistance: 250 });
interaction(green, red, { g: -0.1, maxDistance: 500 });
interaction(red, green, { g: -0.05, maxDistance: 250 });
interaction(blue, red, { g: -0.4, maxDistance: 250 });
if (!paused) {
interactions.forEach(function (itn) {
return interaction(itn.group1, itn.group2, itn.options);
});
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
particles.forEach(function (particle) {
particle.draw();
});
requestAnimationFrame(animate);
particles.forEach(function (particle) { return particle.draw(ctx); });
return requestAnimationFrame(animate);
}
animate();
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff