Skip to content

Instantly share code, notes, and snippets.

@ji-podhead
Created October 7, 2025 13:38
Show Gist options
  • Select an option

  • Save ji-podhead/4950c9a6111e4b7c6dde7a73cdd5741e to your computer and use it in GitHub Desktop.

Select an option

Save ji-podhead/4950c9a6111e4b7c6dde7a73cdd5741e to your computer and use it in GitHub Desktop.
Voxelize a mesh with threejs and sample the texture
import * as THREE from 'three';
import { MeshSurfaceSampler } from 'three/examples/jsm/math/MeshSurfaceSampler.js';
/**
* Extrahiert Textureddaten in ein format, das einfach zu sampeln ist.
* @param {THREE.Texture} texture - Die zu verarbeitende Texture.
* @returns {ImageData} Die Bilddaten der Texture.
*/
function getTextureImageData(texture) {
const image = texture.image;
if (!image || !image.width || !image.height) {
console.error("Texture image is not loaded or invalid.");
return null;
}
// Optimierung: Canvas nur erstellen, wenn nötig
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext('2d', { willReadFrequently: true });
context.drawImage(image, 0, 0, image.width, image.height);
// Optimierung: Direkter Zugriff auf ImageData
return context.getImageData(0, 0, image.width, image.height);
}
/**
* Voxelisiert ein Mesh durch Oberflächensampling und extrahiert Farbdaten aus der Texture.
* Diese Version ist stark optimiert und verwendet MeshSurfaceSampler.
* @param {THREE.Mesh} mesh - Das zu voxelisierende Mesh.
* @param {number} particleCount - Die Anzahl der zu sampelnden Partikel.
* @param {number} [distanceThreshold=5] - Minimale Entfernung vom Modellzentrum für Partikel.
* @returns {Promise<{positions: Float32Array, colors: Float32Array}>}
* Ein Promise, das mit Positionen und Farben für die Partikel auflöst.
*/
export async function textureVoxelizer(mesh, particleCount, distanceThreshold = 5000) {
// Optimierung: Frühe Validierung
if (!mesh.geometry || !mesh.material) {
console.error("Mesh muss Geometrie und Material haben.");
return null;
}
const material = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
const texture = material.map;
// Optimierung: Sofortige Fehlerbehandlung
if (!texture || !texture.isTexture) {
console.error("Das Material muss eine Texture haben.");
return { positions: new Float32Array(), colors: new Float32Array() };
}
// Optimierung: Direkte Texturextraktion
if (!texture.image) {
console.error("Texture-Bild nicht verfügbar.");
return { positions: new Float32Array(), colors: new Float32Array() };
}
const imageData = getTextureImageData(texture);
if (!imageData) {
console.error("Fehler beim Abrufen der Textureddaten.");
return { positions: new Float32Array(), colors: new Float32Array() };
}
// Optimierung: Vorinitialisierte Arrays
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
// Optimierung: Effizienter Sampler
const sampler = new MeshSurfaceSampler(mesh).build();
const _position = new THREE.Vector3();
const _normal = new THREE.Vector3();
const raycaster = new THREE.Raycaster();
// Optimierung: Parallelisierte Partikelgenerierung
const sampleCount = Math.ceil(particleCount / 4); // 4 Partikel pro Iteration
for (let i = 0; i < sampleCount; i++) {
// Optimierung: Mehrere Partikel pro Iteration
for (let j = 0; j < 4 && i * 4 + j < particleCount; j++) {
sampler.sample(_position, _normal);
// Optimierung: Präzise UV-Koordinaten
raycaster.set(_position.clone().addScaledVector(_normal, 0.001), _normal.clone().negate());
const intersects = raycaster.intersectObject(mesh, false);
if (intersects.length > 0 && intersects[0].uv) {
const uv = intersects[0].uv;
const index = (i * 4 + j) * 3;
// Position setzen
positions[index] = _position.x;
positions[index + 1] = _position.y;
positions[index + 2] = _position.z;
// Farbe extrahieren und optimieren
const tx = Math.floor(uv.x * (imageData.width - 1));
const ty = Math.floor((1 - uv.y) * (imageData.height - 1));
const colorIndex = (ty * imageData.width + tx) * 4;
// Sample RGB from texture
const sampledR = imageData.data[colorIndex] / 255;
const sampledG = imageData.data[colorIndex + 1] / 255;
const sampledB = imageData.data[colorIndex + 2] / 255;
// Create a THREE.Color object from sampled RGB
const sampledColor = new THREE.Color(sampledR, sampledG, sampledB);
// Get HSL values
const hsl = sampledColor.getHSL(new THREE.Color()); // getHSL returns the color object itself, so we can chain it or use a temp var
// Apply user's HSL modification
const modifiedH = hsl.h;
const modifiedS = hsl.s * 0.8;
const modifiedL = hsl.l * 0.8 + 0.2;
// Set the color with modified HSL
sampledColor.setHSL(modifiedH, modifiedS, modifiedL);
// Get the new RGB values and scale them by 100 as in the original code
const finalR = sampledColor.r * 250;
const finalG = sampledColor.g * 250;
const finalB = sampledColor.b * 250;
// Assign color values to the Float32Array using the correct index
colors[index] = finalR;
colors[index + 1] = finalG;
colors[index + 2] = finalB;
}
}
}
return {
positions: positions,
colors: colors
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment