Skip to content

Instantly share code, notes, and snippets.

@erichlof
Created May 14, 2025 18:38
Show Gist options
  • Save erichlof/c5432180f73585e5f6121cebcf0a7784 to your computer and use it in GitHub Desktop.
Save erichlof/c5432180f73585e5f6121cebcf0a7784 to your computer and use it in GitHub Desktop.
Making more interesting and complex models using CSG techniques
// scene/demo-specific variables go here
let boxGeometry, boxMaterial;
let boxMeshes = [];
let boxGeometries = [];
let totalNumberOfShapes = 0;
let shape = new THREE.Object3D();
let invMatrix = new THREE.Matrix4();
let el; // elements of the invMatrix
let shapeBoundingBox_minCorner = new THREE.Vector3();
let shapeBoundingBox_maxCorner = new THREE.Vector3();
let shapeBoundingBox_centroid = new THREE.Vector3();
let shape_array;
let shapeDataTexture;
let aabb_array;
let aabbDataTexture;
let totalWorklist;
let outline_IntensityObject, outline_IntensityController;
let needChangeOutlineIntensity = false;
let outline_ColorObject, outline_ColorController;
let needChangeOutlineColor = false;
let outlineColor;
function Begin_CSG_Section()
{
}
function End_CSG_Section()
{
}
function Add_CSG_Shape(shapeA)
{
}
function Operation_Union(shapeB)
{
}
function Operation_Difference(shapeB)
{
}
function Operation_Overlap(shapeB)
{
}
// called automatically from within initTHREEjs() function (located in InitCommon.js file)
function initSceneData()
{
demoFragmentShaderFileName = 'CSG_Model_Rendering_Fragment.glsl';
// scene/demo-specific three.js objects setup goes here
sceneIsDynamic = false;
cameraFlightSpeed = 100;
// pixelRatio is resolution - range: 0.5(half resolution) to 1.0(full resolution)
pixelRatio = mouseControl ? 1.0 : 0.75;
EPS_intersect = 0.01;
// set camera's field of view
worldCamera.fov = 50;
focusDistance = 120.0;
apertureChangeSpeed = 5;
// position and orient camera
cameraControlsObject.position.set(0, -20, 120);
///cameraControlsYawObject.rotation.y = 0.0;
// look slightly upward
cameraControlsPitchObject.rotation.x = 0.005;
boxMaterial = new THREE.MeshBasicMaterial();
totalNumberOfShapes = 50; // 50
console.log("Shape count: " + totalNumberOfShapes);
totalWorklist = new Uint32Array(totalNumberOfShapes);
shape_array = new Float32Array(2048 * 2048 * 4);
// 2048 = width of texture, 2048 = height of texture, 4 = r,g,b, and a components
aabb_array = new Float32Array(2048 * 2048 * 4);
// 2048 = width of texture, 2048 = height of texture, 4 = r,g,b, and a components
let ix32 = 0;
let ix9 = 0;
for (let i = 0; i < totalNumberOfShapes; i++)
{
ix32 = i * 32;
ix9 = i * 9;
shape.position.set((Math.random() * 2 - 1) * 50, (Math.random() * 2 - 1) * 50, (Math.random() * 2 - 1) * 50);
shape.rotation.set((Math.random() * 2 - 1) * Math.PI, (Math.random() * 2 - 1) * Math.PI, (Math.random() * 2 - 1) * Math.PI);
shape.scale.set((Math.random() * 8) + 1, (Math.random() * 8) + 1, (Math.random() * 8) + 1);
shape.updateMatrixWorld(true); // 'true' forces immediate matrix update
invMatrix.copy(shape.matrixWorld).invert();
el = invMatrix.elements;
//slot 0 Shape transform Matrix 4x4 (16 elements total)
shape_array[ix32 + 0] = el[0]; // r or x // shape transform Matrix element[0]
shape_array[ix32 + 1] = el[1]; // g or y // shape transform Matrix element[1]
shape_array[ix32 + 2] = el[2]; // b or z // shape transform Matrix element[2]
shape_array[ix32 + 3] = el[3]; // a or w // shape transform Matrix element[3]
//slot 1
shape_array[ix32 + 4] = el[4]; // r or x // shape transform Matrix element[4]
shape_array[ix32 + 5] = el[5]; // g or y // shape transform Matrix element[5]
shape_array[ix32 + 6] = el[6]; // b or z // shape transform Matrix element[6]
shape_array[ix32 + 7] = el[7]; // a or w // shape transform Matrix element[7]
//slot 2
shape_array[ix32 + 8] = el[8]; // r or x // shape transform Matrix element[8]
shape_array[ix32 + 9] = el[9]; // g or y // shape transform Matrix element[9]
shape_array[ix32 + 10] = el[10]; // b or z // shape transform Matrix element[10]
shape_array[ix32 + 11] = el[11]; // a or w // shape transform Matrix element[11]
//slot 3
shape_array[ix32 + 12] = el[12]; // r or x // shape transform Matrix element[12]
shape_array[ix32 + 13] = el[13]; // g or y // shape transform Matrix element[13]
shape_array[ix32 + 14] = el[14]; // b or z // shape transform Matrix element[14]
shape_array[ix32 + 15] = el[15]; // a or w // shape transform Matrix element[15]
//slot 4
shape_array[ix32 + 16] = Math.floor(Math.random() * 5); // r or x // shape type id# (0: box, 1: sphere, 2: cylinder, 3: cone, 4: paraboloid, etc)
shape_array[ix32 + 17] = 4; // g or y // material type id# (0: LIGHT, 1: DIFF, 2: REFR, 3: SPEC, 4: COAT, etc)
shape_array[ix32 + 18] = 0.0; // b or z // material Metalness - default: 0.0(no metal, a dielectric), 1.0 would be purely metal
shape_array[ix32 + 19] = 0.0; // a or w // material Roughness - default: 0.0(no roughness, totally smooth)
//slot 5
shape_array[ix32 + 20] = Math.random(); // r or x // material albedo color R (if LIGHT, this is also its emissive color R)
shape_array[ix32 + 21] = Math.random(); // g or y // material albedo color G (if LIGHT, this is also its emissive color G)
shape_array[ix32 + 22] = Math.random(); // b or z // material albedo color B (if LIGHT, this is also its emissive color B)
shape_array[ix32 + 23] = 1.0; // a or w // material Opacity (Alpha) - default: 1.0 (fully opaque), 0.0 is fully transparent
//slot 6
shape_array[ix32 + 24] = 1.0; // r or x // material Index of Refraction(IoR) - default: 1.0(air) (or 1.5(glass), 1.33(water), etc)
shape_array[ix32 + 25] = 0.0; // g or y // material ClearCoat Roughness - default: 0.0 (no roughness, totally smooth)
shape_array[ix32 + 26] = 1.5; // b or z // material ClearCoat IoR - default: 1.5(thick ClearCoat)
shape_array[ix32 + 27] = 0; // a or w // material data
//slot 7
shape_array[ix32 + 28] = 0; // r or x // material data
shape_array[ix32 + 29] = 0; // g or y // material data
shape_array[ix32 + 30] = 0; // b or z // material data
shape_array[ix32 + 31] = 0; // a or w // material data
// if this shape is a Box, use THREE.BoxGeometry as starting point for this shape's AABB
if (shape_array[ix32 + 16] == 0)
boxGeometries[i] = new THREE.BoxGeometry( 2, 2, 2 );
else // else use THREE.SphereGeometry, as it produces a tighter-fitting AABB when shape is rotated
boxGeometries[i] = new THREE.SphereGeometry(1.4);
boxMeshes[i] = new THREE.Mesh( boxGeometries[i], boxMaterial );
boxMeshes[i].geometry.applyMatrix4(shape.matrixWorld);
boxMeshes[i].geometry.computeBoundingBox();
shapeBoundingBox_minCorner.copy(boxMeshes[i].geometry.boundingBox.min);
shapeBoundingBox_maxCorner.copy(boxMeshes[i].geometry.boundingBox.max);
boxMeshes[i].geometry.boundingBox.getCenter(shapeBoundingBox_centroid);
aabb_array[ix9 + 0] = shapeBoundingBox_minCorner.x;
aabb_array[ix9 + 1] = shapeBoundingBox_minCorner.y;
aabb_array[ix9 + 2] = shapeBoundingBox_minCorner.z;
aabb_array[ix9 + 3] = shapeBoundingBox_maxCorner.x;
aabb_array[ix9 + 4] = shapeBoundingBox_maxCorner.y;
aabb_array[ix9 + 5] = shapeBoundingBox_maxCorner.z;
aabb_array[ix9 + 6] = shapeBoundingBox_centroid.x;
aabb_array[ix9 + 7] = shapeBoundingBox_centroid.y;
aabb_array[ix9 + 8] = shapeBoundingBox_centroid.z;
totalWorklist[i] = i;
} // end for (let i = 0; i < totalNumberOfShapes; i++)
for (let i = 0; i < totalNumberOfShapes * 2; i++)
buildnodes[i] = new BVH_Node();
console.log("BvhGeneration...");
console.time("BvhGeneration");
BVH_QuickBuild(totalWorklist, aabb_array);
console.timeEnd("BvhGeneration");
shapeDataTexture = new THREE.DataTexture(shape_array,
2048,
2048,
THREE.RGBAFormat,
THREE.FloatType,
THREE.Texture.DEFAULT_MAPPING,
THREE.ClampToEdgeWrapping,
THREE.ClampToEdgeWrapping,
THREE.NearestFilter,
THREE.NearestFilter,
1,
THREE.NoColorSpace);
shapeDataTexture.flipY = false;
shapeDataTexture.generateMipmaps = false;
shapeDataTexture.needsUpdate = true;
aabbDataTexture = new THREE.DataTexture(aabb_array,
2048,
2048,
THREE.RGBAFormat,
THREE.FloatType,
THREE.Texture.DEFAULT_MAPPING,
THREE.ClampToEdgeWrapping,
THREE.ClampToEdgeWrapping,
THREE.NearestFilter,
THREE.NearestFilter,
1,
THREE.NoColorSpace);
aabbDataTexture.flipY = false;
aabbDataTexture.generateMipmaps = false;
aabbDataTexture.needsUpdate = true;
// In addition to the default GUI on all demos, add any special GUI elements that this particular demo requires
outline_IntensityObject = { Outline_Intensity: 2.0 };
outline_ColorObject = { Outline_Color: [1, 1, 1] };
function handleOutlineColorChange() { needChangeOutlineColor = true; }
function handleOutlineIntensityChange() { needChangeOutlineIntensity = true; }
outline_IntensityController = gui.add(outline_IntensityObject, 'Outline_Intensity', 0, 20, 0.5).onChange(handleOutlineIntensityChange);
outline_ColorController = gui.addColor(outline_ColorObject, 'Outline_Color').onChange(handleOutlineColorChange);
handleOutlineColorChange();
handleOutlineIntensityChange();
// scene/demo-specific uniforms go here
pathTracingUniforms.tShape_DataTexture = { value: shapeDataTexture };
pathTracingUniforms.tAABB_DataTexture = { value: aabbDataTexture };
pathTracingUniforms.uOutlineIntensity = { value: 2.0 };
pathTracingUniforms.uOutlineColor = { value: new THREE.Color() };
} // end function initSceneData()
// called automatically from within the animate() function (located in InitCommon.js file)
function updateVariablesAndUniforms()
{
if (needChangeOutlineIntensity)
{
pathTracingUniforms.uOutlineIntensity.value = outline_IntensityController.getValue();
cameraIsMoving = true;
needChangeOutlineIntensity = false;
}
if (needChangeOutlineColor)
{
outlineColor = outline_ColorController.getValue();
pathTracingUniforms.uOutlineColor.value.setRGB(outlineColor[0], outlineColor[1], outlineColor[2]);
cameraIsMoving = true;
needChangeOutlineColor = false;
}
// INFO
cameraInfoElement.innerHTML = "FOV: " + worldCamera.fov + " / Aperture: " + apertureSize.toFixed(2) + " / FocusDistance: " + focusDistance + "<br>" + "Samples: " + sampleCounter;
} // end function updateVariablesAndUniforms()
init(); // init app and start animating
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment