//
//  growPlants.js 
//  examples
//
//  Created by Benjamin Arnold on May 29, 2014
//  Copyright 2014 High Fidelity, Inc.
//
//  This sample script allows the user to grow different types of plants on the voxels
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z fighting
var previewLineWidth = 2.0;

var voxelSize = 1;
var windowDimensions = Controller.getViewportDimensions();
var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/";

var MAX_VOXEL_SCALE_POWER = 5;
var MIN_VOXEL_SCALE_POWER = -8;
var MAX_VOXEL_SCALE = Math.pow(2.0, MAX_VOXEL_SCALE_POWER);
var MIN_VOXEL_SCALE = Math.pow(2.0, MIN_VOXEL_SCALE_POWER);


var linePreviewTop = Overlays.addOverlay("line3d", {
                                         position: { x: 0, y: 0, z: 0},
                                         end: { x: 0, y: 0, z: 0},
                                         color: { red: 0, green: 255, blue: 0},
                                         alpha: 1,
                                         visible: false,
                                         lineWidth: previewLineWidth
                                         });

var linePreviewBottom = Overlays.addOverlay("line3d", {
                                            position: { x: 0, y: 0, z: 0},
                                            end: { x: 0, y: 0, z: 0},
                                            color: { red: 0, green: 255, blue: 0},
                                            alpha: 1,
                                            visible: false,
                                            lineWidth: previewLineWidth
                                            });

var linePreviewLeft = Overlays.addOverlay("line3d", {
                                          position: { x: 0, y: 0, z: 0},
                                          end: { x: 0, y: 0, z: 0},
                                          color: { red: 0, green: 255, blue: 0},
                                          alpha: 1,
                                          visible: false,
                                          lineWidth: previewLineWidth
                                          });

var linePreviewRight = Overlays.addOverlay("line3d", {
                                           position: { x: 0, y: 0, z: 0},
                                           end: { x: 0, y: 0, z: 0},
                                           color: { red: 0, green: 255, blue: 0},
                                           alpha: 1,
                                           visible: false,
                                           lineWidth: previewLineWidth
                                           });

                              
var UIColor = { red: 0, green: 160, blue: 0};
var activeUIColor = { red: 0, green: 255, blue: 0};
            
var toolHeight = 50;
var toolWidth = 50;

var editToolsOn = true; 

var voxelToolSelected = false;
        
var scaleSelectorWidth = 144;
var scaleSelectorHeight = 37;        

var scaleSelectorX = windowDimensions.x / 5.0;
var scaleSelectorY = windowDimensions.y - scaleSelectorHeight;             
                                     
var voxelTool = Overlays.addOverlay("image", {
                                   x: scaleSelectorX + scaleSelectorWidth + 1, y: windowDimensions.y - toolHeight, width: toolWidth, height: toolHeight,
                                   subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight },
                                   imageURL: toolIconUrl + "voxel-tool.svg",
                                   visible: editToolsOn,
                                   color: UIColor,
                                   alpha: 0.9
                                   });
                                           
var copyScale = true;
function ScaleSelector() {
   this.x = scaleSelectorX;
   this.y = scaleSelectorY;
   this.width = scaleSelectorWidth;
   this.height = scaleSelectorHeight;

   this.displayPower = false;
   this.scale = 1.0;
   this.power = 0;

   this.FIRST_PART = this.width * 40.0 / 100.0;
   this.SECOND_PART = this.width * 37.0 / 100.0;
   
   this.buttonsOverlay = Overlays.addOverlay("image", {
                                             x: this.x, y: this.y,
                                             width: this.width, height: this.height,
                                             //subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight },
                                             imageURL: toolIconUrl + "voxel-size-selector.svg",
                                             alpha: 0.9,
                                             visible: editToolsOn,
                                             color: activeUIColor
                                             });
   this.textOverlay = Overlays.addOverlay("text", {
                                          x: this.x + this.FIRST_PART, y: this.y,
                                          width: this.SECOND_PART, height: this.height,
                                          topMargin: 13,
                                          text: this.scale.toString(),
                                          alpha: 0.0,
                                          visible: editToolsOn,
                                          color: activeUIColor
                                          });
   this.powerOverlay = Overlays.addOverlay("text", {
                                           x: this.x + this.FIRST_PART, y: this.y,
                                           width: this.SECOND_PART, height: this.height,
                                           leftMargin: 28,
                                           text: this.power.toString(),
                                           alpha: 0.0,
                                           visible: false,
                                           color: activeUIColor
                                           });
   this.setScale = function(scale) {
       if (scale > MAX_VOXEL_SCALE) {
           scale = MAX_VOXEL_SCALE;
       }
       if (scale < MIN_VOXEL_SCALE) {
           scale = MIN_VOXEL_SCALE;
       }

       this.scale = scale;
       this.power = Math.floor(Math.log(scale) / Math.log(2));
       this.update();
   }

   this.show = function(doShow) {
       Overlays.editOverlay(this.buttonsOverlay, {visible: doShow});
       Overlays.editOverlay(this.textOverlay, {visible: doShow});
       Overlays.editOverlay(this.powerOverlay, {visible: doShow && this.displayPower});
   }

   this.move = function() {
       this.x = swatchesX + swatchesWidth;
       this.y = swatchesY;

       Overlays.editOverlay(this.buttonsOverlay, {
                            x: this.x, y: this.y,
                            });
       Overlays.editOverlay(this.textOverlay, {
                            x: this.x + this.FIRST_PART, y: this.y,
                            });
       Overlays.editOverlay(this.powerOverlay, {
                            x: this.x + this.FIRST_PART, y: this.y,
                            });
   }


   this.switchDisplay = function() {
       this.displayPower = !this.displayPower;

       if (this.displayPower) {
           Overlays.editOverlay(this.textOverlay, {
                                leftMargin: 18,
                                text: "2"
                                });
           Overlays.editOverlay(this.powerOverlay, {
                                text: this.power.toString(),
                                visible: editToolsOn
                                });
       } else {
           Overlays.editOverlay(this.textOverlay, {
                                leftMargin: 13,
                                text: this.scale.toString()
                                });
           Overlays.editOverlay(this.powerOverlay, {
                                visible: false
                                });
       }
   }

   this.update = function() {
       if (this.displayPower) {
           Overlays.editOverlay(this.powerOverlay, {text: this.power.toString()});
       } else {
           Overlays.editOverlay(this.textOverlay, {text: this.scale.toString()});
       }
   }

   this.incrementScale = function() {
       copyScale = false;
       if (this.power < MAX_VOXEL_SCALE_POWER) {
           ++this.power;
           this.scale *= 2.0;
           this.update();
       }
   }

   this.decrementScale = function() {
       copyScale = false;
       if (MIN_VOXEL_SCALE_POWER < this.power) {
           --this.power;
           this.scale /= 2.0;
           this.update();
       }
   }

   this.clicked = function(x, y) {
       if (this.x < x  && x < this.x + this.width &&
           this.y < y && y < this.y + this.height) {

           if (x < this.x + this.FIRST_PART) {
               this.decrementScale();
           } else if (x < this.x + this.FIRST_PART + this.SECOND_PART) {
               this.switchDisplay();
           } else {
               this.incrementScale();
           }
           return true;
       }
       return false;
   }

   this.cleanup = function() {
       Overlays.deleteOverlay(this.buttonsOverlay);
       Overlays.deleteOverlay(this.textOverlay);
       Overlays.deleteOverlay(this.powerOverlay);
   }

}
var scaleSelector = new ScaleSelector();

                                           
function calculateVoxelFromIntersection(intersection, operation) {

    var resultVoxel;
   
    var x;
    var y;
    var z;
    
    // if our "target voxel size" is larger than the voxel we intersected with, then we need to find the closest
    // ancestor voxel of our target size that contains our intersected voxel.
    if (voxelSize > intersection.voxel.s) {
        x = Math.floor(intersection.voxel.x / voxelSize) * voxelSize;
        y = Math.floor(intersection.voxel.y / voxelSize) * voxelSize;
        z = Math.floor(intersection.voxel.z / voxelSize) * voxelSize;
    } else {
        // otherwise, calculate the enclosed voxel of size voxelSize that the intersection point falls inside of.
        // if you have a voxelSize that's smaller than the voxel you're intersecting, this calculation will result
        // in the subvoxel that the intersection point falls in, if the target voxelSize matches the intersecting
        // voxel this still works and results in returning the intersecting voxel which is what we want
        var adjustToCenter = Vec3.multiply(Voxels.getFaceVector(intersection.face), (voxelSize * -0.5));
       
        var centerOfIntersectingVoxel = Vec3.sum(intersection.intersection, adjustToCenter);
        x = Math.floor(centerOfIntersectingVoxel.x / voxelSize) * voxelSize;
        y = Math.floor(centerOfIntersectingVoxel.y / voxelSize) * voxelSize;
        z = Math.floor(centerOfIntersectingVoxel.z / voxelSize) * voxelSize;
    }
    resultVoxel = { x: x, y: y, z: z, s: voxelSize };
    var highlightAt = { x: x, y: y, z: z, s: voxelSize };
    


    // we only do the "add to the face we're pointing at" adjustment, if the operation is an add
    // operation, and the target voxel size is equal to or smaller than the intersecting voxel.
    var wantAddAdjust = (operation == "add" && (voxelSize <= intersection.voxel.s));
   
    // now we also want to calculate the "edge square" for the face for this voxel
    if (intersection.face == "MIN_X_FACE") {
        
        highlightAt.x = x - zFightingSizeAdjust;
        if (wantAddAdjust) {
            resultVoxel.x -= voxelSize;
        }
        
        resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
        resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
        resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
        resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
        
    } else if (intersection.face == "MAX_X_FACE") {
        
        highlightAt.x = x + voxelSize + zFightingSizeAdjust;
        if (wantAddAdjust) {
            resultVoxel.x += resultVoxel.s;
        }
        
        resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
        resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
        resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust };
        resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
        
    } else if (intersection.face == "MIN_Y_FACE") {
        
        highlightAt.y = y - zFightingSizeAdjust;
        if (wantAddAdjust) {
            resultVoxel.y -= voxelSize;
        }
        
        resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust  };
        resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust };
        resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z  + voxelSize - zFightingSizeAdjust };
        resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust };
        
    } else if (intersection.face == "MAX_Y_FACE") {
        
        highlightAt.y = y + voxelSize + zFightingSizeAdjust;
        if (wantAddAdjust) {
            resultVoxel.y += voxelSize;
        }
        
        resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust };
        resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust};
        resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z  + voxelSize - zFightingSizeAdjust};
        resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust};
        
    } else if (intersection.face == "MIN_Z_FACE") {
        
        highlightAt.z = z - zFightingSizeAdjust;
        if (wantAddAdjust) {
            resultVoxel.z -= voxelSize;
        }
        
        resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z };
        resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z};
        resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z };
        resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z};
        
    } else if (intersection.face == "MAX_Z_FACE") {
        
        highlightAt.z = z + voxelSize + zFightingSizeAdjust;
        if (wantAddAdjust) {
            resultVoxel.z += voxelSize;
        }
        
        resultVoxel.bottomLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z };
        resultVoxel.bottomRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z};
        resultVoxel.topLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z };
        resultVoxel.topRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z};
        
    }
    return resultVoxel;
}
                                           
