Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,19 @@ <h2 data-i18n="sections.displacement">Displacement</h2>
<section class="panel-section">
<h2 data-i18n="sections.transform">Transform</h2>

<div class="form-row">
<label class="checkbox-label" for="fixed-world-texture"
data-i18n-title="tooltips.fixedWorldTexture"
title="Normalize UVs with a fixed length in millimetres instead of the part’s largest size, so the same settings yield similar physical texture size on different meshes.">
<input type="checkbox" id="fixed-world-texture" />
<span data-i18n="labels.fixedWorldTexture">Fixed texture scale (world mm)</span>
</label>
</div>
<div class="form-row" id="reference-extent-row" style="display:none">
<label for="reference-extent-mm" data-i18n="labels.referenceExtentMm" data-i18n-title="tooltips.referenceExtentMm" title="Millimetres used to normalize UV coordinates (before Scale U/V). Same value across parts for consistent pattern size.">Reference extent (mm)</label>
<input type="number" class="val" id="reference-extent-mm" value="200" min="0.1" max="10000" step="0.1" data-wheel-decimals="1" />
</div>

<div class="form-row slider-row">
<label for="scale-u" data-i18n="labels.scaleU">Scale U</label>
<input type="range" id="scale-u" min="0" max="1000" step="1" value="435" />
Expand Down
4 changes: 2 additions & 2 deletions js/displacement.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { computeUV, getDominantCubicAxis, getCubicBlendWeights } from './mapping.js';
import { computeUV, getDominantCubicAxis, getCubicBlendWeights, getReferenceExtent } from './mapping.js';

/**
* Apply displacement to every vertex of a non-indexed BufferGeometry.
Expand Down Expand Up @@ -320,7 +320,7 @@ export function applyDisplacement(geometry, imageData, imgWidth, imgHeight, sett
const zaX = zoneAreaX[vid], zaY = zoneAreaY[vid], zaZ = zoneAreaZ[vid];
const total = zaX + zaY + zaZ;
if (total > 0) {
const md = Math.max(bounds.size.x, bounds.size.y, bounds.size.z, 1e-6);
const md = getReferenceExtent(settings, bounds);
const rotRad = (settings.rotation ?? 0) * Math.PI / 180;
let grey = 0;
if (zaX > 0) { // X-dominant zone → YZ projection
Expand Down
4 changes: 4 additions & 0 deletions js/i18n/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export default {
"projection.planarXZ": "Planar XZ",
"projection.planarYZ": "Planar YZ",
"sections.transform": "Transformation",
"labels.fixedWorldTexture": "Feste Texturskalierung (mm)",
"tooltips.fixedWorldTexture": "Wenn aktiv, wird der UV-Abstand mit einer festen Länge in Millimetern statt der größten Bemaßung des Teils normiert — dieselben Einstellungen wirken auf verschiedenen Meshes ähnlich groß. Zylinder- und Kugelmodus nutzen weiterhin mesh-basierte Radien.",
"labels.referenceExtentMm": "Referenzmaß (mm)",
"tooltips.referenceExtentMm": "Millimetre zur Normierung der UV-Koordinaten pro Achse (vor Skalierung U/V). Gleicher Wert auf mehreren Teilen für einheitliche Mustergröße.",
"labels.scaleU": "Skalierung U",
"labels.scaleV": "Skalierung V",
"labels.offsetU": "Versatz U",
Expand Down
4 changes: 4 additions & 0 deletions js/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export default {
"projection.planarXZ": "Planar XZ",
"projection.planarYZ": "Planar YZ",
"sections.transform": "Transform",
"labels.fixedWorldTexture": "Fixed texture scale (world mm)",
"tooltips.fixedWorldTexture": "When enabled, UV spacing uses a fixed length in millimetres instead of the part’s largest dimension, so the same settings look similar in physical size on different meshes. Cylindrical and spherical modes still use mesh-based radii.",
"labels.referenceExtentMm": "Reference extent (mm)",
"tooltips.referenceExtentMm": "Millimetres used to normalize UV coordinates along each axis (before Scale U/V). Use the same value across parts for consistent pattern size.",
"labels.scaleU": "Scale U",
"labels.scaleV": "Scale V",
"labels.offsetU": "Offset U",
Expand Down
39 changes: 39 additions & 0 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const settings = {
boundaryFalloff: 0,
symmetricDisplacement: false,
useDisplacement: false,
/** When true, UV normalization uses referenceExtentMm instead of each mesh's largest bbox edge. */
fixedWorldTextureScale: false,
/** Millimetres: one normalized UV span along an axis (before Scale U/V). */
referenceExtentMm: 200,
};

// ── Canvas filter support (Safari / iOS WebView don't support ctx.filter) ────
Expand Down Expand Up @@ -234,6 +238,15 @@ const boundaryFalloffSlider = document.getElementById('boundary-falloff');
const boundaryFalloffVal = document.getElementById('boundary-falloff-val');
const symmetricDispToggle = document.getElementById('symmetric-displacement');
const dispPreviewToggle = document.getElementById('displacement-preview');
const fixedWorldTextureToggle = document.getElementById('fixed-world-texture');
const referenceExtentRow = document.getElementById('reference-extent-row');
const referenceExtentMmVal = document.getElementById('reference-extent-mm');

function refreshReferenceExtentUi() {
if (referenceExtentRow) {
referenceExtentRow.style.display = settings.fixedWorldTextureScale ? '' : 'none';
}
}

