Skip to content

Commit

Permalink
Try to generate more organic organisms, refers #15
Browse files Browse the repository at this point in the history
- the organism "body" have a gradient color,
    this seems to be unsupported by the scratch costumes editor
    in the stage works
- now hat "when organism dies" works, fixes #19
  • Loading branch information
raffaelepojer committed Aug 18, 2020
1 parent e87467c commit 619a4ed
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 43 deletions.
10 changes: 7 additions & 3 deletions src/extensions/botch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class Scratch3Botch {
{
opcode: 'isDeadHat',
blockType: BlockType.HAT,
isEdgeActivated: false,
text: 'when an organism dies'
},
{
Expand Down Expand Up @@ -276,7 +277,7 @@ class Scratch3Botch {
util.target, this.mass, this.maxForce);

// change the costume of the original sprite
const newSvg = new svgen(130, 130).generateMultiple(org.dna[0], org.dna[1], 5);
const newSvg = new svgen(130, 130).generateOrgSVG(200, this.dna[0], this.dna[1], 5);
org.svg = newSvg;

this.organismMap.set(util.target.id, org);
Expand Down Expand Up @@ -507,8 +508,10 @@ class Scratch3Botch {
this.organismMap.set(newClone.id, newOrg);
}
}

if (org.dead()) {
this.runtime.startHats('botch_isDeadHat', null, org.target.sprite);
this.runtime._hats.botch_isDeadHat.edgeActivated = false;
this.runtime.startHats('botch_isDeadHat', null, org.target);
}
/* if (org.dead()) {
if (org.deathAnimation(util)) { // wait the end of animation
Expand Down Expand Up @@ -565,7 +568,8 @@ class Scratch3Botch {
}

if (org.dead()) {
this.runtime.startHats('botch_isDeadHat', null, org.target.sprite);
this.runtime._hats.botch_isDeadHat.edgeActivated = false;
this.runtime.startHats('botch_isDeadHat', null, org.target);
}
/* if (org.dead()) {
if (org.deathAnimation(util)) { // wait the end of animation
Expand Down
3 changes: 2 additions & 1 deletion src/extensions/botch/organism.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ class Organism {
this.dna[3] = Math.random() * 150; // poison perception
}

this.svg = new svgen(130, 130).generateMultiple(this.dna[0], this.dna[1], 5);
// this.svg = new svgen(130, 130).generateMultiple(this.dna[0], this.dna[1], 5);
this.svg = new svgen(130, 130).generateOrgSVG(100, this.dna[0], this.dna[1], 5);
this.botchUtil.uploadCostumeEdit(this.svg, this.target.id);

// Variable assignment to the sprite
Expand Down
194 changes: 155 additions & 39 deletions src/extensions/botch/svg-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class SVGgen {
* @param {int} width width of the svg
* @param {int} height height of the svg
* @param {string} color color base of the svg
* @since botch-0.1
*/
constructor (width = 30, height = 30, color = SVGgen.getRandomColor()) {
this.width = width;
Expand All @@ -23,6 +24,7 @@ class SVGgen {
/**
* Generate a random Hex color
* @returns {string} new color
* @since botch-0.1
*/
static getRandomColor () {
const letters = '0123456789ABCDEF';
Expand All @@ -33,6 +35,34 @@ class SVGgen {
return color;
}

/**
* Return the new color with a new luminosity
* @param {string} hex hex color
* @param {number} lum luminosity
* @returns {string} new color
* @author https://www.sitepoint.com/javascript-generate-lighter-darker-color/
* @since botch-0.2
*/
colorLuminance (hex, lum) {

// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
lum = lum || 0;

// convert to decimal and change luminosity
let rgb = '#'; let c; let i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substr(i * 2, 2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += (`00${c}`).substr(c.length);
}

return rgb;
}

checkBorders (margin) {
this.points.forEach(p => {
p.x = p.x > this.width ? this.width - margin : p.x;
Expand All @@ -42,6 +72,81 @@ class SVGgen {
});
}

/**
* Generate an SVG using quadratic bezier curve
* the function works on a squared svg canvas
* it create 4 points on the diagonals
* y = x
* y = -x + this.height
* @param {number} dim dimension of the svg (square)
* @param {number} foodR food attraction
* @param {number} poisonR poison attraction
* @param {number} mag max poison or food attraction
* @returns {string} the svg
* @since botch-0.2
*/
generateOrgSVG (dim, foodR, poisonR, mag) {
const f = MathUtil.scale(foodR, -mag, mag, 0, 15);
const p = MathUtil.scale(poisonR, -mag, mag, 0, 15);

// resize the canvas
this.width = dim;
this.height = dim;

// generate the 4 points on the diagonal
const margin = 15;
const w1 = (this.width / 2) - margin;
const w2 = (this.width / 2) + margin;

const ta = Math.floor(this.rdn(0, w1));
const p1 = new Vector2(ta, ta);

const tb = Math.floor(this.rdn(w2, this.width - margin));
const p2 = new Vector2(tb, -tb + this.height);

const tc = Math.floor(this.rdn(w2, this.width - margin));
const p3 = new Vector2(tc, tc);

const td = Math.floor(this.rdn(0, w1));
const p4 = new Vector2(td, -td + this.height);

// generate the 4 control points
const va = Math.floor(this.rdn(0, this.width));
const v2a = va < this.width / 2 ?
Math.floor(this.rdn(0, va)) : Math.floor(this.rdn(0, -va + this.height));
const c1 = new Vector2(va, v2a);

const vb = Math.floor(this.rdn(this.width / 2, this.width));
const v2b = Math.floor(this.rdn(-vb + this.height, vb));
const c2 = new Vector2(vb, v2b);

const vc = Math.floor(this.rdn(0, this.width));
const v2c = vc < this.width / 2 ?
Math.floor(this.rdn(-vc + this.height, this.height)) : Math.floor(this.rdn(vc, this.height));
const c3 = new Vector2(vc, v2c);

const vd = Math.floor(this.rdn(0, this.width / 2));
const v2d = Math.floor(this.rdn(vd, -vd + this.height));
const c4 = new Vector2(vd, v2d);

return `<svg height="${this.height}" width="${this.width}" viewBox="0 0 ${this.width} ${this.height}" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">` +
`<defs> ` +
`<radialGradient id="RadialGradient1"> ` +
`<stop offset="0%" stop-color="${this.colorLuminance(this.color, 0.4)}"/> ` +
`<stop offset="100%" stop-color="${this.colorLuminance(this.color, -0.2)}"/> ` +
`</radialGradient>` +
`</defs>` +
`<path d="M ${p1.x} ${p1.y} Q ${c1.x} ${c1.y} ${p2.x} ${p2.y} Q ${c2.x} ${c2.y} ${p3.x} ${p3.y} ` +
`Q ${c3.x} ${c3.y} ${p4.x} ${p4.y} Q ${c4.x} ${c4.y} ${p1.x} ${p1.y}" ` +
`fill="url(#RadialGradient1)" style="stroke:none;stroke-width:1" />` +
`<circle cx="${p2.x}" cy="${p2.y}" ` +
`r="${f}" stroke="none" stroke-width="1" fill="${this.colorLuminance(this.color, 0.7)}" />` + // food
`<circle cx="${p3.x}" cy="${p3.y}" ` +
`r="${p}" stroke="none" stroke-width="1" fill="${this.colorLuminance(this.color, -0.7)}" />` +
// `${this.pointToEyesTr(foodR, poisonR)}` +
`</svg>`;
}

/**
* Generate 3 points in the svg space
*
Expand All @@ -50,6 +155,7 @@ class SVGgen {
* | 1
* | -
* 2
* @since botch-0.1
*/
generateTriangle () {
const p1 = new Vector2(
Expand Down Expand Up @@ -77,6 +183,7 @@ class SVGgen {
* | 1
* | -
* 2
* @since botch-0.1
*/
generateTriangle2 (margin) {
const p1 = new Vector2(
Expand Down Expand Up @@ -110,6 +217,7 @@ class SVGgen {
* | -2
* 1 -
* @param {number} margin margin
* @since botch-0.1
*/
generateTrapezoid (margin) {
const v = this.width;
Expand Down Expand Up @@ -143,6 +251,7 @@ class SVGgen {
/**
* Compute the center of gravity of the trapezoid and center it in the frame
* https://online.scuola.zanichelli.it/cannarozzozavanella-files/Costruzioni/Approfondimenti/Zanichelli_Costruzioni_UnitaE1_Par5.pdf
* @since botch-0.1
*/
computeCenterGravityTrapezoid () {
const h = Math.abs(this.points[1].x - this.points[0].x);
Expand Down Expand Up @@ -182,15 +291,9 @@ class SVGgen {
// this.checkBorders(this.strokeWidth);
}

/**
* Compute the center of the trapezoid respect to the bounding box
*/
computeCenterBoundingBoxTrapezoid () {

}

/**
* https://it.wikipedia.org/wiki/Baricentro_(geometria)
* @since botch-0.1
*/
computeBarycenterTriangle () {
let Xbr = 0;
Expand Down Expand Up @@ -224,61 +327,60 @@ class SVGgen {

}

pointToCircle () {
return `<circle cx=" ${this.points[0].x}" cy="${this.points[0].y}" ` +
`r="4" stroke="black" stroke-width="2" fill="red" />`;
}

/**
* generate the "eyes" of the trapezoid
* @param {number} foodR food att eye size
* @param {number} poisonR poison att eye size
* @returns {string} svg of the eyes in the points position
* @since botch-0.1
*/
pointToEyesTr (foodR, poisonR) {
return `<circle cx=" ${this.points[2].x}" cy="${this.points[2].y}" ` +
`r="${foodR}" stroke="black" stroke-width="1" fill="green" />` + // food
`<circle cx=" ${this.points[1].x}" cy="${this.points[1].y}" ` +
`r="${poisonR}" stroke="black" stroke-width="1" fill="red" />`; // poison
}

// eyes of triangle
/**
* generate the "eyes" of the triangle
* @param {number} foodR food att eye size
* @param {number} poisonR poison att eye size
* @param {number} sign sign of the attraction
* @returns {string} svg of the eyes in the points position
* @since botch-0.1
*/
pointToEyesCr (foodR, poisonR, sign) {
const i = sign > 0 ? 0 : 1;

return `<circle cx=" ${this.points[i].x}" cy="${this.points[i].y}" ` +
`r="${foodR}" stroke="black" stroke-width="1" fill="green" />` +
`<circle cx=" ${this.points[1 - i].x}" cy="${this.points[1 - i].y}" ` +
`r="${poisonR}" stroke="black" stroke-width="1" fill="red" />`;

}

/**
* return a string of each point coordinates x,y
* @returns {string} coordinate
* @since botch-0.1
*/
pointsToString () {
let str = '';
this.points.forEach(c => {
str += `${c.x},${c.y} `;
});
return str;
}

// trapezoid with eyes
generateObj3 (foodR, poisonR) {
this.generateTrapezoid(25);
this.computeCenterGravityTrapezoid();

return `<svg height="${this.height}" width="${this.width}" viewBox="0 0 ${this.width} ${this.height}" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">` +
`<polygon points="${this.pointsToString()}" style="fill:${this.color};stroke:black;stroke-width:3" />` +
`${this.pointToEyesTr(foodR, poisonR)}` +
`</svg>`;
}

// trapezoid
generateObj2 () {
this.generateTrapezoid();
this.computeCenterTrapezoid();

return `<svg height="${this.height}" width="${this.width}" viewBox="0 0 ${this.width} ${this.height}" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">` +
`<polygon points="${this.pointsToString()}" style="fill:${this.color};stroke:black;stroke-width:4" />` +
`</svg>`;
}

// triangle with eyes or trapezoid
// if it is attracted or repulsed by both food and poison => trapezoid
// else triangle
/**
* triangle with eyes or trapezoid
* if it is attracted or repulsed by both food and poison => trapezoid
* else triangle
* @param {number} foodR food attraction
* @param {number} poisonR poison attraction
* @param {number} mag max poison or food attraction
* @returns {string} svg
* @since botch-0.1
*/
generateMultiple (foodR, poisonR, mag) {
const f = MathUtil.scale(foodR, -mag, mag, 0, 15);
const p = MathUtil.scale(poisonR, -mag, mag, 0, 15);
Expand All @@ -303,7 +405,21 @@ class SVGgen {

}

// triangle
/**
* generate a point on the head of the triangle
* @returns {string} svg circle
* @since botch-0.1
*/
pointToCircle () {
return `<circle cx=" ${this.points[0].x}" cy="${this.points[0].y}" ` +
`r="4" stroke="black" stroke-width="2" fill="red" />`;
}

/**
* generate triangle with eyes
* @returns {string} svg
* @since botch-0.1
*/
generateSvgObj1 () {
this.generateTriangle();
this.computeBarycenterTriangle();
Expand Down

0 comments on commit 619a4ed

Please sign in to comment.