var trackLastMouseX = 0;
var trackLastMouseY = 0;

function showPreviewLines() {

    var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY);

    var intersection = Voxels.findRayIntersection(pickRay);

    if (intersection.intersects) {
        var resultVoxel = calculateVoxelFromIntersection(intersection, "");
        Overlays.editOverlay(linePreviewTop, { position: resultVoxel.topLeft, end: resultVoxel.topRight, visible: true });
        Overlays.editOverlay(linePreviewBottom, { position: resultVoxel.bottomLeft, end: resultVoxel.bottomRight, visible: true });
        Overlays.editOverlay(linePreviewLeft, { position: resultVoxel.topLeft, end: resultVoxel.bottomLeft, visible: true });
        Overlays.editOverlay(linePreviewRight, { position: resultVoxel.topRight, end: resultVoxel.bottomRight, visible: true });
   } else {
        Overlays.editOverlay(linePreviewTop, { visible: false });
        Overlays.editOverlay(linePreviewBottom, { visible: false });
        Overlays.editOverlay(linePreviewLeft, { visible: false });
        Overlays.editOverlay(linePreviewRight, { visible: false });
    }
}

function mouseMoveEvent(event) {
    trackLastMouseX = event.x;
    trackLastMouseY = event.y;
    if (!voxelToolSelected) {
        return;
    }
    showPreviewLines();
}


// Array of possible trees, right now there is only one
var treeTypes = [];

treeTypes.push({
    name: "Tall Green",
    // Voxel Colors
    wood: { r: 133, g: 81, b: 53 },
    leaves: { r: 22, g: 83, b: 31 },

    // How tall the tree is
    height: { min: 20, max: 60 },
    middleHeight: 0.3,

    // Chance of making a branch
    branchChance: { min: 0.01, max: 0.1 },
    branchLength: { min: 30, max: 60 },
    branchThickness: { min: 2, max: 7},

    // The width of the core, affects width and shape
    coreWidth: { min: 1, max: 4 },

    //TODO: Make this quadratic splines instead of linear
    bottomThickness: { min: 2, max: 8 },
    middleThickness: { min: 1, max: 4 },
    topThickness: { min: 3, max: 6 },
    
    //Modifies leaves at top
    leafCapSizeOffset: 0
});

