fractalkitty looking for math? fractalkitty.com

all

Little Gems

#noise#random#trig#3d#interactive#animation#dom#tool

angleverticesfacesseedValuenoiseOffsetfacevertexIndexregenerateupdateShapelayerssegmentstwistnoiseScalenoiseAmountsafeRadiusgetNoiseValuegenerateChiralShaperadiusbottomCenterIndexlayerlayerTwistbaseXbaseYnoiseVal


let angle = 0;
let vertices = [];
let faces = [];
let seedValue = 0;
let noiseOffset = 0;

function setup() {
  cnv = createCanvas(400, 400, WEBGL);
  seedValue = random(1000);
  updateShape();
  angle = PI / 5;
}

function draw() {
  background(255);
  orbitControl(5, 5);
  rotateX(angle);
  rotateY(angle * 0.7);
  rotateZ(angle * 0.5);
  pointLight(205, 205, 205, -600, -600, 600);
  pointLight(205, 205, 205, 600, 1200, 1200);
  pointLight(205, 205, 205, -600, -600, -600);
  stroke(100, 150, 200, 100);
  beginShape(TRIANGLES);
  for (let face of faces) {
    for (let vertexIndex of face) {
      let v = vertices[vertexIndex];
      specularMaterial(100, 140, 155);
      vertex(v.x, v.y, v.z);
    }
  }
  endShape();
  angle += 0.005;
}

function regenerate() {
  seedValue = random(1000);
  noiseOffset = random(1000);
  updateShape();
}

function updateShape() {
  const layers = parseInt(document.getElementById("layers").value);
  const segments = parseInt(document.getElementById("segments").value);
  const twist = parseInt(document.getElementById("twist").value) * 0.1;
  const height = parseInt(document.getElementById("height").value);
  const noiseScale =
    parseInt(document.getElementById("noiseScale").value) * 0.01;
  const noiseAmount = parseInt(document.getElementById("noiseAmount").value);
  const safeRadius = parseInt(document.getElementById("safeRadius").value);
  document.getElementById("layersValue").textContent = round(layers, 1);
  document.getElementById("segmentsValue").textContent = round(segments, 1);
  document.getElementById("twistValue").textContent = round(twist, 1);
  document.getElementById("heightValue").textContent = round(height, 1);
  document.getElementById("noiseScaleValue").textContent = round(noiseScale, 1);
  document.getElementById("noiseAmountValue").textContent = round(
    noiseAmount,
    1
  );
  document.getElementById("safeRadiusValue").textContent = round(safeRadius, 1);

  generateChiralShape(
    segments,
    twist,
    height,
    noiseScale,
    noiseAmount,
    safeRadius,
    layers
  );
}

function getNoiseValue(x, y, z, scale) {
  return noise(
    (x + seedValue) * scale,
    (y + seedValue) * scale,
    (z + noiseOffset) * scale
  );
}

function generateChiralShape(
  segments,
  twist,
  height,
  noiseScale,
  noiseAmount,
  safeRadius,
  layers
) {
  vertices = [];
  faces = [];

  const radius = 100;

  vertices.push({ x: 0, y: 0, z: -height / 2 });
  const bottomCenterIndex = 0;

  for (let layer = 0; layer <= layers; layer++) {
    const z = (layer / layers) * height - height / 2;
    const layerTwist = (layer / layers) * twist;

    for (let i = 0; i < segments; i++) {
      const angle = (i / segments) * TWO_PI + layerTwist;
      const baseX = cos(angle);
      const baseY = sin(angle);

      const r = radius * (1 - Math.abs(layer - layers / 2) / (layers / 1.5));

      const noiseVal = getNoiseValue(
        baseX + layer * 0.1,
        baseY + layer * 0.1,
        z * 0.01,
        noiseScale
      );

      const displacement = map(noiseVal, 0, 1, -noiseAmount, noiseAmount);
      const rawRadius = r + displacement;
      const safeR = max(rawRadius, safeRadius);

      vertices.push({
        x: baseX * safeR,
        y: baseY * safeR,
        z: z + displacement * 0.5
      });
    }
  }

  vertices.push({ x: 0, y: 0, z: height / 2 });
  const topCenterIndex = vertices.length - 1;

  for (let layer = 0; layer < layers; layer++) {
    for (let i = 0; i < segments; i++) {
      const current = layer * segments + i + 1;
      const next = layer * segments + ((i + 1) % segments) + 1;
      const aboveCurrent = (layer + 1) * segments + i + 1;
      const aboveNext = (layer + 1) * segments + ((i + 1) % segments) + 1;
      faces.push([current, next, aboveNext]);
      faces.push([current, aboveNext, aboveCurrent]);
    }
  }

  for (let i = 0; i < segments; i++) {
    const current = i + 1;
    const next = ((i + 1) % segments) + 1;
    faces.push([bottomCenterIndex, next, current]);
  }

  const topLayerStart = layers * segments + 1;
  for (let i = 0; i < segments; i++) {
    const current = topLayerStart + i;
    const next = topLayerStart + ((i + 1) % segments);
    faces.push([topCenterIndex, current, next]);
  }
}

function exportSTL() {
  let stl = "solid chiralShape\n";

  for (let face of faces) {
    const v1 = vertices[face[0]];
    const v2 = vertices[face[1]];
    const v3 = vertices[face[2]];

    const normal = calculateNormal(v1, v2, v3);

    stl += ` facet normal ${normal.x} ${normal.y} ${normal.z}\n`;
    stl += "  outer loop\n";
    stl += `   vertex ${v1.x} ${v1.y} ${v1.z}\n`;
    stl += `   vertex ${v2.x} ${v2.y} ${v2.z}\n`;
    stl += `   vertex ${v3.x} ${v3.y} ${v3.z}\n`;
    stl += "  endloop\n";
    stl += " endfacet\n";
  }

  stl += "endsolid chiralShape";

  const blob = new Blob([stl], { type: "text/plain" });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = "chiralShape.stl";
  link.click();
  URL.revokeObjectURL(url);
}

function calculateNormal(v1, v2, v3) {
  let ax = v2.x - v1.x;
  let ay = v2.y - v1.y;
  let az = v2.z - v1.z;

  let bx = v3.x - v1.x;
  let by = v3.y - v1.y;
  let bz = v3.z - v1.z;

  let nx = ay * bz - az * by;
  let ny = az * bx - ax * bz;
  let nz = ax * by - ay * bx;

  let length = Math.sqrt(nx * nx + ny * ny + nz * nz);
  return {
    x: nx / length,
    y: ny / length,
    z: nz / length
  };
}