feat: fields and colors

This commit is contained in:
cha0s 2024-08-01 00:39:54 -05:00
parent a4949bd7a0
commit 30caab6c9e
6 changed files with 148 additions and 10 deletions

View File

@ -1,9 +1,13 @@
import Component from '@/ecs/component.js';
import {hexToHsl, hslToHex, hslToRgb, rgbToHsl} from '@/util/color.js';
export default class Sprite extends Component {
instanceFromSchema() {
return class SpriteInstance extends super.instanceFromSchema() {
$$anchor = {x: 0.5, y: 0.5};
$$hue = 0;
$$saturation = 0;
$$lightness = 1;
$$scale = {x: 1, y: 1};
$$sourceJson = {};
get anchor() {
@ -59,6 +63,20 @@ export default class Sprite extends Component {
}
return true;
}
get hue() {
return this.$$hue;
}
set hue(hue) {
const [, s, l] = hexToHsl(this.$$tint);
this.tint = hslToHex(hue, s, l);
}
get lightness() {
return this.$$lightness;
}
set lightness(lightness) {
const [h, s] = hexToHsl(this.$$tint);
this.tint = hslToHex(h, s, lightness);
}
get rotates() {
if (!this.$$sourceJson.meta) {
return false;
@ -71,6 +89,13 @@ export default class Sprite extends Component {
}
return this.$$sourceJson.meta.rotation;
}
get saturation() {
return this.$$saturation;
}
set saturation(saturation) {
const [h, , l] = hexToHsl(this.$$tint);
this.tint = hslToHex(h, saturation, l);
}
get scale() {
return this.$$scale;
}
@ -97,6 +122,17 @@ export default class Sprite extends Component {
: '';
return this.$$sourceJson.frames[frame].sourceSize;
}
get tint() {
return super.tint;
}
set tint(tint) {
[
this.$$hue,
this.$$saturation,
this.$$lightness,
] = hexToHsl(tint)
super.tint = tint;
}
toNet(recipient, data) {
// eslint-disable-next-line no-unused-vars
const {elapsed, ...rest} = super.toNet(recipient, data);

View File

@ -1,11 +1,13 @@
import K from 'kefir';
import * as easings from '@/util/easing.js';
export default class Emitter {
constructor(ecs) {
this.ecs = ecs;
this.scheduled = [];
}
async allocate({entity, shape}) {
async allocate({entity, fields, shape}) {
const allocated = this.ecs.get(await this.ecs.create(entity));
if (shape) {
switch (shape.type) {
@ -16,6 +18,19 @@ export default class Emitter {
}
}
}
if (fields) {
for (const {easing = 'linear', path, value} of fields) {
let walk = allocated;
const pathCopy = path.slice(0);
const final = pathCopy.pop();
for (const key of pathCopy) {
walk = walk[key];
}
const c = Math.random() * (value.length - 1);
const i = Math.floor(c);
walk[final] = easings[easing](c - i, value[i], value[i + 1] - value[i], 1);
}
}
return allocated;
}
emit(particle) {

61
app/util/color.js Normal file
View File

@ -0,0 +1,61 @@
const {min, max, round} = Math;
export function hslToHex(h, s, l) {
const [r, g, b] = hslToRgb(h, s, l);
return (r << 16) | (g << 8) | b;
}
export function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l;
}
else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hueToRgb(p, q, h + 1/3);
g = hueToRgb(p, q, h);
b = hueToRgb(p, q, h - 1/3);
}
return [round(r * 255), round(g * 255), round(b * 255)];
}
function hueToRgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
export function hexToHsl(i) {
const [r, g, b] = hexToRgb(i);
return rgbToHsl(r, g, b);
}
export function hexToRgb(i) {
const o = i > 16777215 ? 8 : 0;
return [
(i >> o + 16) & 255,
(i >> o + 8 ) & 255,
(i >> o + 0 ) & 255,
];
}
export function rgbToHsl(r, g, b) {
(r /= 255), (g /= 255), (b /= 255);
const vmax = max(r, g, b), vmin = min(r, g, b);
let h, s, l = (vmax + vmin) / 2;
if (vmax === vmin) {
return [0, 0, l];
}
const d = vmax - vmin;
s = l > 0.5 ? d / (2 - vmax - vmin) : d / (vmax + vmin);
if (vmax === r) h = (g - b) / d + (g < b ? 6 : 0);
if (vmax === g) h = (b - r) / d + 2;
if (vmax === b) h = (r - g) / d + 4;
h /= 6;
return [h, s, l];
}

View File

@ -2,6 +2,7 @@ import {parse as acornParse} from 'acorn';
import {LRUCache} from 'lru-cache';
import Sandbox from '@/astride/sandbox.js';
import * as color from '@/util/color.js';
import delta from '@/util/delta.js';
import lfo from '@/util/lfo.js';
import * as MathUtil from '@/util/math.js';
@ -40,6 +41,7 @@ export default class Script {
static contextDefaults() {
return {
color,
console,
delta,
lfo,

View File

@ -13,14 +13,32 @@ if (projected?.length > 0) {
Sound.play('/assets/hoe/dig.wav');
for (const {x, y} of projected) {
Emitter.emit({
count: 25,
frequency: 0.01,
count: 75,
fields: [
{
path: ['Sprite', 'lightness'],
value: [0.05, 0.25],
},
{
path: ['Sprite', 'alpha'],
value: [0.3, 1],
},
{
path: ['Sprite', 'scaleX'],
value: [0.05, 0.3],
},
{
path: ['Sprite', 'scaleY'],
value: [0.05, 0.3],
},
],
frequency: 0.004,
shape: {
type: 'filledRect',
payload: {width: 16, height: 16},
},
entity: {
Forces: {forceY: -50},
Forces: {forceY: -40},
Position: {
x: x * layer.tileSize.x + (layer.tileSize.x / 2),
y: y * layer.tileSize.y + (layer.tileSize.y / 2),

View File

@ -14,24 +14,30 @@ if (projected?.length > 0) {
for (const {x, y} of projected) {
Emitter.emit({
count: 25,
frequency: 0.01,
count: 100,
fields: [
{
path: ['Sprite', 'lightness'],
value: [0.111, 0.666],
},
],
frequency: 0.005,
shape: {
type: 'filledRect',
payload: {width: 16, height: 16},
},
entity: {
Forces: {forceY: 100},
Forces: {forceY: 400},
Position: {
x: x * layer.tileSize.x + (layer.tileSize.x / 2),
y: y * layer.tileSize.y - layer.tileSize.y,
},
Sprite: {
scaleX: 0.2,
scaleY: 0.2,
scaleX: 0.1,
scaleY: 0.3,
tint: 0x0022aa,
},
Ttl: {ttl: 0.25},
Ttl: {ttl: 0.0625},
}
});
}