marchingcubestest/src/index.js

212 lines
5.7 KiB
JavaScript

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { makeNoise3D } from 'open-simplex-noise';
import { makeCuboid } from 'fractal-noise';
import { cornerIndexAFromEdge, cornerIndexBFromEdge, triangulationTable } from './table';
import { mkslider, mkcheckbox, mkspacer } from './interface';
const noise3D = makeNoise3D(55);
let DIM = 24;
let xMax = DIM + 1;
let yMax = DIM + 1;
let isoLevel = 7;
let grid = [];
let mesh;
let bbox;
function to1D( x, y, z ) {
return (z * xMax * yMax) + (y * xMax) + x;
}
function to3D( idx ) {
const z = idx / (xMax * yMax);
idx -= (z * xMax * yMax);
const y = idx / xMax;
const x = idx % xMax;
return [ x, y, z ];
}
let frequency = 0.15;
let octaves = 8;
let persistence = 0.2;
let amplitude = 1;
let depthScale = 16;
function generateGrid() {
grid.length = 0;
const cube = makeCuboid(DIM + 1, DIM + 1, DIM + 1, noise3D, { frequency, octaves, persistence, amplitude })
for (let x = 0; x < DIM + 1; x++) {
for (let y = 0; y < DIM + 1; y++) {
for (let z = 0; z < DIM + 1; z++) {
grid[to1D(x, y, z)] = (cube[x][y][z] + 0.5) * depthScale;
}
}
}
// const center = new THREE.Vector3(DIM / 2, DIM / 2, DIM / 2);
// for (let x = 0; x < DIM; x++) {
// for (let y = 0; y < DIM; y++) {
// for (let z = 0; z < DIM; z++) {
// const dist = new THREE.Vector3(x, y, z).distanceTo(center);
// grid[to1D(x, y, z)] = dist;
// }
// }
// }
// console.log(Math.min(...grid));
// console.log(Math.max(...grid));
}
function dim4(x, y, z) {
return new THREE.Vector4(x, y, z, grid[to1D(x, y, z)]);
}
function interpolateVerts(v1, v2) {
const t = (isoLevel - v1.w) / (v2.w - v1.w);
const v1xyz = new THREE.Vector3(v1.x, v1.y, v1.z);
const v2xyz = new THREE.Vector3(v2.x, v2.y, v2.z);
const subtract = v2xyz.clone().sub(v1xyz);
return v1xyz.add(subtract.multiplyScalar(t))
}
function marchCube() {
const geometry = new THREE.BufferGeometry();
const positions = [];
for (let x = 0; x < DIM; x += 1) {
for (let y = 0; y < DIM; y += 1) {
for (let z = 0; z < DIM; z += 1) {
let cube = [
dim4(x, y, z), dim4(x + 1, y, z), dim4(x + 1, y, z + 1), dim4(x, y, z + 1),
dim4(x, y + 1, z), dim4(x + 1, y + 1, z), dim4(x + 1, y + 1, z + 1), dim4(x, y + 1, z + 1),
];
let cubeIndex = 0;
for (let i = 0; i < 8; i++) {
if (cube[i].w < isoLevel) {
cubeIndex |= (1 << i);
}
}
for (let i = 0; triangulationTable[cubeIndex][i] != -1; i += 3) {
for (let tri = 0; tri < 3; tri++) {
const a0 = cornerIndexAFromEdge[triangulationTable[cubeIndex][i + tri]];
const b0 = cornerIndexBFromEdge[triangulationTable[cubeIndex][i + tri]];
const tri0 = interpolateVerts(cube[a0], cube[b0]);
positions.push(tri0.x, tri0.y, tri0.z);
}
}
}
}
}
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
geometry.computeVertexNormals();
return geometry;
}
function update(setGrid) {
if (setGrid) {
generateGrid();
}
mesh.geometry.dispose();
const geom = marchCube();
mesh.geometry = geom;
}
function guitools() {
const gui = document.createElement('div');
gui.classList.add('overlay');
document.body.appendChild(gui);
const dimensions = mkslider('Dimensions', 8, 64, 8, DIM, (newval) => {
DIM = newval;
xMax = newval + 1;
yMax = newval + 1;
const vhdim = new THREE.Vector3(DIM, DIM, DIM);
bbox.max = vhdim;
update(true);
});
gui.appendChild(dimensions);
const isoset = mkslider('Iso level', 1, depthScale, 1, isoLevel, (newval) => {
isoLevel = newval;
update(false);
});
gui.appendChild(isoset);
gui.appendChild(mkspacer());
const freqset = mkslider('Frequency', 0.01, 1, 0.01, frequency, (newval) => {
frequency = newval;
update(true);
});
gui.appendChild(freqset);
const ampset = mkslider('Amplitude', 0.01, 1, 0.01, amplitude, (newval) => {
amplitude = newval;
update(true);
});
gui.appendChild(ampset);
const perset = mkslider('Persistence', 0.01, 1, 0.01, persistence, (newval) => {
persistence = newval;
update(true);
});
gui.appendChild(perset);
const octset = mkslider('Octaves', 1, 16, 1, octaves, (newval) => {
octaves = newval;
update(true);
});
gui.appendChild(octset);
gui.appendChild(mkspacer());
const wireframe = mkcheckbox('Wireframe', false, (newval) => {
mesh.material.wireframe = newval;
});
gui.appendChild(wireframe);
}
function init() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor(0x00aaff);
document.body.appendChild( renderer.domElement );
const controls = new OrbitControls(camera, renderer.domElement);
camera.position.set( 48, 48, 48 );
controls.update();
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
generateGrid();
const geom = marchCube();
mesh = new THREE.Mesh(geom, new THREE.MeshNormalMaterial());
scene.add(mesh);
const light = new THREE.DirectionalLight(0xffffff);
light.position.set(10, 10, 0);
scene.add(light);
const ambient = new THREE.AmbientLight(0x424141);
scene.add(ambient);
const vhdim = new THREE.Vector3(DIM, DIM, DIM);
bbox = new THREE.Box3(new THREE.Vector3(0, 0, 0), vhdim);
const helper = new THREE.Box3Helper(bbox, 0xfcdf00);
scene.add(helper);
guitools();
animate();
}
init();