580 lines
14 KiB
JavaScript
580 lines
14 KiB
JavaScript
paper.install(window);
|
|
window.onload = function() {
|
|
var canvas = document.getElementById('grower');
|
|
canvas.width = 300;
|
|
canvas.height = 300;
|
|
paper.setup(canvas);
|
|
|
|
Leaf.veinArea = new Path();
|
|
Leaf.auxinArea = new Path();
|
|
Leaf.size = 100; //200;
|
|
Leaf.auxinR = 9;
|
|
Leaf.veinR = Leaf.auxinR;
|
|
Leaf.auxinNr = 15;
|
|
Leaf.veinGrowRate = 1.5;
|
|
Leaf.leafGrowRate = 3; //1.04;
|
|
Leaf.maxGrowIters = 10;
|
|
//Leaf.maxSize = 1338*0.75; //legs
|
|
//Leaf.maxSize = 1511*0.75; //seat
|
|
Leaf.maxSize = 290;
|
|
Leaf.branches = [];
|
|
Leaf.auxin = [];
|
|
Leaf.color = new Color(0.8, 0.8, 0.8);
|
|
|
|
Leaf.midlayerItems = [];
|
|
Leaf.frontlayerItems = [];
|
|
|
|
Leaf.finishedShape = new Path();
|
|
Leaf.finished = false;
|
|
Leaf.final = false;
|
|
|
|
//Leaf.mouse = new Path.Circle(new Point(0,0),10);
|
|
|
|
Leaf.uniform = true;
|
|
|
|
|
|
var leaf = new Leaf(new Point(view.bounds.width/2,view.bounds.height/2));
|
|
Leaf.trycount = 10;
|
|
|
|
/*view.onClick = function(event){
|
|
var svg = project.exportSVG({ asString: true });
|
|
var svgBlob = new Blob([svg], {type:"image/svg+xml;charset=utf-8"});
|
|
var svgUrl = URL.createObjectURL(svgBlob);
|
|
var downloadLink = document.createElement("a");
|
|
downloadLink.href = svgUrl;
|
|
downloadLink.download = "growth.svg";
|
|
document.body.appendChild(downloadLink);
|
|
downloadLink.click();
|
|
document.body.removeChild(downloadLink);
|
|
}*/
|
|
|
|
view.onFrame = function(event) {
|
|
// On each frame, rotate the path by 3 degrees:
|
|
|
|
if(leaf.shape.bounds.height<Leaf.maxSize){
|
|
leaf.iteration();
|
|
}else{
|
|
|
|
if(!Leaf.finished){
|
|
|
|
leaf.iteration();
|
|
|
|
|
|
|
|
|
|
|
|
}else{
|
|
if(!Leaf.final){
|
|
console.log('finalize');
|
|
leaf.removeSmallVeins();
|
|
|
|
leaf.sortChildbranches();
|
|
leaf.rearrangeBranches();
|
|
leaf.calcWidth();
|
|
var svgString = leaf.shape.exportSVG({asString: true, bounds: 'content'});
|
|
console.log(svgString);
|
|
leaf.shape.remove();
|
|
|
|
var midlayer = new Layer(Leaf.midlayerItems);
|
|
var frontlayer = new Layer(Leaf.frontlayerItems);
|
|
|
|
Leaf.final = true;
|
|
|
|
setTimeout(function() {
|
|
// fixing a race condition in some browsers
|
|
generateTextures();
|
|
renderScene();
|
|
}, 10);
|
|
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Auxin{
|
|
constructor(point, closestVein){
|
|
this.pos = point;
|
|
this.closest = closestVein;
|
|
this.drawNode(this.pos);
|
|
Leaf.auxin.push(this);
|
|
}
|
|
|
|
drawNode(point){
|
|
this.node = new Path.Circle(point,0);
|
|
this.node.fillColor = 'red';
|
|
}
|
|
|
|
remove(){
|
|
this.node.remove();
|
|
//this.node.fillColor = 'pink';
|
|
}
|
|
|
|
scale(perc, pos){
|
|
this.node.scale(perc, pos);
|
|
this.pos = this.node.position;
|
|
}
|
|
|
|
}
|
|
|
|
class Leaf{
|
|
|
|
|
|
|
|
constructor(startpoint) {
|
|
this.pos = startpoint;
|
|
|
|
//this.shape = new Path.Circle(new Point(this.pos.x-Leaf.size,this.pos.y-Leaf.size),Leaf.size);
|
|
|
|
/*this.shape = new Path();
|
|
this.shape.add(new Segment(new Point(this.pos.x, this.pos.y), new Point(Leaf.size,0), new Point(-Leaf.size,0)));
|
|
this.shape.add(new Point(this.pos.x, this.pos.y-Leaf.size));
|
|
this.shape.closed = true;
|
|
*/
|
|
|
|
var rect = new Rectangle(new Point(startpoint.x-Leaf.size/2, startpoint.y-Leaf.size), new Size(Leaf.size, Leaf.size));
|
|
this.shape = new Path.Ellipse(rect);
|
|
//this.shape.rotation = 20;
|
|
|
|
//this.shape = new Path.RegularPolygon(new Point(this.pos.x, this.pos.y-Leaf.size), 3, 50);
|
|
|
|
|
|
//this.shape = project.importSVG(footstring2);
|
|
//this.shape = project.importSVG(seatstring);
|
|
|
|
//this.shape = this.shape.children[1];
|
|
|
|
//this.shape = project.importSVG('leaf.svg', {insert: false, expandShapes: true});
|
|
//console.log('shape', this.shape);
|
|
//this.shape = this.shape.children[1];
|
|
|
|
this.shape.strokeColor = 'red';
|
|
this.shape.strokeWidth = 2;
|
|
this.shape.fillColor = 'yellow';
|
|
|
|
|
|
//this.shape.position = new Point(this.pos.x, this.pos.y);
|
|
this.shape.position = new Point(this.pos.x, this.pos.y);
|
|
this.shape.scale(Leaf.size/this.shape.bounds.height, this.pos);
|
|
//this.shape.translate(new Point(-this.shape.bounds.width/2+Leaf.veinR/2,-this.shape.bounds.height/3+Leaf.veinR/2));
|
|
|
|
|
|
|
|
this.veins = new VeinGraph(this.pos);
|
|
this.auxin = [];
|
|
this.growCount = 0;
|
|
}
|
|
|
|
iteration(){
|
|
|
|
this.growVeins();
|
|
this.removeConsumedAuxin();
|
|
if(this.shape.bounds.height<Leaf.maxSize){
|
|
this.growLeaf();
|
|
}
|
|
if(!this.placeAuxin()){
|
|
Leaf.trycount--;
|
|
}else{
|
|
Leaf.trycount = 10;
|
|
}
|
|
if(Leaf.trycount==0){
|
|
Leaf.finished = true;
|
|
}
|
|
this.attractVeins();
|
|
}
|
|
|
|
placeAuxin(){
|
|
var f = false;
|
|
for(var i = 0; i<Leaf.auxinNr; i++){
|
|
var randPoint = new Point(seededRand(), seededRand());
|
|
var point = new Point(this.shape.bounds.width, this.shape.bounds.height).multiply(randPoint).add(this.shape.bounds.topLeft);
|
|
|
|
if(this.shape.contains(point) && this.veins.getDist(point)>Leaf.veinR && !this.auxinTooClose(point)){
|
|
//if(this.isEmptyAuxinNeighbourhood(point) && this.isEmptyVeinNeighbourhood(point)){
|
|
this.auxin.push(new Auxin(point));
|
|
f = true;
|
|
|
|
|
|
//tmp.fillColor = 'green';
|
|
//Leaf.auxinArea.strokeColor = 'red';
|
|
|
|
//}
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
auxinTooClose(point){
|
|
for(var i = 0; i<Leaf.auxin.length; i++){
|
|
if(Leaf.auxin[i].pos.getDistance(point)<Leaf.auxinR){
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
removeConsumedAuxin(){
|
|
var newauxin = []
|
|
for(var i = 0; i<this.auxin.length; i++){
|
|
if(this.veins.getDist(this.auxin[i].pos)>Leaf.veinR){
|
|
newauxin.push(this.auxin[i]);
|
|
}else{
|
|
this.auxin[i].remove();
|
|
}
|
|
}
|
|
this.auxin = newauxin;
|
|
}
|
|
|
|
attractVeins(){
|
|
for(var i = 0; i<this.auxin.length; i++){
|
|
//var closestNode = this.veins.getClosest(this.auxin[i]);
|
|
var n = this.veins.getClosestNode(this.auxin[i].pos);
|
|
//var p = this.veins.getClosestNodePoint(this.auxin[i].pos);
|
|
var b = this.veins.getClosestBranch(this.auxin[i].pos);
|
|
n.setInfluence(this.auxin[i]);
|
|
//var line = new Path.Line(p, this.auxin[i].pos);
|
|
//line.strokeColor = 'grey';
|
|
|
|
this.auxin[i].node.fillColor = b.path.strokeColor;
|
|
|
|
}
|
|
}
|
|
|
|
growVeins(){
|
|
this.veins.grow();
|
|
}
|
|
|
|
growLeaf(){
|
|
var perc = (this.shape.bounds.height+Leaf.leafGrowRate)/this.shape.bounds.height;
|
|
this.shape.scale(perc, this.pos)
|
|
if(Leaf.uniform){
|
|
this.scaleAuxin(perc);
|
|
this.scaleBranches(perc);
|
|
}
|
|
}
|
|
|
|
scaleAuxin(perc){
|
|
for(var i = 0; i<Leaf.auxin.length; i++){
|
|
Leaf.auxin[i].scale(perc, this.pos);
|
|
}
|
|
}
|
|
|
|
scaleBranches(perc){
|
|
for(var i = 0; i<Leaf.branches.length; i++){
|
|
Leaf.branches[i].scale(perc, this.pos);
|
|
}
|
|
}
|
|
|
|
removeSmallVeins(){
|
|
this.veins.removeSmallVeins();
|
|
}
|
|
|
|
calcWidth(){
|
|
this.veins.calcWidth();
|
|
}
|
|
|
|
sortChildbranches(){
|
|
this.veins.sortChildbranches();
|
|
}
|
|
|
|
rearrangeBranches(){
|
|
this.veins.rearrangeBranches();
|
|
}
|
|
|
|
}
|
|
|
|
class VeinGraph{
|
|
|
|
constructor(point) {
|
|
this.root = new VeinBranch(point);
|
|
this.root.addConnection(new Point(point.x, point.y-10));
|
|
}
|
|
|
|
getClosestNode(point){
|
|
var b = this.getClosestBranch(point);
|
|
|
|
return b.getClosestNode(point);
|
|
}
|
|
|
|
getClosestNodePoint(point){
|
|
var b = this.getClosestBranch(point);
|
|
|
|
return b.getClosestNodePoint(point);
|
|
}
|
|
|
|
getClosestBranch(point){
|
|
//var b = this.root.getClosestBranch(point, this.root);
|
|
var b = this.root;
|
|
var dist = this.root.path.getNearestPoint(point).getDistance(point);
|
|
var dist2 = 0;
|
|
for(var i = 0; i<Leaf.branches.length; i++){
|
|
|
|
dist2 = Leaf.branches[i].path.getNearestPoint(point).getDistance(point);
|
|
|
|
if(dist2<dist){
|
|
|
|
dist = dist2;
|
|
b = Leaf.branches[i];
|
|
}
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
getDist(point){
|
|
var b = this.getClosestBranch(point);
|
|
var p = b.getClosestPoint(point);
|
|
return p.getDistance(point);
|
|
}
|
|
|
|
|
|
grow(){
|
|
this.root.grow();
|
|
}
|
|
|
|
removeSmallVeins(){
|
|
this.root.removeSmallVeins();
|
|
}
|
|
|
|
calcWidth(){
|
|
this.root.calcWidth();
|
|
}
|
|
|
|
sortChildbranches(){
|
|
this.root.sortChildbranches();
|
|
}
|
|
|
|
rearrangeBranches(){
|
|
this.root.rearrangeBranches();
|
|
}
|
|
|
|
}
|
|
|
|
class VeinBranch{
|
|
constructor(point) {
|
|
this.path = new Path();
|
|
//var col = Color.random();
|
|
//this.path.strokeColor = col;
|
|
this.path.strokeColor = 'red';
|
|
this.path.strokeCap = 'round';
|
|
this.path.strokeWidth = 3;
|
|
this.nodes = [];
|
|
this.childBranches = [];
|
|
if(point){
|
|
this.addConnection(point);
|
|
}
|
|
|
|
Leaf.branches.push(this);
|
|
}
|
|
|
|
rearrangeBranches(){
|
|
if(this.childBranches.length>0){
|
|
var newPath = this.path.splitAt(this.path.getLocationOf(this.childBranches[0].path.firstSegment.point));
|
|
//newPath.strokeColor = Color.random();
|
|
|
|
var newBranch = new VeinBranch();
|
|
newBranch.path = newPath;
|
|
console.log(newBranch.childBranches);
|
|
for(var i = 1; i<this.childBranches.length; i++){
|
|
newBranch.childBranches.push(this.childBranches[i]);
|
|
}
|
|
|
|
|
|
this.childBranches = [this.childBranches[0]];
|
|
this.childBranches.push(newBranch);
|
|
console.log(this);
|
|
|
|
this.childBranches[0].rearrangeBranches();
|
|
this.childBranches[1].rearrangeBranches();
|
|
}
|
|
|
|
}
|
|
|
|
sortChildbranches(){
|
|
//console.log(this.childBranches);
|
|
this.childBranches.sort((a, b) => (this.path.getOffsetOf(a.path.firstSegment.point) > this.path.getOffsetOf(b.path.firstSegment.point)) ? 1 : -1)
|
|
//console.log(this.childBranches);
|
|
for(var i = 0; i<this.childBranches.length; i++){
|
|
this.childBranches[i].sortChildbranches();
|
|
}
|
|
}
|
|
|
|
addConnection(point){
|
|
this.path.add(point);
|
|
this.nodes.push(new VeinNode());
|
|
}
|
|
|
|
addBranch(point1, point2){
|
|
var b = new VeinBranch(point1);
|
|
b.addConnection(point2);
|
|
this.childBranches.push(b);
|
|
}
|
|
|
|
getClosestPoint(point){
|
|
|
|
var p = this.path.getNearestPoint(point);
|
|
|
|
return p;
|
|
}
|
|
|
|
getClosestNode(point){
|
|
|
|
var dist = this.path.firstSegment.point.getDistance(point);
|
|
var node = 0;
|
|
for( var i = 1; i<this.path.segments.length; i++){
|
|
var compdist = this.path.segments[i].point.getDistance(point);
|
|
if(compdist<dist){
|
|
node = i;
|
|
dist = compdist;
|
|
}
|
|
}
|
|
return this.nodes[node];
|
|
}
|
|
|
|
getClosestNodePoint(point){
|
|
var dist = this.path.firstSegment.point.getDistance(point);
|
|
var node = 0;
|
|
for( var i = 1; i<this.path.segments.length; i++){
|
|
var compdist = this.path.segments[i].point.getDistance(point);
|
|
if(compdist<dist){
|
|
node = i;
|
|
dist = compdist;
|
|
}
|
|
}
|
|
return this.path.segments[node].point;
|
|
}
|
|
|
|
/*getClosestBranch(point, veinbranch){
|
|
|
|
var p = this.path.getNearestPoint(point);
|
|
var compdist = point.getDistance(p);
|
|
var pvb = veinbranch.path.getNearestPoint(point);
|
|
var dist = point.getDistance(pvb);
|
|
|
|
var smallest = this;
|
|
if(dist<compdist){
|
|
smallest = veinbranch;
|
|
compdist = dist;
|
|
}
|
|
for(var i = 0; i<this.childBranches.length; i++){
|
|
|
|
var b = this.childBranches[i].getClosestBranch(point,smallest);
|
|
p = b.path.getNearestPoint(point);
|
|
dist = point.getDistance(p);
|
|
if(dist<compdist){
|
|
smallest = this.childBranches[i];
|
|
compdist = dist;
|
|
}
|
|
}
|
|
return smallest;
|
|
|
|
}*/
|
|
|
|
grow(){
|
|
for(var i = 0; i<this.nodes.length; i++){
|
|
var n = this.nodes[i].grow(this.path.segments[i].point);
|
|
if(n!=null){
|
|
var newpoint = this.path.segments[i].point.add(n);
|
|
if(i==this.nodes.length-1){
|
|
this.addConnection(newpoint);
|
|
}else{
|
|
this.addBranch(this.path.segments[i].point,newpoint);
|
|
}
|
|
}
|
|
}
|
|
for(var i=0; i<this.childBranches.length; i++){
|
|
this.childBranches[i].grow();
|
|
}
|
|
}
|
|
|
|
scale(perc, pos){
|
|
this.path.scale(perc, pos);
|
|
}
|
|
|
|
remove(){
|
|
this.path.remove();
|
|
}
|
|
|
|
removeSmallVeins(){
|
|
for(var i = this.childBranches.length-1; i>=0; i--){
|
|
if(this.childBranches[i].path.length<Leaf.auxinR && this.childBranches[i].childBranches.length==0){
|
|
this.childBranches[i].remove();
|
|
this.childBranches.splice(i,1);
|
|
}else{
|
|
this.childBranches[i].removeSmallVeins();
|
|
}
|
|
}
|
|
}
|
|
|
|
calcWidth(){
|
|
if(this.childBranches.length==0){
|
|
//this.path.strokeWidth = 10;
|
|
//return 10;
|
|
this.path.strokeWidth = Leaf.auxinR-7;
|
|
this.path.strokeColor = Leaf.color;
|
|
return Leaf.auxinR-2;
|
|
}
|
|
this.path.bringToFront();
|
|
var wid = 0;
|
|
var col = -12;
|
|
for(var i = 0; i<this.childBranches.length; i++){
|
|
var w = this.childBranches[i].calcWidth();
|
|
wid = wid + Math.pow(w,5);
|
|
col += w;
|
|
}
|
|
wid = Math.pow(wid, 1/5);
|
|
this.path.strokeWidth = wid-9;
|
|
this.path.strokeColor = Leaf.color;
|
|
var innerPath1 = this.path.clone();
|
|
var innerPath2 = this.path.clone();
|
|
this.path.strokeColor.brightness -= col/127;
|
|
innerPath1.strokeWidth = (wid-3)/1.5;
|
|
innerPath1.strokeColor.brightness -= col/127;
|
|
innerPath2.strokeWidth = (wid-3)/2-2;
|
|
innerPath2.strokeColor.brightness -= col/16;
|
|
Leaf.midlayerItems.push(innerPath1);
|
|
Leaf.frontlayerItems.push(innerPath2);
|
|
return wid;
|
|
}
|
|
}
|
|
|
|
class VeinNode{
|
|
|
|
constructor() {
|
|
this.auxinInfluence = [];
|
|
}
|
|
|
|
grow(pos){
|
|
//
|
|
if(this.auxinInfluence.length>0){
|
|
var direction = new Point(0,0);
|
|
var oldDists = [];
|
|
for(var i = 0; i<this.auxinInfluence.length; i++){
|
|
var dir = this.auxinInfluence[i].pos.subtract(pos).normalize();
|
|
direction = direction.add(dir);
|
|
|
|
oldDists.push(this.auxinInfluence[i].pos.getDistance(pos));
|
|
}
|
|
if(direction.length<1){
|
|
return null;
|
|
}
|
|
direction = direction.normalize(Leaf.veinGrowRate);
|
|
this.auxinInfluence = [];
|
|
return direction;
|
|
|
|
}
|
|
|
|
this.auxinInfluence = [];
|
|
return null;
|
|
}
|
|
|
|
setInfluence(point){
|
|
this.auxinInfluence.push(point);
|
|
}
|
|
}
|
|
|
|
|
|
|