User:Shoebby/Webby Sprouts: Difference between revisions

From XPUB & Lens-Based wiki
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
Download here: https://addons.mozilla.org/en-US/firefox/addon/webby-sprouts/
Download here: https://addons.mozilla.org/en-US/firefox/addon/webby-sprouts/


blogpost: https://shoebby.github.io/dist/blogpost-penplot.html
blogpost: https://www.lexie.land/dist/blogpost-penplot.html


==== (Where am I in) The Process ====
==== (Where am I in) The Process ====
<gallery>
My dive into fractals brought me into contact with a lot of different mediums for drawing them, here is the process shown by way of images in (rough) chronological order.
1 penplot 1.jpg|Caption1
 
1 penplot 2.mp4|Caption1
===== Pen Plotting =====
1 penplot 3.jpg|Caption1
import math
1 penplot 4.png|Caption1
import numpy as np
1 penplot 5.png|Caption1
1 penplot 6.PNG|Caption1
file = open("fractal.hgl", "w")
1 penplot 7.png|Caption1
1 penplot 8.png|Caption1
def rotate(vec, angle):
1 penplot 9.png|Caption1
    rads = math.radians(angle)
1 penplot 10.png|Caption1
    a = math.cos(rads)
1 penplot 11.png|Caption1
    b = math.sin(rads)
1 penplot 12.PNG|Caption1
    R = np.array([[a,-b], [b,a]])
1 penplot 13.png|Caption1
    return np.dot(R, vec)
1 penplot 14.png|Caption1
def tree(x1, y1, x2, y2, angleLeft, angleRight, ratio, depth):
    if depth == 0: return
    file.write(f'PU {round(x1)},{round(y1)};\n')
    file.write(f'PD {round(x2)},{round(y2)};\n')
    base = np.array([x2 - x1, y2 - y1])
    new_base = base * ratio
    right = rotate(new_base, angleRight)
    left = rotate(new_base, -angleLeft)
   
    end = np.array([x2, y2])
    right_end = np.add(end, right)
    left_end = np.add(end, left)
    tree(x2, y2, right_end[0], right_end[1], angleLeft, angleRight, ratio, depth - 1)
    tree(x2, y2, left_end[0], left_end[1], angleLeft, angleRight, ratio, depth - 1)
print("What's the stem's starting x position? (whole numbers)")
start_x = int(input())
print("What's the stem's starting y position? (whole numbers)")
start_y = int(input())
print("What's the stem's end x position? (whole numbers)")
end_x = int(input())
print("What's the stem's end y position? (whole numbers)")
end_y = int(input())
print("Are the branch angles symmetrical? (y/n)")
symmetrical = input().lower().strip() == "y"
if symmetrical:
    print("What angle are the branches? (in degrees, whole numbers)")
    angle = int(input())
    angleLeft = angle
    angleRight = angle
else:
    print("What angle is the left branch? (in degrees, whole numbers)")
    angleLeft = int(input())
    print("What angle is the right branch? (in degrees, whole numbers)")
    angleRight = int(input())
print("What ratio should the branches be? (decimal numbers (i.e. .75)) >1 means the branches grow with every recursion, <1 means they shrink.")
ratio = float(input())
print("How many recursions should this fractal be? (whole numbers) 5-10 is usually a good starting point to get an idea of the overall shape.)")
recursions = int(input())
tree(start_x, start_y, end_x, end_y, angleLeft, angleRight, ratio, recursions)
 
<gallery mode="packed" widths="300" heights="200">
1 penplot 1.jpg|First fractal, made using the pen plotter and a Math'd python script to generate instructions
1 penplot 2.mp4|Video of the first fractal being drawn, you can hear my partner in the background 'ooh' and 'aah'ing
1 penplot 3.jpg|Following fractal experiments introduce variability in left/right angles and ratios
1 penplot 4.png|A fractal where the left angle is higher than the right angle
1 penplot 5.png|Chaotic fractal 1
1 penplot 6.PNG|Chaotic fractal 2
1 penplot 7.png|Chaotic fractal 3
1 penplot 9.png|Chaotic fractal 4
1 penplot 10.png|This fractal introduces slight randomization per branch, set within min-max limits
1 penplot 11.png|This fractal also randomizes ratios per branch, adding a much more organic and clustering look to the overall structure
1 penplot 12.PNG|I think this one is quite pretty
1 penplot 13.png|This one looks kinda cool I think
1 penplot 14.png|Hey woah
</gallery>
</gallery>


