Skip to content

Commit 91e4d29

Browse files
authored
LDrawLoader: Check if a face edge is a sub segment of a long edge when smoothing normals (#23077)
* Add support for hard edges when they are a subsegment of a hard edge * Track the number of materials per part * Propagate face materials * update comments
1 parent 9daf001 commit 91e4d29

File tree

1 file changed

+119
-4
lines changed

1 file changed

+119
-4
lines changed

examples/jsm/loaders/LDrawLoader.js

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
ShaderMaterial,
1414
UniformsLib,
1515
UniformsUtils,
16-
Vector3
16+
Vector3,
17+
Ray
1718
} from '../../../build/three.module.js';
1819

1920
// Special surface finish tag types.
@@ -187,7 +188,8 @@ function generateFaceNormals( faces ) {
187188

188189
}
189190

190-
function smoothNormals( faces, lineSegments ) {
191+
const _ray = new Ray();
192+
function smoothNormals( faces, lineSegments, checkSubSegments = false ) {
191193

192194
function hashVertex( v ) {
193195

@@ -207,7 +209,27 @@ function smoothNormals( faces, lineSegments ) {
207209

208210
}
209211

212+
// converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected
213+
// onto the original line.
214+
function toNormalizedRay( v0, v1, targetRay ) {
215+
216+
targetRay.direction.subVectors( v1, v0 ).normalize();
217+
218+
const scalar = v0.dot( targetRay.direction );
219+
targetRay.origin.copy( v0 ).addScaledVector( targetRay.direction, - scalar );
220+
221+
return targetRay;
222+
223+
}
224+
225+
function hashRay( ray ) {
226+
227+
return hashEdge( ray.origin, ray.direction );
228+
229+
}
230+
210231
const hardEdges = new Set();
232+
const hardEdgeRays = new Map();
211233
const halfEdgeList = {};
212234
const normals = [];
213235

@@ -221,6 +243,43 @@ function smoothNormals( faces, lineSegments ) {
221243
hardEdges.add( hashEdge( v0, v1 ) );
222244
hardEdges.add( hashEdge( v1, v0 ) );
223245

246+
// only generate the hard edge ray map if we're checking subsegments because it's more expensive to check
247+
// and requires more memory.
248+
if ( checkSubSegments ) {
249+
250+
// add both ray directions to the map
251+
const ray = toNormalizedRay( v0, v1, new Ray() );
252+
const rh1 = hashRay( ray );
253+
if ( ! hardEdgeRays.has( rh1 ) ) {
254+
255+
toNormalizedRay( v1, v0, ray );
256+
const rh2 = hashRay( ray );
257+
258+
const info = {
259+
ray,
260+
distances: [],
261+
};
262+
263+
hardEdgeRays.set( rh1, info );
264+
hardEdgeRays.set( rh2, info );
265+
266+
}
267+
268+
// store both segments ends in min, max order in the distances array to check if a face edge is a
269+
// subsegment later.
270+
const info = hardEdgeRays.get( rh1 );
271+
let d0 = info.ray.direction.dot( v0 );
272+
let d1 = info.ray.direction.dot( v1 );
273+
if ( d0 > d1 ) {
274+
275+
[ d0, d1 ] = [ d1, d0 ];
276+
277+
}
278+
279+
info.distances.push( d0, d1 );
280+
281+
}
282+
224283
}
225284

226285
// track the half edges associated with each triangle
@@ -238,7 +297,53 @@ function smoothNormals( faces, lineSegments ) {
238297
const hash = hashEdge( v0, v1 );
239298

240299
// don't add the triangle if the edge is supposed to be hard
241-
if ( hardEdges.has( hash ) ) continue;
300+
if ( hardEdges.has( hash ) ) {
301+
302+
continue;
303+
304+
}
305+
306+
// if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds
307+
if ( checkSubSegments ) {
308+
309+
toNormalizedRay( v0, v1, _ray );
310+
311+
const rayHash = hashRay( _ray );
312+
if ( hardEdgeRays.has( rayHash ) ) {
313+
314+
const info = hardEdgeRays.get( rayHash );
315+
const { ray, distances } = info;
316+
let d0 = ray.direction.dot( v0 );
317+
let d1 = ray.direction.dot( v1 );
318+
319+
if ( d0 > d1 ) {
320+
321+
[ d0, d1 ] = [ d1, d0 ];
322+
323+
}
324+
325+
// return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals
326+
let found = false;
327+
for ( let i = 0, l = distances.length; i < l; i += 2 ) {
328+
329+
if ( d0 >= distances[ i ] && d1 <= distances[ i + 1 ] ) {
330+
331+
found = true;
332+
break;
333+
334+
}
335+
336+
}
337+
338+
if ( found ) {
339+
340+
continue;
341+
342+
}
343+
344+
}
345+
346+
}
242347

243348
const info = {
244349
index: index,
@@ -1005,6 +1110,7 @@ class LDrawLoader extends Loader {
10051110
lineSegments: [],
10061111
conditionalSegments: [],
10071112
totalFaces: 0,
1113+
faceMaterials: new Set(),
10081114

10091115
// If true, this object is the start of a construction step
10101116
startingConstructionStep: false
@@ -1751,6 +1857,8 @@ class LDrawLoader extends Loader {
17511857

17521858
}
17531859

1860+
currentParseScope.faceMaterials.add( material );
1861+
17541862
break;
17551863

17561864
// Line type 4: Quadrilateral
@@ -1883,7 +1991,11 @@ class LDrawLoader extends Loader {
18831991
if ( this.smoothNormals && doSmooth ) {
18841992

18851993
generateFaceNormals( subobjectParseScope.faces );
1886-
smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments );
1994+
1995+
// only check subsetgments if we have multiple materials in a single part because this seems to be the case where it's needed most --
1996+
// there may be cases where a single edge line crosses over polygon edges that are broken up by multiple materials.
1997+
const checkSubSegments = subobjectParseScope.faceMaterials.size > 1;
1998+
smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments, checkSubSegments );
18871999

18882000
}
18892001

@@ -1927,10 +2039,12 @@ class LDrawLoader extends Loader {
19272039
const parentLineSegments = parentParseScope.lineSegments;
19282040
const parentConditionalSegments = parentParseScope.conditionalSegments;
19292041
const parentFaces = parentParseScope.faces;
2042+
const parentFaceMaterials = parentParseScope.faceMaterials;
19302043

19312044
const lineSegments = subobjectParseScope.lineSegments;
19322045
const conditionalSegments = subobjectParseScope.conditionalSegments;
19332046
const faces = subobjectParseScope.faces;
2047+
const faceMaterials = subobjectParseScope.faceMaterials;
19342048

19352049
for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
19362050

@@ -1987,6 +2101,7 @@ class LDrawLoader extends Loader {
19872101
}
19882102

19892103
parentParseScope.totalFaces += subobjectParseScope.totalFaces;
2104+
faceMaterials.forEach( material => parentFaceMaterials.add( material ) );
19902105

19912106
}
19922107

0 commit comments

Comments
 (0)