v0.4.15: gevelbekleding per woning (rabat/steen, override per wand), stenen trasraam (110 mm, 181 texturen), riet per unit, vloerafwerking, verfkleur binnen, boeideelkleur, gevel tot onder dakrand, binnenhoek-fixes; testontwerp als standaard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7d91416004
commit
12c2579471
+82
-18
@@ -49,9 +49,57 @@ window.RoofModel = (() => {
|
||||
fasciaHeight: 300, // mm
|
||||
spotMin: 800, // mm h.o.h. boeideel-spots
|
||||
spotMax: 1200,
|
||||
trimHeight: 80 // mm
|
||||
trimHeight: 80, // mm
|
||||
fasciaColor: 'antraciet' // FASCIA_COLORS
|
||||
};
|
||||
|
||||
// Kleur van het boeideel (egaal).
|
||||
const FASCIA_COLORS = {
|
||||
wit: { label: 'Wit', hex: 0xf2f2f0 },
|
||||
lichtgrijs: { label: 'Lichtgrijs', hex: 0xbfc3c6 },
|
||||
middelgrijs: { label: 'Middelgrijs', hex: 0x7d8286 },
|
||||
antraciet: { label: 'Antraciet', hex: 0x3a3f44 },
|
||||
zwart: { label: 'Zwart', hex: 0x151617 }
|
||||
};
|
||||
|
||||
// Dakbedekking per unit (roof.covering). Riet e.d. alleen bij een hellend
|
||||
// dak (lessenaar/zadel); plat valt terug op dakleer. thickMM telt mee in de
|
||||
// dakdikte (vThick/topAt), dus boeideel/daktrim en een opbouw ernaast
|
||||
// schuiven vanzelf mee omhoog. Texturen: assets/textures/dak/, naadloos;
|
||||
// de stengels lopen langs de helling.
|
||||
// Riet: één keuze per textuur assets/textures/dak/riet_<nr>.jpg (nummer
|
||||
// hier toevoegen); zelfde opbouw (180 mm, 3,8 kg/m2).
|
||||
const RIET = { thickMM: 180, kgM2: 3.8, tileMM: 1000, nrs: ['001', '002', '003'] };
|
||||
const COVERINGS = Object.assign({
|
||||
dakleer: { label: 'Dakleer (EPDM)', thickMM: DAKLEER_MM, kgM2: DENSITY_DAKLEER_KGM2 }
|
||||
}, ...RIET.nrs.map(nr => ({
|
||||
['riet_' + nr]: { label: 'Riet ' + nr, thickMM: RIET.thickMM, kgM2: RIET.kgM2, file: `assets/textures/dak/riet_${nr}.jpg`, tileMM: RIET.tileMM, sloped: true }
|
||||
})));
|
||||
// Werkelijke bedekking voor een dakvorm (riet niet op een plat dak).
|
||||
function coveringFor(key, shapeType) {
|
||||
const c = COVERINGS[key];
|
||||
return c && !(c.sloped && shapeType === 'plat') ? key : 'dakleer';
|
||||
}
|
||||
const coveringMaterials = {};
|
||||
function coveringMaterial(key) {
|
||||
const c = COVERINGS[key];
|
||||
if (!c || !c.file) return null;
|
||||
if (coveringMaterials[key]) return coveringMaterials[key];
|
||||
const mat = new THREE.MeshStandardMaterial({ color: 0x9c8466, roughness: 0.95 });
|
||||
if (typeof document !== 'undefined' && THREE.TextureLoader) {
|
||||
new THREE.TextureLoader().load(c.file, (tex) => {
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
||||
tex.repeat.set(1000 / c.tileMM, 1000 / c.tileMM);
|
||||
tex.anisotropy = 8;
|
||||
mat.map = tex;
|
||||
mat.color.set(0xffffff);
|
||||
mat.needsUpdate = true;
|
||||
});
|
||||
}
|
||||
coveringMaterials[key] = mat;
|
||||
return mat;
|
||||
}
|
||||
|
||||
function clone(obj) { return JSON.parse(JSON.stringify(obj)); }
|
||||
|
||||
// Dak-state per unit: volgt standaard de vloermaat (followFloor), maar mag
|
||||
@@ -60,11 +108,12 @@ window.RoofModel = (() => {
|
||||
const deck = clone(floorState);
|
||||
deck.chassis = { enabled: false, wheelDiameter: 400, trackWidth: 1200, wheelbase: 1200 };
|
||||
deck.openings = [];
|
||||
return { followFloor: true, offsetX: 0, offsetZ: 0, fascia: true, deck };
|
||||
return { followFloor: true, offsetX: 0, offsetZ: 0, fascia: true, covering: 'dakleer', deck };
|
||||
}
|
||||
|
||||
function thickness(calcData) {
|
||||
return calcData.joistH + calcData.underlayThick + DAKLEER_MM;
|
||||
// Dikte dek + bedekking (mm, haaks op het dakvlak).
|
||||
function thickness(calcData, covering) {
|
||||
return calcData.joistH + calcData.underlayThick + (COVERINGS[covering] || COVERINGS.dakleer).thickMM;
|
||||
}
|
||||
|
||||
function createMaterials() {
|
||||
@@ -106,20 +155,31 @@ window.RoofModel = (() => {
|
||||
hole.absarc(o.x * toM, o.y * toM - dM / 2, o.r * toM, 0, Math.PI * 2, false);
|
||||
shape.holes.push(hole);
|
||||
});
|
||||
const leer = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { steps: 1, depth: DAKLEER_MM * toM, bevelEnabled: false }), m.dakleerMaterial);
|
||||
// Andere bedekking (opts.covering, bijv. riet): een laag van thickMM met
|
||||
// de textuur (vorm-coördinaten in m = UV; langs de helling = v).
|
||||
const cov = COVERINGS[opts.covering] || COVERINGS.dakleer;
|
||||
const covMat = coveringMaterial(opts.covering) || m.dakleerMaterial;
|
||||
const leer = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { steps: 1, depth: cov.thickMM * toM, bevelEnabled: false }), covMat);
|
||||
leer.rotation.x = Math.PI / 2;
|
||||
leer.position.y = deckH + DAKLEER_MM * toM;
|
||||
leer.position.y = deckH + cov.thickMM * toM;
|
||||
leer.castShadow = cov.thickMM > DAKLEER_MM;
|
||||
leer.visible = opts.dakleerVisible !== false;
|
||||
group.add(leer);
|
||||
return group;
|
||||
}
|
||||
|
||||
// Specificaties: die van het dek (FloorModel) + dakleer.
|
||||
function computeSpecs(calcData, resolvedOpenings) {
|
||||
// Specificaties: die van het dek (FloorModel) + de bedekking (dakleer of
|
||||
// bijv. riet: coveringM2/Kg, dakleer dan 0).
|
||||
function computeSpecs(calcData, resolvedOpenings, covering) {
|
||||
const deck = FloorModel.computeSpecs(calcData, resolvedOpenings);
|
||||
const dakleerM2 = deck.osbAreaM2; // zelfde vlak minus doorvoeren
|
||||
const dakleerKg = dakleerM2 * DENSITY_DAKLEER_KGM2;
|
||||
return Object.assign({}, deck, { dakleerM2, dakleerKg, totalKg: deck.totalKg + dakleerKg });
|
||||
const areaM2 = deck.osbAreaM2; // zelfde vlak minus doorvoeren
|
||||
const isLeer = !COVERINGS[covering] || covering === 'dakleer';
|
||||
const kg = areaM2 * (COVERINGS[covering] || COVERINGS.dakleer).kgM2;
|
||||
return Object.assign({}, deck, {
|
||||
dakleerM2: isLeer ? areaM2 : 0, dakleerKg: isLeer ? kg : 0,
|
||||
rietM2: isLeer ? 0 : areaM2, rietKg: isLeer ? 0 : kg,
|
||||
totalKg: deck.totalKg + kg
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
@@ -193,6 +253,7 @@ window.RoofModel = (() => {
|
||||
const cuts = [a0, ...kinks, a1];
|
||||
const flat = FloorModel.computeFromState(roofState.deck, rims);
|
||||
const H = (p) => profileHeight(shape.profile, p);
|
||||
const covering = coveringFor(roofState.covering, shape.type);
|
||||
|
||||
// Doorvoeren: positie in de plattegrond (zoals in de dak-editor) -> vlak.
|
||||
const holes = flat.resolvedOpenings.filter(o => !o.removed).map(o => {
|
||||
@@ -223,10 +284,10 @@ window.RoofModel = (() => {
|
||||
}
|
||||
const mid = (p0 + p1) / 2;
|
||||
return Object.assign(plane, {
|
||||
data, specs: computeSpecs(data.calcData, data.resolvedOpenings),
|
||||
data, specs: computeSpecs(data.calcData, data.resolvedOpenings, covering),
|
||||
cx: alongX ? mid - xc : 0, cz: alongX ? 0 : mid - zc, y: (h0 + h1) / 2,
|
||||
// Verticale dikte (dek + dakleer) van dit vlak.
|
||||
vThick: thickness(data.calcData) / cos
|
||||
// Verticale dikte (dek + bedekking) van dit vlak.
|
||||
vThick: thickness(data.calcData, covering) / cos
|
||||
});
|
||||
});
|
||||
|
||||
@@ -239,7 +300,7 @@ window.RoofModel = (() => {
|
||||
return H(q) + t;
|
||||
};
|
||||
return {
|
||||
shape, planes, specs: sumSpecs(planes.map(pl => pl.specs)),
|
||||
shape, covering, planes, specs: sumSpecs(planes.map(pl => pl.specs)),
|
||||
undersideAt, topAt,
|
||||
ridges: kinks.map(value => ({ axis: shape.axis, value })),
|
||||
maxTop: Math.max(...planes.map(pl => Math.max(pl.h0, pl.h1) + pl.vThick))
|
||||
@@ -272,7 +333,7 @@ window.RoofModel = (() => {
|
||||
0, 1 / c, sn, 0,
|
||||
0, 0, c, 0,
|
||||
0, 0, 0, 1);
|
||||
shear.add(build3D(pl.data.calcData, pl.data.resolvedOpenings, opts));
|
||||
shear.add(build3D(pl.data.calcData, pl.data.resolvedOpenings, Object.assign({ covering: unitRoof.covering }, opts)));
|
||||
g.add(shear);
|
||||
group.add(g);
|
||||
});
|
||||
@@ -412,7 +473,7 @@ window.RoofModel = (() => {
|
||||
const top = Math.max(...segments.map(sg => Math.max(sg.topAt(sg.a), sg.topAt(sg.b))));
|
||||
return {
|
||||
segments, overhang: s.overhang, spotRatio: SPOT_RATIO,
|
||||
fasciaHeight: s.fasciaHeight, trimHeight: s.trimHeight,
|
||||
fasciaHeight: s.fasciaHeight, trimHeight: s.trimHeight, fasciaColor: s.fasciaColor,
|
||||
top, edgeAbove: EDGE_ABOVE_MM,
|
||||
fasciaLengthMM: segments.filter(sg => sg.fascia).reduce((t, sg) => t + segLen(sg), 0),
|
||||
trimLengthMM: segments.filter(sg => !sg.fascia).reduce((t, sg) => t + segLen(sg), 0),
|
||||
@@ -461,6 +522,8 @@ window.RoofModel = (() => {
|
||||
const toM = 0.001;
|
||||
const group = new THREE.Group();
|
||||
if (!edge) return group;
|
||||
// Het boeidelen-materiaal wordt gedeeld: kleur volgens de instelling.
|
||||
materials.fasciaMaterial.color.setHex((FASCIA_COLORS[edge.fasciaColor] || FASCIA_COLORS.antraciet).hex);
|
||||
edge.segments.forEach(sg => {
|
||||
let polys = [[sg.a, sg.b, sg.outerB, sg.outerA]];
|
||||
sg.ridges.forEach(r => {
|
||||
@@ -491,11 +554,12 @@ window.RoofModel = (() => {
|
||||
const s = Object.assign({}, DEFAULT_SETTINGS, settings || {});
|
||||
s.overhang = Math.min(OVERHANG_MAX, Math.max(OVERHANG_MIN, s.overhang));
|
||||
delete s.edge; delete s.spots; // oud (v0.3.0): nu per unit
|
||||
if (!FASCIA_COLORS[s.fasciaColor]) s.fasciaColor = DEFAULT_SETTINGS.fasciaColor;
|
||||
return s;
|
||||
}
|
||||
|
||||
return {
|
||||
DAKLEER_MM, OVERHANG_MIN, OVERHANG_MAX, DEFAULT_SETTINGS, SPOT_RATIO,
|
||||
DAKLEER_MM, EDGE_ABOVE_MM, RIET, OVERHANG_MIN, OVERHANG_MAX, DEFAULT_SETTINGS, SPOT_RATIO, FASCIA_COLORS, COVERINGS, coveringFor,
|
||||
createDefaultRoof, thickness, createMaterials, build3D, computeSpecs,
|
||||
computeShape, wallProfile, profileHeight, computeUnitRoof, buildUnit3D,
|
||||
computeEdge, buildEdge3D, normalizeSettings
|
||||
|
||||
Reference in New Issue
Block a user