Skip to content

Instantly share code, notes, and snippets.

@AloiSama
Last active November 3, 2025 10:29
Show Gist options
  • Select an option

  • Save AloiSama/ee927b254a97b606721ddc119d1a49e3 to your computer and use it in GitHub Desktop.

Select an option

Save AloiSama/ee927b254a97b606721ddc119d1a49e3 to your computer and use it in GitHub Desktop.
Tree gen
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lumber Mill Simulator (Fixed & Debugged)</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f0f0f0;
padding: 15px;
margin: 0;
}
#debug-text {
font-size: 14px;
color: #666;
background-color: #eee;
padding: 5px 10px;
border-radius: 4px;
}
#canvas-container {
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
#controls {
margin-top: 15px;
display: flex;
gap: 15px;
}
button {
font-size: 16px;
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: #007aff;
color: white;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #0056b3;
}
.generate-btn {
background-color: #34c759 !important;
}
.generate-btn:hover {
background-color: #2a9e48 !important;
}
</style>
</head>
<body>
<p id="debug-text">Debug: p5.js is loading...</p>
<div id="canvas-container"></div>
<div id="controls"></div>
<script>
// --- GLOBALS ---
let tree;
let currentBoard = null;
let debugP; // Reference to the debug <p> tag
// Colors
const C_LIGHT = [227, 207, 177]; // Earlywood
const C_DARK = [163, 131, 103]; // Latewood
const C_BARK = [89, 58, 29];
const C_PITH = [112, 73, 37];
// Layout
const W = 1000; // Canvas Width
const H = 600; // Canvas Height
const TREE_CX = W * 0.3;
const TREE_CY = H / 2;
const BOARD_CX = W * 0.7;
const BOARD_CY = H / 2;
// --- p5.js SETUP & DRAW ---
function setup() {
let canvas = createCanvas(W, H);
canvas.parent('canvas-container');
// Get debug text reference
debugP = document.getElementById('debug-text');
// --- BUTTONS ---
let btn;
btn = createButton('Generate New Tree');
btn.parent('controls');
btn.class('generate-btn'); // Add class for styling
btn.mousePressed(generateNewTree);
btn = createButton('Cut Flatsawn Board');
btn.parent('controls');
btn.mousePressed(() => cutBoard('flatsawn'));
btn = createButton('Cut Quartersawn Board');
btn.parent('controls');
btn.mousePressed(() => cutBoard('quartersawn'));
btn = createButton('Cut Riftsawn Board');
btn.parent('controls');
btn.mousePressed(() => cutBoard('riftsawn'));
noiseDetail(8, 0.45);
generateNewTree();
// --- DEBUG ---
debugP.innerHTML = "Debug: p5.js setup complete. Code is running.";
debugP.style.color = 'green';
}
function draw() {
try {
background(250);
// Draw the tree cross-section
tree.drawCrossSection(TREE_CX, TREE_CY);
// If a board is cut, draw its location on the tree
if (currentBoard) {
currentBoard.drawCutLocation(TREE_CX, TREE_CY);
currentBoard.drawDisplay(BOARD_CX, BOARD_CY);
}
// Draw UI labels
drawLabels();
} catch (e) {
// --- DEBUG ---
// If the draw loop fails, report it!
debugP.innerHTML = `Debug: ERROR in draw() loop: ${e.message}`;
debugP.style.color = 'red';
noLoop(); // Stop the loop from crashing
}
}
function drawLabels() {
fill(50);
noStroke();
textAlign(CENTER, CENTER);
textSize(18);
text("Tree Cross-Section", TREE_CX, H - 30);
if (currentBoard) {
let name = currentBoard.type.charAt(0).toUpperCase() + currentBoard.type.slice(1);
text(`${name} Board`, BOARD_CX, H - 30);
textSize(14);
fill(100);
text("Face Grain", BOARD_CX, BOARD_CY - 135);
text("End Grain", BOARD_CX, BOARD_CY + 115);
}
}
// --- CORE FUNCTIONS ---
function generateNewTree() {
tree = new Tree();
currentBoard = null;
}
function cutBoard(type) {
currentBoard = new Board(type, tree);
}
// --- TREE CLASS ---
class Tree {
constructor() {
this.radius = random(120, 180);
this.pith = createVector(random(-15, 15), random(-15, 15));
this.ellipticity = random(0.9, 1.1); // How oval-shaped
this.numRings = int(random(30, 50));
this.noiseSeed = random(1000); // For grain wobble
this.ringSpacing = this.radius / this.numRings;
}
// Main function to draw the tree's end grain
drawCrossSection(cx, cy) {
push();
translate(cx + this.pith.x, cy + this.pith.y);
// Bark
fill(C_BARK);
noStroke();
this.drawRing(this.radius + 8, this.ellipticity);
// Wood
fill(C_LIGHT);
noStroke();
this.drawRing(this.radius, this.ellipticity);
// Rings
noFill();
strokeWeight(1.5);
for (let i = 1; i <= this.numRings; i++) {
let r = i * this.ringSpacing;
let c = (i % 2 === 0) ? color(C_DARK) : lerpColor(color(C_DARK), color(C_LIGHT), 0.35);
stroke(c);
this.drawRing(r, this.ellipticity);
}
// Pith
fill(C_PITH);
noStroke();
ellipse(0, 0, 5, 5);
pop();
}
// Helper to draw one (wobbly) ring
drawRing(radius, ellipticity) {
let noiseAmount = radius * 0.05; // Wobble is proportional to radius
beginShape();
for (let a = 0; a < TWO_PI; a += PI / 30) {
let r = radius + map(noise(this.noiseSeed + a * 5, radius * 0.1), 0, 1, -noiseAmount, noiseAmount);
let x = cos(a) * r * ellipticity;
let y = sin(a) * r;
vertex(x, y);
}
endShape(CLOSE);
}
}
// --- BOARD CLASS ---
class Board {
constructor(type, tree) {
this.type = type;
this.tree = tree;
this.boardWidth = 100;
this.boardThickness = 25;
this.boardLength = 350; // For display
this.cutAngle = 0; // Angle of the cut relative to horizontal
// Define cut position relative to pith (0,0)
this.cutPos = createVector(0, 0); // (x, y) offset from pith
if (type === 'flatsawn') {
this.cutAngle = 0; // 0 degrees
this.cutPos.y = random(tree.radius * 0.1, tree.radius * 0.6);
} else if (type === 'quartersawn') {
this.cutAngle = PI / 2; // 90 degrees
this.cutPos.x = random(-tree.radius * 0.1, tree.radius * 0.1);
} else if (type === 'riftsawn') {
this.cutAngle = PI / 4; // 45 degrees
this.cutPos.y = random(tree.radius * 0.1, tree.radius * 0.3);
}
}
// Draw the board (face and end)
drawDisplay(cx, cy) {
this.drawFaceGrain(cx, cy - 80);
this.drawEndGrain(cx, cy + 150);
}
// --- Draw the "Face Grain" (FIXED LOGIC) ---
drawFaceGrain(cx, cy) {
push();
translate(cx - this.boardLength / 2, cy - this.boardWidth / 2);
// Draw board background
fill(C_LIGHT);
stroke(C_DARK);
strokeWeight(1);
rect(0, 0, this.boardLength, this.boardWidth);
// --- Procedural Grain Logic (Pixel-by-pixel) ---
let grainNoiseSeed = this.tree.noiseSeed + 1000;
let grainStretch = 0.015; // How long the grain streaks are
strokeWeight(1.5); // Draw points
for (let x = 0; x < this.boardLength; x += 1) { // Iterate along length
for (let y = 0; y < this.boardWidth; y += 1) { // Iterate along width
// y_rel is the distance from the center of the board's width
let y_rel = y - this.boardWidth / 2;
// Calculate this pixel's (x,y) position in the end-grain plane
let x_pos = this.cutPos.x + cos(this.cutAngle) * y_rel;
let y_pos = this.cutPos.y + sin(this.cutAngle) * y_rel;
// Calculate distance to pith in 2D
let dist = sqrt(x_pos * x_pos + y_pos * y_pos);
// Add organic wobble
let n = noise(x * grainStretch, dist * 0.1, grainNoiseSeed);
let wobble = map(n, 0, 1, -this.tree.ringSpacing, this.tree.ringSpacing);
let final_dist = dist + wobble;
// Find what fraction of a ring we are in
let frac = (final_dist % this.tree.ringSpacing) / this.tree.ringSpacing;
// Check if we are in the "latewood" (dark) part of a ring
if (frac < 0.5) { // 50% latewood
let c = lerpColor(color(C_DARK), color(C_LIGHT), frac / 0.5);
stroke(c);
point(x, y); // Draw the pixel
}
}
}
pop();
}
// --- Draw the "End Grain" (FIXED LOGIC) ---
drawEndGrain(cx, cy) {
push();
// Go to top-left of end-grain box
translate(cx - this.boardWidth / 2, cy - this.boardThickness / 2);
// 1. Create clipping region
beginShape();
vertex(0, 0);
vertex(this.boardWidth, 0);
vertex(this.boardWidth, this.boardThickness);
vertex(0, this.boardThickness);
endShape(CLOSE);
clip();
// 2. Draw the *entire* tree, but translated and rotated
// to match the cut.
// We move our coordinate system so the *center* of the board's
// end grain (which is at x=this.boardWidth/2, y=this.boardThickness/2)
// aligns with the tree's cut position.
translate(this.boardWidth / 2, this.boardThickness / 2);
rotate(-this.cutAngle);
translate(-this.cutPos.x, -this.cutPos.y);
// Now draw the tree relative to its visual center (pith included)
this.tree.drawCrossSection(TREE_CX, TREE_CY);
pop();
}
// --- Draw a rectangle on the main tree (FIXED LOGIC) ---
drawCutLocation(tree_cx, tree_cy) {
push();
// Go to tree's visual center (including pith offset)
translate(tree_cx + this.tree.pith.x, tree_cy + this.tree.pith.y);
// Go to cut location
translate(this.cutPos.x, this.cutPos.y);
// Rotate to match the cut angle
rotate(this.cutAngle);
// Draw the end-grain box
noFill();
stroke(255, 0, 0); // Bright red
strokeWeight(2.5);
rectMode(CENTER);
rect(0, 0, this.boardWidth, this.boardThickness);
pop();
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment