Add waterline segment creation and update contact patch logic
This commit is contained in:
@@ -275,6 +275,70 @@ function computeTriangleNormal(pointA, pointB, pointC) {
|
||||
return normal.normalize();
|
||||
}
|
||||
|
||||
function addUniqueProjectedPoint(points, point) {
|
||||
const alreadyExists = points.some((candidate) => Math.abs(candidate.x - point.x) <= WATER_CONTACT_EPSILON && Math.abs(candidate.y - point.y) <= WATER_CONTACT_EPSILON);
|
||||
if (!alreadyExists) {
|
||||
points.push(point);
|
||||
}
|
||||
}
|
||||
|
||||
function createWaterlineSegmentFromPolygon(polygon, surfaceY) {
|
||||
if (polygon.length < 2) {
|
||||
return null;
|
||||
}
|
||||
const intersectionPoints = [];
|
||||
let previousPoint = polygon[polygon.length - 1] ?? null;
|
||||
if (previousPoint === null) {
|
||||
return null;
|
||||
}
|
||||
for (const point of polygon) {
|
||||
const previousDelta = previousPoint.y - surfaceY;
|
||||
const delta = point.y - surfaceY;
|
||||
const previousOnPlane = Math.abs(previousDelta) <= WATER_CONTACT_EPSILON;
|
||||
const onPlane = Math.abs(delta) <= WATER_CONTACT_EPSILON;
|
||||
if (previousOnPlane && onPlane) {
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(previousPoint.x, previousPoint.z));
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(point.x, point.z));
|
||||
}
|
||||
else if (previousOnPlane) {
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(previousPoint.x, previousPoint.z));
|
||||
}
|
||||
else if (onPlane) {
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(point.x, point.z));
|
||||
}
|
||||
else if ((previousDelta < 0 && delta > 0) || (previousDelta > 0 && delta < 0)) {
|
||||
const interpolation = (surfaceY - previousPoint.y) / (point.y - previousPoint.y);
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(previousPoint.x + (point.x - previousPoint.x) * interpolation, previousPoint.z + (point.z - previousPoint.z) * interpolation));
|
||||
}
|
||||
previousPoint = point;
|
||||
}
|
||||
if (intersectionPoints.length < 2) {
|
||||
return null;
|
||||
}
|
||||
let startPoint = intersectionPoints[0] ?? null;
|
||||
let endPoint = intersectionPoints[1] ?? null;
|
||||
let longestDistanceSquared = -1;
|
||||
for (let startIndex = 0; startIndex < intersectionPoints.length; startIndex += 1) {
|
||||
for (let endIndex = startIndex + 1; endIndex < intersectionPoints.length; endIndex += 1) {
|
||||
const candidateStart = intersectionPoints[startIndex];
|
||||
const candidateEnd = intersectionPoints[endIndex];
|
||||
if (candidateStart === undefined || candidateEnd === undefined) {
|
||||
continue;
|
||||
}
|
||||
const distanceSquared = candidateStart.distanceToSquared(candidateEnd);
|
||||
if (distanceSquared > longestDistanceSquared) {
|
||||
longestDistanceSquared = distanceSquared;
|
||||
startPoint = candidateStart;
|
||||
endPoint = candidateEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startPoint === null || endPoint === null || longestDistanceSquared <= WATER_CONTACT_EPSILON) {
|
||||
return null;
|
||||
}
|
||||
return [startPoint.clone(), endPoint.clone()];
|
||||
}
|
||||
|
||||
function createPatchCornerPoints(patch) {
|
||||
const axis = new Vector2(patch.axisX, patch.axisZ);
|
||||
if (axis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
||||
@@ -416,10 +480,15 @@ function appendTriangleMeshContactPatches(patches, source, volume, inverseRotati
|
||||
continue;
|
||||
}
|
||||
const clippedPolygon = clipPolygonToContactVolume(polygon, halfX, surfaceY - surfaceBand, surfaceY + surfaceBand, halfZ);
|
||||
if (clippedPolygon.length < 2) {
|
||||
const waterlineSegment = createWaterlineSegmentFromPolygon(clippedPolygon, surfaceY);
|
||||
if (waterlineSegment === null) {
|
||||
continue;
|
||||
}
|
||||
const patch = createPatchFromProjectedPoints(clippedPolygon.map((point) => new Vector2(point.x, point.z)), null, bandMinimumThickness);
|
||||
const preferredAxis = waterlineSegment[1].clone().sub(waterlineSegment[0]);
|
||||
if (preferredAxis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
||||
continue;
|
||||
}
|
||||
const patch = createPatchFromProjectedPoints([waterlineSegment[0], waterlineSegment[1]], preferredAxis, bandMinimumThickness);
|
||||
if (patch !== null) {
|
||||
rawPatches.push({
|
||||
patch,
|
||||
|
||||
@@ -410,6 +410,84 @@ function computeTriangleNormal(pointA: Vector3, pointB: Vector3, pointC: Vector3
|
||||
return normal.normalize();
|
||||
}
|
||||
|
||||
function addUniqueProjectedPoint(points: Vector2[], point: Vector2) {
|
||||
const alreadyExists = points.some(
|
||||
(candidate) => Math.abs(candidate.x - point.x) <= WATER_CONTACT_EPSILON && Math.abs(candidate.y - point.y) <= WATER_CONTACT_EPSILON
|
||||
);
|
||||
|
||||
if (!alreadyExists) {
|
||||
points.push(point);
|
||||
}
|
||||
}
|
||||
|
||||
function createWaterlineSegmentFromPolygon(polygon: Vector3[], surfaceY: number) {
|
||||
if (polygon.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const intersectionPoints: Vector2[] = [];
|
||||
let previousPoint = polygon[polygon.length - 1] ?? null;
|
||||
|
||||
if (previousPoint === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const point of polygon) {
|
||||
const previousDelta = previousPoint.y - surfaceY;
|
||||
const delta = point.y - surfaceY;
|
||||
const previousOnPlane = Math.abs(previousDelta) <= WATER_CONTACT_EPSILON;
|
||||
const onPlane = Math.abs(delta) <= WATER_CONTACT_EPSILON;
|
||||
|
||||
if (previousOnPlane && onPlane) {
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(previousPoint.x, previousPoint.z));
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(point.x, point.z));
|
||||
} else if (previousOnPlane) {
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(previousPoint.x, previousPoint.z));
|
||||
} else if (onPlane) {
|
||||
addUniqueProjectedPoint(intersectionPoints, new Vector2(point.x, point.z));
|
||||
} else if ((previousDelta < 0 && delta > 0) || (previousDelta > 0 && delta < 0)) {
|
||||
const interpolation = (surfaceY - previousPoint.y) / (point.y - previousPoint.y);
|
||||
addUniqueProjectedPoint(
|
||||
intersectionPoints,
|
||||
new Vector2(previousPoint.x + (point.x - previousPoint.x) * interpolation, previousPoint.z + (point.z - previousPoint.z) * interpolation)
|
||||
);
|
||||
}
|
||||
|
||||
previousPoint = point;
|
||||
}
|
||||
|
||||
if (intersectionPoints.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let startPoint = intersectionPoints[0] ?? null;
|
||||
let endPoint = intersectionPoints[1] ?? null;
|
||||
let longestDistanceSquared = -1;
|
||||
|
||||
for (let startIndex = 0; startIndex < intersectionPoints.length; startIndex += 1) {
|
||||
for (let endIndex = startIndex + 1; endIndex < intersectionPoints.length; endIndex += 1) {
|
||||
const candidateStart = intersectionPoints[startIndex];
|
||||
const candidateEnd = intersectionPoints[endIndex];
|
||||
if (candidateStart === undefined || candidateEnd === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distanceSquared = candidateStart.distanceToSquared(candidateEnd);
|
||||
if (distanceSquared > longestDistanceSquared) {
|
||||
longestDistanceSquared = distanceSquared;
|
||||
startPoint = candidateStart;
|
||||
endPoint = candidateEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startPoint === null || endPoint === null || longestDistanceSquared <= WATER_CONTACT_EPSILON) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [startPoint.clone(), endPoint.clone()] as const;
|
||||
}
|
||||
|
||||
function createPatchCornerPoints(patch: WaterContactPatch) {
|
||||
const axis = new Vector2(patch.axisX, patch.axisZ);
|
||||
if (axis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
||||
@@ -594,14 +672,20 @@ function appendTriangleMeshContactPatches(
|
||||
}
|
||||
|
||||
const clippedPolygon = clipPolygonToContactVolume(polygon, halfX, surfaceY - surfaceBand, surfaceY + surfaceBand, halfZ);
|
||||
const waterlineSegment = createWaterlineSegmentFromPolygon(clippedPolygon, surfaceY);
|
||||
|
||||
if (clippedPolygon.length < 2) {
|
||||
if (waterlineSegment === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const preferredAxis = waterlineSegment[1].clone().sub(waterlineSegment[0]);
|
||||
if (preferredAxis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const patch = createPatchFromProjectedPoints(
|
||||
clippedPolygon.map((point) => new Vector2(point.x, point.z)),
|
||||
null,
|
||||
[waterlineSegment[0], waterlineSegment[1]],
|
||||
preferredAxis,
|
||||
bandMinimumThickness
|
||||
);
|
||||
|
||||
|
||||
@@ -274,6 +274,63 @@ describe("water material helpers", () => {
|
||||
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(0.2);
|
||||
expect(Math.abs(patches[0]?.axisX ?? 0)).toBeGreaterThan(0.2);
|
||||
expect(Math.abs(patches[0]?.axisZ ?? 0)).toBeGreaterThan(0.2);
|
||||
expect(patches[0]?.halfDepth ?? 1).toBeLessThan(0.3);
|
||||
});
|
||||
|
||||
it("uses narrow waterline bands for large sloped triangle surfaces", () => {
|
||||
const patches = collectWaterContactPatches(
|
||||
{
|
||||
center: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
rotationDegrees: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
size: {
|
||||
x: 12,
|
||||
y: 2,
|
||||
z: 12
|
||||
}
|
||||
},
|
||||
[
|
||||
{
|
||||
kind: "triangleMesh",
|
||||
mergeProfile: "aggressive",
|
||||
vertices: new Float32Array([
|
||||
-4, -1, -3,
|
||||
4, 1.4, -3,
|
||||
4, 1.4, 3,
|
||||
-4, -1, 3
|
||||
]),
|
||||
indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
|
||||
transform: {
|
||||
position: {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
},
|
||||
rotationDegrees: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
scale: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
expect(patches).toHaveLength(1);
|
||||
expect(patches[0]?.halfWidth ?? 0).toBeGreaterThan(1.5);
|
||||
expect(patches[0]?.halfDepth ?? 1).toBeLessThan(0.3);
|
||||
});
|
||||
|
||||
it("merges adjacent triangle mesh strips into one longer foam band", () => {
|
||||
|
||||
Reference in New Issue
Block a user