// Applies noise to color
var colorNoiseRange = 0.2;


// Useful constants
var LEFT = 0;
var BACK = 1;
var RIGHT = 2;
var FRONT = 3;
var UP = 4;

// Interpolates between min and max of treevar based on b
function interpolate(treeVar, b) {
    return (treeVar.min + (treeVar.max - treeVar.min) * b);
}

function makeBranch(x, y, z, step, length, dir, thickness, wood, leaves) {
    var moveDir;

    var currentThickness;
    //thickness attenuates to thickness - 3
    var finalThickness = thickness - 3;
    if (finalThickness < 1) {
        finalThickness = 1;
    }
    //Iterative branch generation
    while (true) {
   
        //If we are at the end, place a ball of leaves
        if (step == 0) {
            makeSphere(x, y, z, 2 + finalThickness, leaves); 
            return;
        }
        //thickness attenuation
        currentThickness = Math.round((finalThickness + (thickness - finalThickness) * (step/length))) - 1;
		
        
        // If the branch is thick, grow a vertical slice
        if (currentThickness > 0) {
            for (var i = -currentThickness; i <= currentThickness; i++) {
                var len = currentThickness - Math.abs(i);
                switch (dir) {
                case 0: //left
                case 2: //right
                    growInDirection(x, y + i * voxelSize, z, len, len, BACK, wood, false, true);
                    growInDirection(x, y + i * voxelSize, z, len, len, FRONT, wood, false, false)          
                    break;
                case 1: //back
                case 3: //front
                    growInDirection(x, y + i * voxelSize, z, len, len, LEFT, wood, false, true);
                    growInDirection(x, y + i * voxelSize, z, len, len, RIGHT, wood, false, false)         
                    break;
                }
            }
        } else {
            //Otherwise place a single voxel
            var colorNoise = (colorNoiseRange * Math.random() - colorNoiseRange * 0.5) + 1.0;
            Voxels.setVoxel(x, y, z, voxelSize, wood.r * colorNoise, wood.g * colorNoise, wood.b * colorNoise);
        }

        // determines random change in direction for branch
        var r = Math.floor(Math.random() * 9);

         
        if (r >= 6){
            moveDir = dir; //in same direction
        } else if (r >= 4) {
            moveDir = UP; //up
        }
        else if (dir == LEFT){
            if (r >= 2){
                moveDir = FRONT;
            }
            else{
                moveDir = BACK;
            }
        }
        else if (dir == BACK){
            if (r >= 2){
                moveDir = LEFT;
            }
            else{
                moveDir = RIGHT;
            }
        }
        else if (dir == RIGHT){
            if (r >= 2){
                moveDir = BACK;
            }
            else{
                moveDir = FRONT;
            }
        }
        else if (dir == FRONT){
            if (r >= 2){
                moveDir = RIGHT;
            }
            else{
                moveDir = LEFT;
            }
        }

        //Move the branch by moveDir
        switch (moveDir) {
        case 0: //left
 
            x = x - voxelSize;
            break;
        case 1: //back
            z = z - voxelSize;
            break;
        case 2: //right
            x = x + voxelSize;
            break;
        case 3: //front
            z = z + voxelSize;
            break;        
        case 4: //up
            y = y + voxelSize;
            break;
        }
        
	step--;
    }
}

// Places a sphere of voxels
function makeSphere(x, y, z, radius, color) {
    if (radius <= 0) {
        return;
    }
    var width = radius * 2 + 1;
    var distance;
    
    for (var i = -radius; i <= radius; i++){
        for (var j = -radius; j <= radius; j++){
            for (var k = -radius; k <= radius; k++){
                distance = Math.sqrt(i * i + j * j + k * k);
                if (distance <= radius){
                     var colorNoise = (colorNoiseRange * Math.random() - colorNoiseRange * 0.5) + 1.0;
                     Voxels.setVoxel(x + i * voxelSize, y + j * voxelSize, z + k * voxelSize, voxelSize, color.r * colorNoise, color.g * colorNoise, color.b * colorNoise);
                }
            }
        }
    }
}

