Add support for segment-shaped water contact patches
This commit is contained in:
@@ -28,7 +28,10 @@ export interface WaterContactTriangleMesh {
|
|||||||
|
|
||||||
export type WaterContactSource = WaterContactBounds | WaterContactOrientedBox | WaterContactTriangleMesh;
|
export type WaterContactSource = WaterContactBounds | WaterContactOrientedBox | WaterContactTriangleMesh;
|
||||||
|
|
||||||
|
export type WaterContactPatchShape = "box" | "segment";
|
||||||
|
|
||||||
export interface WaterContactPatch {
|
export interface WaterContactPatch {
|
||||||
|
shape: WaterContactPatchShape;
|
||||||
x: number;
|
x: number;
|
||||||
z: number;
|
z: number;
|
||||||
halfWidth: number;
|
halfWidth: number;
|
||||||
@@ -42,6 +45,7 @@ export interface WaterMaterialResult {
|
|||||||
animationUniform: { value: number } | null;
|
animationUniform: { value: number } | null;
|
||||||
contactPatchesUniform: { value: Vector4[] } | null;
|
contactPatchesUniform: { value: Vector4[] } | null;
|
||||||
contactPatchAxesUniform: { value: Vector2[] } | null;
|
contactPatchAxesUniform: { value: Vector2[] } | null;
|
||||||
|
contactPatchShapesUniform: { value: number[] } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WaterMaterialOptions {
|
interface WaterMaterialOptions {
|
||||||
@@ -66,7 +70,7 @@ interface OrientedWaterVolume {
|
|||||||
size: Vec3;
|
size: Vec3;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TriangleMeshPatchSample {
|
interface TriangleMeshSegmentSample {
|
||||||
patch: WaterContactPatch;
|
patch: WaterContactPatch;
|
||||||
normal: Vector3;
|
normal: Vector3;
|
||||||
}
|
}
|
||||||
@@ -389,6 +393,7 @@ function createPatchFromProjectedPoints(projectedPoints: Vector2[], preferredAxi
|
|||||||
const patchCenterSecondary = (minSecondary + maxSecondary) * 0.5;
|
const patchCenterSecondary = (minSecondary + maxSecondary) * 0.5;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
shape: "box",
|
||||||
x: primaryAxis.x * patchCenterPrimary + secondaryAxis.x * patchCenterSecondary,
|
x: primaryAxis.x * patchCenterPrimary + secondaryAxis.x * patchCenterSecondary,
|
||||||
z: primaryAxis.y * patchCenterPrimary + secondaryAxis.y * patchCenterSecondary,
|
z: primaryAxis.y * patchCenterPrimary + secondaryAxis.y * patchCenterSecondary,
|
||||||
halfWidth,
|
halfWidth,
|
||||||
@@ -410,6 +415,28 @@ function computeTriangleNormal(pointA: Vector3, pointB: Vector3, pointC: Vector3
|
|||||||
return normal.normalize();
|
return normal.normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSegmentPatchFromEndpoints(startPoint: Vector2, endPoint: Vector2, radius: number): WaterContactPatch | null {
|
||||||
|
const axis = endPoint.clone().sub(startPoint);
|
||||||
|
const length = axis.length();
|
||||||
|
|
||||||
|
if (length <= WATER_CONTACT_EPSILON) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
axis.divideScalar(length);
|
||||||
|
const center = startPoint.clone().add(endPoint).multiplyScalar(0.5);
|
||||||
|
|
||||||
|
return {
|
||||||
|
shape: "segment",
|
||||||
|
x: center.x,
|
||||||
|
z: center.y,
|
||||||
|
halfWidth: length * 0.5,
|
||||||
|
halfDepth: Math.max(radius, WATER_CONTACT_EPSILON),
|
||||||
|
axisX: axis.x,
|
||||||
|
axisZ: axis.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function addUniqueProjectedPoint(points: Vector2[], point: Vector2) {
|
function addUniqueProjectedPoint(points: Vector2[], point: Vector2) {
|
||||||
const alreadyExists = points.some(
|
const alreadyExists = points.some(
|
||||||
(candidate) => Math.abs(candidate.x - point.x) <= WATER_CONTACT_EPSILON && Math.abs(candidate.y - point.y) <= WATER_CONTACT_EPSILON
|
(candidate) => Math.abs(candidate.x - point.x) <= WATER_CONTACT_EPSILON && Math.abs(candidate.y - point.y) <= WATER_CONTACT_EPSILON
|
||||||
@@ -488,7 +515,7 @@ function createWaterlineSegmentFromPolygon(polygon: Vector3[], surfaceY: number)
|
|||||||
return [startPoint.clone(), endPoint.clone()] as const;
|
return [startPoint.clone(), endPoint.clone()] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPatchCornerPoints(patch: WaterContactPatch) {
|
function createSegmentEndpoints(patch: WaterContactPatch) {
|
||||||
const axis = new Vector2(patch.axisX, patch.axisZ);
|
const axis = new Vector2(patch.axisX, patch.axisZ);
|
||||||
if (axis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
if (axis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
||||||
axis.set(1, 0);
|
axis.set(1, 0);
|
||||||
@@ -496,17 +523,13 @@ function createPatchCornerPoints(patch: WaterContactPatch) {
|
|||||||
axis.normalize();
|
axis.normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
const perpendicularAxis = new Vector2(-axis.y, axis.x);
|
|
||||||
const center = new Vector2(patch.x, patch.z);
|
const center = new Vector2(patch.x, patch.z);
|
||||||
return [
|
const offset = axis.clone().multiplyScalar(patch.halfWidth);
|
||||||
center.clone().add(axis.clone().multiplyScalar(patch.halfWidth)).add(perpendicularAxis.clone().multiplyScalar(patch.halfDepth)),
|
|
||||||
center.clone().add(axis.clone().multiplyScalar(patch.halfWidth)).add(perpendicularAxis.clone().multiplyScalar(-patch.halfDepth)),
|
return [center.clone().sub(offset), center.clone().add(offset)] as const;
|
||||||
center.clone().add(axis.clone().multiplyScalar(-patch.halfWidth)).add(perpendicularAxis.clone().multiplyScalar(patch.halfDepth)),
|
|
||||||
center.clone().add(axis.clone().multiplyScalar(-patch.halfWidth)).add(perpendicularAxis.clone().multiplyScalar(-patch.halfDepth))
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function measurePatchExtentsInBasis(points: Vector2[], axis: Vector2) {
|
function measureSegmentExtentsInBasis(points: Vector2[], radius: number, axis: Vector2) {
|
||||||
const perpendicularAxis = new Vector2(-axis.y, axis.x);
|
const perpendicularAxis = new Vector2(-axis.y, axis.x);
|
||||||
let minPrimary = Number.POSITIVE_INFINITY;
|
let minPrimary = Number.POSITIVE_INFINITY;
|
||||||
let maxPrimary = Number.NEGATIVE_INFINITY;
|
let maxPrimary = Number.NEGATIVE_INFINITY;
|
||||||
@@ -525,8 +548,44 @@ function measurePatchExtentsInBasis(points: Vector2[], axis: Vector2) {
|
|||||||
return {
|
return {
|
||||||
minPrimary,
|
minPrimary,
|
||||||
maxPrimary,
|
maxPrimary,
|
||||||
minSecondary,
|
minSecondary: minSecondary - radius,
|
||||||
maxSecondary
|
maxSecondary: maxSecondary + radius
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSegmentPatchFromCluster(cluster: {
|
||||||
|
axis: Vector2;
|
||||||
|
endpoints: Vector2[];
|
||||||
|
maxRadius: number;
|
||||||
|
extents: ReturnType<typeof measureSegmentExtentsInBasis>;
|
||||||
|
}): WaterContactPatch | null {
|
||||||
|
const axis = cluster.axis.clone();
|
||||||
|
|
||||||
|
if (axis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
||||||
|
axis.set(1, 0);
|
||||||
|
} else {
|
||||||
|
axis.normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
const perpendicularAxis = new Vector2(-axis.y, axis.x);
|
||||||
|
const halfWidth = (cluster.extents.maxPrimary - cluster.extents.minPrimary) * 0.5;
|
||||||
|
const halfDepth = Math.max(cluster.maxRadius, (cluster.extents.maxSecondary - cluster.extents.minSecondary) * 0.5);
|
||||||
|
|
||||||
|
if (halfWidth <= WATER_CONTACT_EPSILON || halfDepth <= WATER_CONTACT_EPSILON) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const centerPrimary = (cluster.extents.minPrimary + cluster.extents.maxPrimary) * 0.5;
|
||||||
|
const centerSecondary = (cluster.extents.minSecondary + cluster.extents.maxSecondary) * 0.5;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shape: "segment",
|
||||||
|
x: axis.x * centerPrimary + perpendicularAxis.x * centerSecondary,
|
||||||
|
z: axis.y * centerPrimary + perpendicularAxis.y * centerSecondary,
|
||||||
|
halfWidth,
|
||||||
|
halfDepth,
|
||||||
|
axisX: axis.x,
|
||||||
|
axisZ: axis.y
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,17 +611,17 @@ function getTriangleMeshMergeSettings(mergeProfile: WaterContactTriangleMesh["me
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeTriangleMeshContactPatches(rawPatches: TriangleMeshPatchSample[], minimumThickness: number, mergeProfile: WaterContactTriangleMesh["mergeProfile"]) {
|
function mergeTriangleMeshContactPatches(rawPatches: TriangleMeshSegmentSample[], minimumThickness: number, mergeProfile: WaterContactTriangleMesh["mergeProfile"]) {
|
||||||
const mergeSettings = getTriangleMeshMergeSettings(mergeProfile, minimumThickness);
|
const mergeSettings = getTriangleMeshMergeSettings(mergeProfile, minimumThickness);
|
||||||
const clusters: Array<{
|
const clusters: Array<{
|
||||||
axis: Vector2;
|
axis: Vector2;
|
||||||
normal: Vector3;
|
normal: Vector3;
|
||||||
points: Vector2[];
|
endpoints: Vector2[];
|
||||||
extents: ReturnType<typeof measurePatchExtentsInBasis>;
|
maxRadius: number;
|
||||||
|
extents: ReturnType<typeof measureSegmentExtentsInBasis>;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
for (const rawPatch of rawPatches) {
|
for (const rawPatch of rawPatches) {
|
||||||
const patchPoints = createPatchCornerPoints(rawPatch.patch);
|
|
||||||
const patchAxis = new Vector2(rawPatch.patch.axisX, rawPatch.patch.axisZ);
|
const patchAxis = new Vector2(rawPatch.patch.axisX, rawPatch.patch.axisZ);
|
||||||
if (patchAxis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
if (patchAxis.lengthSq() <= WATER_CONTACT_EPSILON) {
|
||||||
patchAxis.set(1, 0);
|
patchAxis.set(1, 0);
|
||||||
@@ -570,6 +629,8 @@ function mergeTriangleMeshContactPatches(rawPatches: TriangleMeshPatchSample[],
|
|||||||
patchAxis.normalize();
|
patchAxis.normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const patchEndpoints = createSegmentEndpoints(rawPatch.patch);
|
||||||
|
|
||||||
let merged = false;
|
let merged = false;
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
@@ -583,7 +644,7 @@ function mergeTriangleMeshContactPatches(rawPatches: TriangleMeshPatchSample[],
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const patchExtents = measurePatchExtentsInBasis(patchPoints, cluster.axis);
|
const patchExtents = measureSegmentExtentsInBasis(patchEndpoints, rawPatch.patch.halfDepth, cluster.axis);
|
||||||
const primaryGap = Math.max(0, Math.max(cluster.extents.minPrimary - patchExtents.maxPrimary, patchExtents.minPrimary - cluster.extents.maxPrimary));
|
const primaryGap = Math.max(0, Math.max(cluster.extents.minPrimary - patchExtents.maxPrimary, patchExtents.minPrimary - cluster.extents.maxPrimary));
|
||||||
const secondaryGap = Math.max(0, Math.max(cluster.extents.minSecondary - patchExtents.maxSecondary, patchExtents.minSecondary - cluster.extents.maxSecondary));
|
const secondaryGap = Math.max(0, Math.max(cluster.extents.minSecondary - patchExtents.maxSecondary, patchExtents.minSecondary - cluster.extents.maxSecondary));
|
||||||
const clusterPrimarySpan = cluster.extents.maxPrimary - cluster.extents.minPrimary;
|
const clusterPrimarySpan = cluster.extents.maxPrimary - cluster.extents.minPrimary;
|
||||||
@@ -601,8 +662,9 @@ function mergeTriangleMeshContactPatches(rawPatches: TriangleMeshPatchSample[],
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
cluster.points.push(...patchPoints.map((point) => point.clone()));
|
cluster.endpoints.push(...patchEndpoints.map((point) => point.clone()));
|
||||||
cluster.extents = measurePatchExtentsInBasis(cluster.points, cluster.axis);
|
cluster.maxRadius = Math.max(cluster.maxRadius, rawPatch.patch.halfDepth);
|
||||||
|
cluster.extents = measureSegmentExtentsInBasis(cluster.endpoints, cluster.maxRadius, cluster.axis);
|
||||||
merged = true;
|
merged = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -611,14 +673,15 @@ function mergeTriangleMeshContactPatches(rawPatches: TriangleMeshPatchSample[],
|
|||||||
clusters.push({
|
clusters.push({
|
||||||
axis: patchAxis,
|
axis: patchAxis,
|
||||||
normal: rawPatch.normal.clone(),
|
normal: rawPatch.normal.clone(),
|
||||||
points: patchPoints.map((point) => point.clone()),
|
endpoints: patchEndpoints.map((point) => point.clone()),
|
||||||
extents: measurePatchExtentsInBasis(patchPoints, patchAxis)
|
maxRadius: rawPatch.patch.halfDepth,
|
||||||
|
extents: measureSegmentExtentsInBasis(patchEndpoints, rawPatch.patch.halfDepth, patchAxis)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return clusters
|
return clusters
|
||||||
.map((cluster) => createPatchFromProjectedPoints(cluster.points, cluster.axis, minimumThickness))
|
.map((cluster) => createSegmentPatchFromCluster(cluster))
|
||||||
.filter((patch): patch is WaterContactPatch => patch !== null);
|
.filter((patch): patch is WaterContactPatch => patch !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +708,7 @@ function appendTriangleMeshContactPatches(
|
|||||||
);
|
);
|
||||||
const bandMinimumThickness = Math.max(0.08, Math.min(0.22, surfaceBand * 0.45));
|
const bandMinimumThickness = Math.max(0.08, Math.min(0.22, surfaceBand * 0.45));
|
||||||
const triangleVertices = [new Vector3(), new Vector3(), new Vector3()];
|
const triangleVertices = [new Vector3(), new Vector3(), new Vector3()];
|
||||||
const rawPatches: TriangleMeshPatchSample[] = [];
|
const rawPatches: TriangleMeshSegmentSample[] = [];
|
||||||
|
|
||||||
for (let indexOffset = 0; indexOffset <= source.indices.length - 3; indexOffset += 3) {
|
for (let indexOffset = 0; indexOffset <= source.indices.length - 3; indexOffset += 3) {
|
||||||
const polygon: Vector3[] = [];
|
const polygon: Vector3[] = [];
|
||||||
@@ -683,11 +746,7 @@ function appendTriangleMeshContactPatches(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const patch = createPatchFromProjectedPoints(
|
const patch = createSegmentPatchFromEndpoints(waterlineSegment[0], waterlineSegment[1], bandMinimumThickness);
|
||||||
[waterlineSegment[0], waterlineSegment[1]],
|
|
||||||
preferredAxis,
|
|
||||||
bandMinimumThickness
|
|
||||||
);
|
|
||||||
|
|
||||||
if (patch !== null) {
|
if (patch !== null) {
|
||||||
rawPatches.push({
|
rawPatches.push({
|
||||||
@@ -811,6 +870,13 @@ export function createWaterContactPatchAxisUniformValue(contactPatches?: WaterCo
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createWaterContactPatchShapeUniformValue(contactPatches?: WaterContactPatch[]): number[] {
|
||||||
|
return Array.from({ length: MAX_WATER_CONTACT_PATCHES }, (_, index) => {
|
||||||
|
const patch = contactPatches?.[index];
|
||||||
|
return patch?.shape === "segment" ? 1 : 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function createWaterMaterial(options: WaterMaterialOptions): WaterMaterialResult {
|
export function createWaterMaterial(options: WaterMaterialOptions): WaterMaterialResult {
|
||||||
if (options.wireframe) {
|
if (options.wireframe) {
|
||||||
return {
|
return {
|
||||||
@@ -823,7 +889,8 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
}),
|
}),
|
||||||
animationUniform: null,
|
animationUniform: null,
|
||||||
contactPatchesUniform: null,
|
contactPatchesUniform: null,
|
||||||
contactPatchAxesUniform: null
|
contactPatchAxesUniform: null,
|
||||||
|
contactPatchShapesUniform: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,7 +904,8 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
}),
|
}),
|
||||||
animationUniform: null,
|
animationUniform: null,
|
||||||
contactPatchesUniform: null,
|
contactPatchesUniform: null,
|
||||||
contactPatchAxesUniform: null
|
contactPatchAxesUniform: null,
|
||||||
|
contactPatchShapesUniform: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,6 +913,7 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
const halfSize = new Vector2(Math.max(options.halfSize.x, WATER_CONTACT_EPSILON), Math.max(options.halfSize.z, WATER_CONTACT_EPSILON));
|
const halfSize = new Vector2(Math.max(options.halfSize.x, WATER_CONTACT_EPSILON), Math.max(options.halfSize.z, WATER_CONTACT_EPSILON));
|
||||||
const contactPatchesUniform = { value: createWaterContactPatchUniformValue(options.contactPatches) };
|
const contactPatchesUniform = { value: createWaterContactPatchUniformValue(options.contactPatches) };
|
||||||
const contactPatchAxesUniform = { value: createWaterContactPatchAxisUniformValue(options.contactPatches) };
|
const contactPatchAxesUniform = { value: createWaterContactPatchAxisUniformValue(options.contactPatches) };
|
||||||
|
const contactPatchShapesUniform = { value: createWaterContactPatchShapeUniformValue(options.contactPatches) };
|
||||||
const waveStrength = Math.max(0, options.waveStrength);
|
const waveStrength = Math.max(0, options.waveStrength);
|
||||||
const waveAmplitude = 0.016 + Math.min(0.12, waveStrength * 0.06);
|
const waveAmplitude = 0.016 + Math.min(0.12, waveStrength * 0.06);
|
||||||
const clampedOpacity = Math.max(0.14, Math.min(1, options.opacity));
|
const clampedOpacity = Math.max(0.14, Math.min(1, options.opacity));
|
||||||
@@ -864,6 +933,7 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
varying vec3 vWaveNormal;
|
varying vec3 vWaveNormal;
|
||||||
varying vec3 vWorldPos;
|
varying vec3 vWorldPos;
|
||||||
varying vec3 vViewDir;
|
varying vec3 vViewDir;
|
||||||
|
#include <fog_pars_vertex>
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 transformedPosition = position;
|
vec3 transformedPosition = position;
|
||||||
@@ -891,9 +961,11 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
}
|
}
|
||||||
|
|
||||||
vec4 worldPos = modelMatrix * vec4(transformedPosition, 1.0);
|
vec4 worldPos = modelMatrix * vec4(transformedPosition, 1.0);
|
||||||
|
vec4 mvPosition = viewMatrix * worldPos;
|
||||||
vWorldPos = worldPos.xyz;
|
vWorldPos = worldPos.xyz;
|
||||||
vViewDir = normalize(cameraPosition - worldPos.xyz);
|
vViewDir = normalize(cameraPosition - worldPos.xyz);
|
||||||
gl_Position = projectionMatrix * viewMatrix * worldPos;
|
gl_Position = projectionMatrix * mvPosition;
|
||||||
|
#include <fog_vertex>
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -908,11 +980,13 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
uniform vec2 halfSize;
|
uniform vec2 halfSize;
|
||||||
uniform vec4 contactPatches[${MAX_WATER_CONTACT_PATCHES}];
|
uniform vec4 contactPatches[${MAX_WATER_CONTACT_PATCHES}];
|
||||||
uniform vec2 contactPatchAxes[${MAX_WATER_CONTACT_PATCHES}];
|
uniform vec2 contactPatchAxes[${MAX_WATER_CONTACT_PATCHES}];
|
||||||
|
uniform float contactPatchShapes[${MAX_WATER_CONTACT_PATCHES}];
|
||||||
|
|
||||||
varying vec2 vLocalSurfaceUv;
|
varying vec2 vLocalSurfaceUv;
|
||||||
varying vec3 vWaveNormal;
|
varying vec3 vWaveNormal;
|
||||||
varying vec3 vWorldPos;
|
varying vec3 vWorldPos;
|
||||||
varying vec3 vViewDir;
|
varying vec3 vViewDir;
|
||||||
|
#include <fog_pars_fragment>
|
||||||
|
|
||||||
float hash(vec2 p) {
|
float hash(vec2 p) {
|
||||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
||||||
@@ -943,6 +1017,22 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float signedDistanceToRegion(vec2 point, vec2 center, vec2 axis, vec2 halfExtents) {
|
||||||
|
vec2 patchPerpendicular = vec2(-axis.y, axis.x);
|
||||||
|
vec2 patchLocalUv = vec2(dot(point - center, axis), dot(point - center, patchPerpendicular));
|
||||||
|
vec2 regionDelta = abs(patchLocalUv) - halfExtents;
|
||||||
|
vec2 outsideDelta = max(regionDelta, 0.0);
|
||||||
|
float outsideDistance = length(outsideDelta);
|
||||||
|
float insideDistance = min(max(regionDelta.x, regionDelta.y), 0.0);
|
||||||
|
return outsideDistance + insideDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
float distanceToSegmentBand(vec2 point, vec2 center, vec2 axis, float halfLength) {
|
||||||
|
float along = clamp(dot(point - center, axis), -halfLength, halfLength);
|
||||||
|
vec2 closestPoint = center + axis * along;
|
||||||
|
return distance(point, closestPoint);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 normal = normalize(vWaveNormal);
|
vec3 normal = normalize(vWaveNormal);
|
||||||
vec3 viewDir = normalize(vViewDir);
|
vec3 viewDir = normalize(vViewDir);
|
||||||
@@ -972,19 +1062,34 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
}
|
}
|
||||||
|
|
||||||
vec2 patchAxis = contactPatchAxes[patchIndex];
|
vec2 patchAxis = contactPatchAxes[patchIndex];
|
||||||
vec2 patchPerpendicular = vec2(-patchAxis.y, patchAxis.x);
|
if (dot(patchAxis, patchAxis) <= 0.0) {
|
||||||
vec2 patchLocalUv = vec2(dot(vLocalSurfaceUv - patchData.xy, patchAxis), dot(vLocalSurfaceUv - patchData.xy, patchPerpendicular));
|
patchAxis = vec2(1.0, 0.0);
|
||||||
vec2 regionDelta = abs(patchLocalUv) - patchData.zw;
|
} else {
|
||||||
vec2 outsideDelta = max(regionDelta, 0.0);
|
patchAxis = normalize(patchAxis);
|
||||||
float outsideDistance = length(outsideDelta);
|
}
|
||||||
float insideDistance = min(max(regionDelta.x, regionDelta.y), 0.0);
|
|
||||||
float signedDistance = outsideDistance + insideDistance;
|
float alongDistance = dot(vLocalSurfaceUv - patchData.xy, patchAxis);
|
||||||
float boundaryScale = max(min(patchData.z, patchData.w), 0.18);
|
float contactBody = 0.0;
|
||||||
float normalizedDistance = abs(signedDistance) / boundaryScale;
|
float ripple = 0.0;
|
||||||
float contactBody = 1.0 - smoothstep(0.0, 0.65, max(signedDistance, 0.0) / boundaryScale);
|
float normalizedDistance = 1.0;
|
||||||
float ripple = (sin(normalizedDistance * 13.0 - time * 3.2) * 0.5 + 0.5) * exp(-normalizedDistance * 2.6);
|
float tangentNoise = noise(vec2(alongDistance * 0.45 + float(patchIndex) * 7.13, time * 0.12));
|
||||||
|
|
||||||
|
if (contactPatchShapes[patchIndex] > 0.5) {
|
||||||
|
float segmentRadius = max(patchData.w * mix(0.82, 1.18, tangentNoise), 0.05);
|
||||||
|
float segmentDistance = distanceToSegmentBand(vLocalSurfaceUv, patchData.xy, patchAxis, patchData.z);
|
||||||
|
normalizedDistance = segmentDistance / segmentRadius;
|
||||||
|
contactBody = 1.0 - smoothstep(0.0, 1.0, normalizedDistance);
|
||||||
|
ripple = (sin(normalizedDistance * 11.0 - time * 3.2 + alongDistance * 0.48) * 0.5 + 0.5) * exp(-normalizedDistance * 1.9);
|
||||||
|
} else {
|
||||||
|
float boundaryScale = max(min(patchData.z, patchData.w), 0.18) * mix(0.86, 1.14, tangentNoise);
|
||||||
|
float signedDistance = signedDistanceToRegion(vLocalSurfaceUv, patchData.xy, patchAxis, patchData.zw);
|
||||||
|
normalizedDistance = abs(signedDistance) / max(boundaryScale, 0.05);
|
||||||
|
contactBody = 1.0 - smoothstep(0.0, 1.0, normalizedDistance);
|
||||||
|
ripple = (sin(normalizedDistance * 13.0 - time * 3.2 + alongDistance * 0.35) * 0.5 + 0.5) * exp(-normalizedDistance * 2.6);
|
||||||
|
}
|
||||||
|
|
||||||
float wakeNoise = noise(vLocalSurfaceUv * 3.4 + vec2(time * 0.34, -time * 0.28));
|
float wakeNoise = noise(vLocalSurfaceUv * 3.4 + vec2(time * 0.34, -time * 0.28));
|
||||||
float foamField = max(contactBody * 0.42, ripple * (0.72 + wakeNoise * 0.28));
|
float foamField = max(contactBody * 0.48, ripple * (0.68 + wakeNoise * 0.32));
|
||||||
contactFoam = max(contactFoam, foamField);
|
contactFoam = max(contactFoam, foamField);
|
||||||
contactRipple = max(contactRipple, ripple);
|
contactRipple = max(contactRipple, ripple);
|
||||||
contactSheen = max(contactSheen, contactBody);
|
contactSheen = max(contactSheen, contactBody);
|
||||||
@@ -1007,6 +1112,7 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
: clamp(surfaceOpacity * 0.72 + refraction * 0.08 + caustics * 0.04, 0.16, 0.7);
|
: clamp(surfaceOpacity * 0.72 + refraction * 0.08 + caustics * 0.04, 0.16, 0.7);
|
||||||
|
|
||||||
gl_FragColor = vec4(color, alpha);
|
gl_FragColor = vec4(color, alpha);
|
||||||
|
#include <fog_fragment>
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1022,10 +1128,12 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
isTopFace: { value: topFaceFlag },
|
isTopFace: { value: topFaceFlag },
|
||||||
halfSize: { value: halfSize },
|
halfSize: { value: halfSize },
|
||||||
contactPatches: contactPatchesUniform,
|
contactPatches: contactPatchesUniform,
|
||||||
contactPatchAxes: contactPatchAxesUniform
|
contactPatchAxes: contactPatchAxesUniform,
|
||||||
|
contactPatchShapes: contactPatchShapesUniform
|
||||||
},
|
},
|
||||||
transparent: true,
|
transparent: true,
|
||||||
depthWrite: false,
|
depthWrite: false,
|
||||||
|
fog: true,
|
||||||
side: DoubleSide
|
side: DoubleSide
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1033,6 +1141,7 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
|||||||
material,
|
material,
|
||||||
animationUniform,
|
animationUniform,
|
||||||
contactPatchesUniform,
|
contactPatchesUniform,
|
||||||
contactPatchAxesUniform
|
contactPatchAxesUniform,
|
||||||
|
contactPatchShapesUniform
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user