// ── Exclusion panel DOM refs ──────────────────────────────────────────────────
const exclBrushBtn = document.getElementById('excl-brush-btn');
Expand Down Expand Up @@ -374,6 +387,7 @@ document.getElementById('theme-toggle').addEventListener('click', () => {
});

wireEvents();
refreshReferenceExtentUi();
// Sync scale number inputs with the slider's initial position
scaleUVal.value = posToScale(parseFloat(scaleUSlider.value));
scaleVVal.value = posToScale(parseFloat(scaleVSlider.value));
Expand Down Expand Up @@ -580,6 +594,31 @@ function wireEvents() {
}
});

if (fixedWorldTextureToggle) {
fixedWorldTextureToggle.addEventListener('change', () => {
settings.fixedWorldTextureScale = fixedWorldTextureToggle.checked;
refreshReferenceExtentUi();
clearTimeout(previewDebounce); previewDebounce = setTimeout(updatePreview, 80);
});
}

function applyReferenceExtentFromInput() {
if (!referenceExtentMmVal) return;
let v = parseFloat(referenceExtentMmVal.value);
if (!Number.isFinite(v)) v = settings.referenceExtentMm;
v = Math.max(0.1, Math.min(10000, v));
settings.referenceExtentMm = v;
referenceExtentMmVal.value = v;
clearTimeout(previewDebounce); previewDebounce = setTimeout(updatePreview, 80);
}
if (referenceExtentMmVal) {
referenceExtentMmVal.addEventListener('change', applyReferenceExtentFromInput);
addFineWheelSupport(referenceExtentMmVal, (v) => {
referenceExtentMmVal.value = v;
applyReferenceExtentFromInput();
});
}

linkSlider(offsetUSlider, offsetUVal, v => { settings.offsetU = v; return v.toFixed(2); });
linkSlider(offsetVSlider, offsetVVal, v => { settings.offsetV = v; return v.toFixed(2); });
linkSlider(rotationSlider, rotationVal, v => { settings.rotation = v; return Math.round(v); });
Expand Down
18 changes: 16 additions & 2 deletions js/mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ export const MODE_TRIPLANAR = 5;
export const MODE_CUBIC = 6;

const TWO_PI = Math.PI * 2;

/**
* Millimetres used to normalize planar / triplanar / cubic UV coordinates.
* Default (fixed off): each mesh's largest bounding-box edge — pattern scales with part size.
* Fixed world scale: user `referenceExtentMm` — same profile yields similar physical pattern size on any mesh.
*/
export function getReferenceExtent(settings, bounds) {
const maxDim = Math.max(bounds.size.x, bounds.size.y, bounds.size.z);
const md = Math.max(maxDim, 1e-6);
if (settings.fixedWorldTextureScale) {
const ref = Number(settings.referenceExtentMm);
if (Number.isFinite(ref) && ref > 0) return ref;
}
return md;
}
const CUBIC_AXIS_EPSILON = 1e-4;

export function getDominantCubicAxis(normal) {
Expand Down Expand Up @@ -114,8 +129,7 @@ export function computeUV(pos, normal, mode, settings, bounds) {
const rotRad = (settings.rotation ?? 0) * Math.PI / 180;
const cosR = Math.cos(rotRad);
const sinR = Math.sin(rotRad);
const maxDim = Math.max(size.x, size.y, size.z);
const md = Math.max(maxDim, 1e-6);
const md = getReferenceExtent(settings, bounds);

let u = 0, v = 0;

Expand Down
9 changes: 9 additions & 0 deletions js/previewMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const sharedGLSL = /* glsl */`
uniform int symmetricDisplacement;
uniform int useDisplacement;
uniform vec2 textureAspect;
uniform int useFixedReference;
uniform float referenceExtentMm;

const float PI = 3.14159265358979;
const float TWO_PI = 6.28318530717959;
Expand Down Expand Up @@ -99,6 +101,9 @@ const sharedGLSL = /* glsl */`
vec3 rel = pos - boundsCenter;
float maxDim = max(boundsSize.x, max(boundsSize.y, boundsSize.z));
float md = max(maxDim, 1e-4);
if (useFixedReference > 0) {
md = max(referenceExtentMm, 1e-4);
}

if (mappingMode == 0) {
return sampleMap(vec2((pos.x - boundsMin.x) / md, (pos.y - boundsMin.y) / md));
Expand Down Expand Up @@ -435,6 +440,8 @@ export function updateMaterial(material, displacementTexture, settings) {
u.useDisplacement.value = settings.useDisplacement ? 1 : 0;
u.textureAspect.value.set(settings.textureAspectU ?? 1, settings.textureAspectV ?? 1);
u.boundaryFalloffDist.value = settings.boundaryFalloff ?? 0.0;
u.useFixedReference.value = settings.fixedWorldTextureScale ? 1 : 0;
u.referenceExtentMm.value = Math.max(Number(settings.referenceExtentMm) || 200, 1e-4);
}

// ── Internal ──────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -467,6 +474,8 @@ function buildUniforms(tex, settings) {
boundaryEdgeCount: { value: 0 },
boundaryEdgeTexWidth: { value: 1.0 },
boundaryFalloffDist: { value: settings.boundaryFalloff ?? 0.0 },
useFixedReference: { value: settings.fixedWorldTextureScale ? 1 : 0 },
referenceExtentMm: { value: Math.max(Number(settings.referenceExtentMm) || 200, 1e-4) },
};
}

Expand Down