function growInDirection(x, y, z, step, length, dir, color, isSideBranching, addVoxel) {
    

    if (addVoxel == true) {
        var colorNoise = (colorNoiseRange * Math.random() - colorNoiseRange * 0.5) + 1.0;
 
        Voxels.setVoxel(x, y, z, voxelSize, color.r * colorNoise, color.g * colorNoise, color.b * colorNoise);
	}

    // If this is a main vein, it will branch outward perpendicular to its motion
	if (isSideBranching == true){
        var step2;
		if (step >= length - 1){
			step2 = length;
		}
		else{
			step2 = step + 1;
		}
        growInDirection(x, y, z, step, length, BACK, color, false, false);
        growInDirection(x, y, z, step, length, FRONT, color, false, false);
	}

	if (step < 1) return;

    // Recursively move in the direction
	if (dir == LEFT) { //left
		growInDirection(x - voxelSize, y, z, step - 1, length, dir, color, isSideBranching, true);
	}
	else if (dir == BACK) { //back
		growInDirection(x, y, z - voxelSize, step - 1, length, dir, color, isSideBranching, true);
    }
	else if (dir == RIGHT) { //right
		growInDirection(x + voxelSize, y, z, step - 1, length, dir, color, isSideBranching, true);
	}
	else if (dir == FRONT) {//front
	    growInDirection(x, y, z + voxelSize, step - 1, length, dir, color, isSideBranching, true);
	}

}

// Grows the thickness of the tree
function growHorizontalSlice(x, y, z, thickness, color, side) {
    // The side variable determines which directions we should grow in
    // it is an optimization that prevents us from visiting voxels multiple
    // times for trees with a coreWidth > 1
    
    // side:
    //  8 == all directions
    //  0  1  2
    //  3 -1  4
    //  5  6  7
    
    Voxels.setVoxel(x, y, z, voxelSize, color.r, color.g, color.b);
    
    //We are done if there is no thickness
    if (thickness == 0) {
        return;
    }
    
    
    switch (side) {
    case 0:
        growInDirection(x, y, z, thickness, thickness, LEFT, color, true, false);
        break;
    case 1:   
        growInDirection(x, y, z, thickness, thickness, BACK, color, false, false); 
        break;
    case 2:
        growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false);   
        break;
    case 3:
        growInDirection(x, y, z, thickness, thickness, LEFT, color, false, false);
        break;
    case 4:
        growInDirection(x, y, z, thickness, thickness, BACK, color, false, false);
        break;
    case 5:
        growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false);
        break;
    case 6:
        growInDirection(x, y, z, thickness, thickness, FRONT, color, false, false); 
        break;
    case 7:
        growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false);
        break;
    case 8:
        if (thickness > 1){
            growInDirection(x, y, z, thickness, thickness, LEFT, color, true, false);
            growInDirection(x, y, z, thickness, thickness, RIGHT, color, true, false)     
        } else if (thickness == 1){
            Voxels.setVoxel(x - voxelSize, y, z, voxelSize, color.r, color.g, color.b);
            Voxels.setVoxel(x + voxelSize, y, z, voxelSize, color.r, color.g, color.b);
            Voxels.setVoxel(x, y, z - voxelSize, voxelSize, color.r, color.g, color.b);
            Voxels.setVoxel(x, y, z + voxelSize, voxelSize, color.r, color.g, color.b);
        }
        break;
    }
}

function computeSide(x, z, coreWidth) {
    // side:
    //  8 == all directions
    //  0  1  2
    //  3 -1  4
    //  5  6  7
   
    // if the core is only a single block, we can grow out in all directions
    if (coreWidth == 1){
        return 8;
    }
    
    // Back face
    if (z == 0) {
        if (x == 0) {
            return 0;
        } else if (x == coreWidth - 1) {
            return 2;
        } else {
            return 1;
        }  
    }
    
    // Front face
    if (z == (coreWidth - 1)) {
        if (x == 0) {
            return 5;
        } else if (x == (coreWidth - 1)) {
            return 7;
        } else {
            return 6;
        }  
    }
    
    // Left face
    if (x == 0) {
        return 3;
    }
    
    // Right face
    if (x == (coreWidth - 1)) {
        return 4;
    }
     
    //Interior
    return -1;
}