<gallery>
===== The Canvas Element =====
2 postscript 1.png|caption1
function tree(x1, y1, x2, y2, angleLeft, angleRight, ratio, depth) {
2 postscript 2.png|caption1
    console.log("entered tree depth: " + depth)
2 postscript 3.png|caption1
    console.log("(" + x1 + ", " + y1 + ") to (" + x2 + ", " + y2 + ")");
2 postscript 4.pdf|caption1
    if (depth <= 0) {
        return;
    }
   
    ctx.strokeStyle = "rgb(" + (parseInt(depth)*10) + " " + (parseInt(depth)*3) + " " + (parseInt(depth)*14) + ")";
    if (depth == 1) {
        ctx.fillStyle = "white";
        ctx.strokeStyle = "white";
        ctx.beginPath();
        ctx.arc(x2, y2, 5, 0, Math.PI * 2, false);
        ctx.fill();
    }
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
    var base = new Vector2(x2 - x1, y2 - y1);
    var new_base = base.clone().multiplyScalar(ratio);
    var angleLeft_rad = MathUtils.degToRad(angleLeft);
    var angleRight_rad = MathUtils.degToRad(angleRight);
    var left = new_base.clone().rotateAround(new Vector2(0, 0), -angleLeft_rad);
    var right = new_base.clone().rotateAround(new Vector2(0, 0), angleRight_rad);
   
    var end = new Vector2(x2, y2);
    var left_end = end.clone().add(left);
    var right_end = end.clone().add(right);
    tree(x2, y2, left_end.x, left_end.y, angleLeft, angleRight, ratio, depth - 1);
    tree(x2, y2, right_end.x, right_end.y, angleLeft, angleRight, ratio, depth - 1);
}
 
<gallery mode="packed" widths="300" heights="200">
3 canvas 1.png|Now taking fractals fully digital I used the canvas element to generate them, generate your own [https://shoebby.github.io/dist/sprout.html here]!
</gallery>
</gallery>


<gallery>
===== Fractals From Divs =====
3 canvas 1.png|Fractals in the canvas element
function initTrees(treeAmount) {
  var startAngle = 360 / treeAmount;
  depthstep = 255 / depth;
  for (i = 0; i < treeAmount; i++) {
    tree(startLength, startAngle*i, depth, null, null, ratio);
  }
}
 
function tree(startLength, startRot, depth, parent, angle, ratio) {
  if (depth == 0) {
    return;
  }
  const newBranch = document.createElement("div");
  newBranch.classList.add("branch");
  newBranch.setAttribute("id", "treeBranch");               
  if (parent != null) {
    parent.appendChild(newBranch);
    newBranch.style.setProperty('--w', parent.offsetWidth * ratio + "px");
    newBranch.style.setProperty('--a', angle + "deg");
  } else if (parent == null) {
    newBranch.classList.add("root");
    document.body.appendChild(newBranch);
    newBranch.style.setProperty('--w', startLength + "px");
    newBranch.style.setProperty('--a', startRot + "deg");
  }
  newBranch.innerHTML = "<span>" + newBranch.offsetWidth + "</span>"
  newBranch.style.setProperty('--colorR', 255);
  newBranch.style.setProperty('--colorG', 255 - (depth * depthstep));
  newBranch.style.setProperty('--colorB', 0);
  newBranch.style.setProperty('--colorA', 1);
  parent = newBranch;
  tree(startLength, startRot, depth - 1, newBranch, angleLeft, ratio);
  tree(startLength, startRot, depth - 1, newBranch, -angleRight, ratio);
}
 
<gallery mode="packed" widths="300" heights="200">
4 divvysprouts 1.png|Canvas didn't really tickle me however, and then Doriane came with the suggestion to instead draw them using divs, which struck me as incredibly cursed and awesome
4 divvysprouts 2.png|It works, and I could even apply past things like uneven left/right angles already, play with it yourself [https://shoebby.github.io/dist/sprout_div.html here]
4 divvysprouts 3.png|Squeer
4 divvysprouts 4.png|Due to the way theyre drawn you can draw multiple over eachother to make neat patterns
4 divvysprouts 5.gif|(gif) First experiments with animating the tree
4 divvysprouts 6.gif|(gif) Spin
4 divvysprouts 7.gif|(gif) Spinnn
4 divvysprouts 8.gif|(gif) Spinnnnn
4 divvysprouts 9.gif|(gif) Spinnnnnnn
4 divvysprouts 11.png|I figured I could also expose the CSS more to the user so they could mess with the styling for themselves, to make the trees pretty and/or weird-looking
4 divvysprouts 12.gif|(gif) Meta-ball-ish dual fractal spinning unfurling and such
4 divvysprouts 13.gif|(gif) Kinda the same but with wackier colours and using mix-blend-mode
</gallery>
</gallery>


<gallery>
===== Fractals From The DOM =====
4 divvysprouts 1.png|caption1
function webTree(startLength, parentElement, parentBranch, angle, ratio, color) {
4 divvysprouts 2.png|caption1
  const newBranch = document.createElement("div");
4 divvysprouts 3.png|caption1
4 divvysprouts 4.png|caption1
  newBranch.classList.add("branchDiv");
4 divvysprouts 5.gif|caption1
  parentElement.classList.add("branchElement");
4 divvysprouts 6.gif|caption1
4 divvysprouts 7.gif|caption1
  if (parentBranch != null) {
4 divvysprouts 8.gif|caption1
    parentBranch.appendChild(newBranch);
4 divvysprouts 9.gif|caption1
4 divvysprouts 10.png|caption1
    newBranch.style.setProperty('--w', parentBranch.offsetWidth * ratio + "px");
4 divvysprouts 11.png|caption1
    newBranch.style.setProperty('--a', angle + "deg");
4 divvysprouts 12.gif|caption1
4 divvysprouts 13.gif|caption1
  } else if (parentBranch == null) {
</gallery>
    newBranch.classList.add("rootDiv");
    document.body.appendChild(newBranch);
    newBranch.style.setProperty('--w', startLength + "px");
    newBranch.style.setProperty('--a', "90deg");
  }
  newBranch.style.setProperty('--colorR', 255);
  newBranch.style.setProperty('--colorG', 255 - newBranch.offsetWidth);
  newBranch.style.setProperty('--colorB', 0);
  newBranch.style.setProperty('--colorA', 1);
  if (newBranch.hasChildNodes() == false) {
    const newBulb = document.createElement("div");
    newBulb.classList.add("branchDiv");
    newBulb.style.height = "50px";
    newBulb.style.width = "50px";
    newBulb.style.backgroundColor = "white";
  }
  if (parentElement.tagName == "A") {
    newBranch.classList.add('linkFlower');
    newBranch.innerHTML = "<a href=" + parentElement + " style='width:100%;height:100%;'></a>";
  }
  var siblings = parentElement.children;
  for (let i = 0; i < siblings.length; i++) {
    if (siblings[i].classList.contains("branchDiv") || siblings[i].tagName == "BR") {
      continue;
    }
    var newAngle = getRndInteger(-55, 55);
    var newRatio = getRndInteger(50, 100) / 100;
    webTree(startLength, siblings[i], newBranch, newAngle, newRatio, color);
  }
}


<gallery>
<gallery mode="packed" widths="300" heights="200">
5 webbysprouts 1.png|caption1
5 webbysprouts 1.png|So now lets take it to a web extension! Assignment: generate a tree fractal based on the webpage DOM
5 webbysprouts 2.png|caption1
5 webbysprouts 2.png|And also show what sort of element is related to which branch!
5 webbysprouts 3.gif|caption1
4 divvysprouts 10.png|On some pages the tree even applies some of the page's styling to itself, why? How? I wish I knew (help)
5 webbysprouts 3.gif|Some interactivity is introduced, now element type pops up on hover! Maybe also highlight the elements themselves? (yes)
</gallery>
</gallery>


<gallery>
===== The Final Yuri =====
6 finalyuri.png|Caption1
<gallery mode="packed" widths="300" heights="200">
6 finalyuri.png|For fun I also made the divvy sprouts compatible with the webby sprouts, so they could intermingle, pull from eachother, and kiss!
</gallery>
</gallery>
==== Questions ====
==== Goals ====

Revision as of 04:30, 31 March 2025

Download here: https://addons.mozilla.org/en-US/firefox/addon/webby-sprouts/

blogpost: https://www.lexie.land/dist/blogpost-penplot.html

(Where am I in) The Process

My dive into fractals brought me into contact with a lot of different mediums for drawing them, here is the process shown by way of images in (rough) chronological order.

Pen Plotting
import math
import numpy as np

file = open("fractal.hgl", "w")

def rotate(vec, angle): 
   rads = math.radians(angle)
   a = math.cos(rads)
   b = math.sin(rads)
   R = np.array([[a,-b], [b,a]])
   return np.dot(R, vec)

def tree(x1, y1, x2, y2, angleLeft, angleRight, ratio, depth):

   if depth == 0: return

   file.write(f'PU {round(x1)},{round(y1)};\n')
   file.write(f'PD {round(x2)},{round(y2)};\n')

   base = np.array([x2 - x1, y2 - y1])
   new_base = base * ratio
   right = rotate(new_base, angleRight)
   left = rotate(new_base, -angleLeft)
   
   end = np.array([x2, y2])
   right_end = np.add(end, right)
   left_end = np.add(end, left)

   tree(x2, y2, right_end[0], right_end[1], angleLeft, angleRight, ratio, depth - 1)
   tree(x2, y2, left_end[0], left_end[1], angleLeft, angleRight, ratio, depth - 1)

print("What's the stem's starting x position? (whole numbers)")
start_x = int(input())
print("What's the stem's starting y position? (whole numbers)")
start_y = int(input())
print("What's the stem's end x position? (whole numbers)")
end_x = int(input())
print("What's the stem's end y position? (whole numbers)")
end_y = int(input())

print("Are the branch angles symmetrical? (y/n)")
symmetrical = input().lower().strip() == "y"
if symmetrical:
   print("What angle are the branches? (in degrees, whole numbers)")
   angle = int(input())
   angleLeft = angle
   angleRight = angle
else:
   print("What angle is the left branch? (in degrees, whole numbers)")
   angleLeft = int(input())
   print("What angle is the right branch? (in degrees, whole numbers)")
   angleRight = int(input())

print("What ratio should the branches be? (decimal numbers (i.e. .75)) >1 means the branches grow with every recursion, <1 means they shrink.")
ratio = float(input())
print("How many recursions should this fractal be? (whole numbers) 5-10 is usually a good starting point to get an idea of the overall shape.)")
recursions = int(input())

tree(start_x, start_y, end_x, end_y, angleLeft, angleRight, ratio, recursions)
The Canvas Element
function tree(x1, y1, x2, y2, angleLeft, angleRight, ratio, depth) {
   console.log("entered tree depth: " + depth)
   console.log("(" + x1 + ", " + y1 + ") to (" + x2 + ", " + y2 + ")");

   if (depth <= 0) {
       return;
   }
   
   ctx.strokeStyle = "rgb(" + (parseInt(depth)*10) + " " + (parseInt(depth)*3) + " " + (parseInt(depth)*14) + ")";

   if (depth == 1) {
       ctx.fillStyle = "white";
       ctx.strokeStyle = "white";
       ctx.beginPath();
       ctx.arc(x2, y2, 5, 0, Math.PI * 2, false);
       ctx.fill();
   }

   ctx.moveTo(x1, y1);
   ctx.lineTo(x2, y2);
   ctx.stroke();

   var base = new Vector2(x2 - x1, y2 - y1);

   var new_base = base.clone().multiplyScalar(ratio);

   var angleLeft_rad = MathUtils.degToRad(angleLeft);
   var angleRight_rad = MathUtils.degToRad(angleRight);

   var left = new_base.clone().rotateAround(new Vector2(0, 0), -angleLeft_rad);
   var right = new_base.clone().rotateAround(new Vector2(0, 0), angleRight_rad);
   
   var end = new Vector2(x2, y2);
   var left_end = end.clone().add(left);
   var right_end = end.clone().add(right);


   tree(x2, y2, left_end.x, left_end.y, angleLeft, angleRight, ratio, depth - 1);
   tree(x2, y2, right_end.x, right_end.y, angleLeft, angleRight, ratio, depth - 1);
}
Fractals From Divs
function initTrees(treeAmount) {
  var startAngle = 360 / treeAmount;
  depthstep = 255 / depth;
  for (i = 0; i < treeAmount; i++) {
    tree(startLength, startAngle*i, depth, null, null, ratio);
  }
}
function tree(startLength, startRot, depth, parent, angle, ratio) {
  if (depth == 0) {
    return;
  }

  const newBranch = document.createElement("div");
  newBranch.classList.add("branch");
  newBranch.setAttribute("id", "treeBranch");                

  if (parent != null) {
    parent.appendChild(newBranch);

    newBranch.style.setProperty('--w', parent.offsetWidth * ratio + "px");
    newBranch.style.setProperty('--a', angle + "deg");

  } else if (parent == null) {
    newBranch.classList.add("root");
    document.body.appendChild(newBranch);

    newBranch.style.setProperty('--w', startLength + "px");
    newBranch.style.setProperty('--a', startRot + "deg");
  }

  newBranch.innerHTML = "" + newBranch.offsetWidth + ""

  newBranch.style.setProperty('--colorR', 255);
  newBranch.style.setProperty('--colorG', 255 - (depth * depthstep));
  newBranch.style.setProperty('--colorB', 0);
  newBranch.style.setProperty('--colorA', 1);

  parent = newBranch;
  tree(startLength, startRot, depth - 1, newBranch, angleLeft, ratio);
  tree(startLength, startRot, depth - 1, newBranch, -angleRight, ratio);
}
Fractals From The DOM
function webTree(startLength, parentElement, parentBranch, angle, ratio, color) {
  const newBranch = document.createElement("div");

  newBranch.classList.add("branchDiv");
  parentElement.classList.add("branchElement");

  if (parentBranch != null) {
    parentBranch.appendChild(newBranch);

    newBranch.style.setProperty('--w', parentBranch.offsetWidth * ratio + "px");
    newBranch.style.setProperty('--a', angle + "deg");

  } else if (parentBranch == null) {
    newBranch.classList.add("rootDiv");
    document.body.appendChild(newBranch);

    newBranch.style.setProperty('--w', startLength + "px");
    newBranch.style.setProperty('--a', "90deg");
  }

  newBranch.style.setProperty('--colorR', 255);
  newBranch.style.setProperty('--colorG', 255 - newBranch.offsetWidth);
  newBranch.style.setProperty('--colorB', 0);
  newBranch.style.setProperty('--colorA', 1);

  if (newBranch.hasChildNodes() == false) {
    const newBulb = document.createElement("div");
    newBulb.classList.add("branchDiv");

    newBulb.style.height = "50px";
    newBulb.style.width = "50px";
    newBulb.style.backgroundColor = "white";
  }

  if (parentElement.tagName == "A") {
    newBranch.classList.add('linkFlower');
    newBranch.innerHTML = "<a href=" + parentElement + " style='width:100%;height:100%;'></a>";
  }

  var siblings = parentElement.children;

  for (let i = 0; i < siblings.length; i++) {
    if (siblings[i].classList.contains("branchDiv") || siblings[i].tagName == "BR") {
      continue;
    }

    var newAngle = getRndInteger(-55, 55);
    var newRatio = getRndInteger(50, 100) / 100;

    webTree(startLength, siblings[i], newBranch, newAngle, newRatio, color);
  }
}
The Final Yuri