function growTree(x, y, z, tree) {

    // The size of the tree, from 0-1
    var treeSize = Math.random();
    
    // Get tree properties by interpolating with the treeSize
    var height = interpolate(tree.height, treeSize);
    var baseHeight = Math.ceil(tree.middleHeight * height);
    var bottomThickness = interpolate(tree.bottomThickness, treeSize);
    var middleThickness = interpolate(tree.middleThickness, treeSize);
    var topThickness = interpolate(tree.topThickness, treeSize);
    var coreWidth = Math.ceil(interpolate(tree.coreWidth, treeSize));

    var thickness;
    var side;
    
    //Loop upwards through each slice of the tree
    for (var i = 0; i < height; i++){
    
        //Branch properties are based on current height as well as the overall tree size
        var branchChance = interpolate(tree.branchChance, i / height);
        var branchLength = Math.ceil(interpolate(tree.branchLength, (i / height) * treeSize));
        var branchThickness = Math.round(interpolate(tree.branchThickness, (i / height) * treeSize));
        
        // Get the "thickness" of the tree by doing linear interpolation between the middle thickness
        // and the top and bottom thickness.
        if (i <= baseHeight && baseHeight != 0){
            thickness = (i / (baseHeight) * (middleThickness - bottomThickness) + bottomThickness);
        } else {
            var denom = ((height - baseHeight)) * (topThickness - middleThickness) + middleThickness;
            if (denom != 0) {
                thickness = (i - baseHeight) / denom;
            } else {
                thickness = 0;
            }
        }
        // The core of the tree is a vertical rectangular prism through the middle of the tree
        
        //Loop through the "core", which helps shape the trunk
        var startX = x - Math.floor(coreWidth / 2) * voxelSize;
        var startZ = z - Math.floor(coreWidth / 2) * voxelSize;
        for (var j = 0; j < coreWidth; j++) {
            for (var k = 0; k < coreWidth; k++) {
                //determine which side of the tree we are on
                side = computeSide(j, k, coreWidth);
                //grow a horizontal slice of the tree
                growHorizontalSlice(startX + j * voxelSize, y + i * voxelSize, startZ + k * voxelSize, Math.floor(thickness), tree.wood, side);
                
                // Branches
                if (side != -1) {
                    var r = Math.random();
                    if (r <= branchChance){
                        var dir = Math.floor((Math.random() * 4)); 
                        makeBranch(startX + j * voxelSize, y + i * voxelSize, startZ + k * voxelSize, branchLength, branchLength, dir, branchThickness, tree.wood, tree.leaves);
                       
                    }
                }
            }
        }
    }
    
    makeSphere(x, y + height * voxelSize, z, topThickness + coreWidth + tree.leafCapSizeOffset, tree.leaves);
}

function mousePressEvent(event) {
    var mouseX = event.x;
    var mouseY = event.y;
    
    var clickedOnSomething = false;
    // Check if we clicked an overlay
    var clickedOverlay = Overlays.getOverlayAtPoint({x: mouseX, y: mouseY});
    
    if (clickedOverlay == voxelTool) {
        voxelToolSelected = !voxelToolSelected;
        
        if (voxelToolSelected == true) {
            Overlays.editOverlay(voxelTool, {
                                 color: activeUIColor
                                 });
        } else { 
            Overlays.editOverlay(voxelTool, {
                                 color: UIColor
                                 });
        }
        
        clickedOnSomething = true;
    } else if (scaleSelector.clicked(event.x, event.y)) {        
        clickedOnSomething = true;
        voxelSize = scaleSelector.scale;
    }
    
    // Return if we clicked on the UI or the voxel tool is disabled
    if (clickedOnSomething || !voxelToolSelected) {
        return;
    }  
    
    // Compute the picking ray for the click
    var pickRay = Camera.computePickRay(event.x, event.y);
    var intersection = Voxels.findRayIntersection(pickRay);
    var resultVoxel = calculateVoxelFromIntersection(intersection, "add");

    // Currently not in use, could randomly select a tree
    var treeIndex = Math.floor(Math.random() * treeTypes.length);
    
    // Grow the first tree type
    growTree(resultVoxel.x, resultVoxel.y, resultVoxel.z, treeTypes[0]);

}


function scriptEnding() {
    Overlays.deleteOverlay(linePreviewTop);
    Overlays.deleteOverlay(linePreviewBottom);
    Overlays.deleteOverlay(linePreviewLeft);
    Overlays.deleteOverlay(linePreviewRight);
    scaleSelector.cleanup();
    Overlays.deleteOverlay(voxelTool);
}

Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);

Script.scriptEnding.connect(scriptEnding);

Voxels.setPacketsPerSecond(10000);