Add water surface subdivision and UV interpolation in box brush mesh
This commit is contained in:
@@ -23,6 +23,7 @@ const EDGE_VERTEX_IDS = {
|
||||
edgeZ_negX_posY: ["negX_posY_negZ", "negX_posY_posZ"],
|
||||
edgeZ_posX_posY: ["posX_posY_negZ", "posX_posY_posZ"]
|
||||
};
|
||||
const WATER_TOP_FACE_RENDER_SEGMENTS = 12;
|
||||
function cloneVec3(vector) {
|
||||
return { x: vector.x, y: vector.y, z: vector.z };
|
||||
}
|
||||
@@ -222,6 +223,29 @@ function computeFaceBounds(vertices) {
|
||||
}
|
||||
return { min, max };
|
||||
}
|
||||
function lerpNumber(start, end, amount) {
|
||||
return start + (end - start) * amount;
|
||||
}
|
||||
function lerpVec3(start, end, amount) {
|
||||
return {
|
||||
x: lerpNumber(start.x, end.x, amount),
|
||||
y: lerpNumber(start.y, end.y, amount),
|
||||
z: lerpNumber(start.z, end.z, amount)
|
||||
};
|
||||
}
|
||||
function interpolateQuadSurfaceVertex(corners, u, v) {
|
||||
const topEdge = lerpVec3(corners[0], corners[1], u);
|
||||
const bottomEdge = lerpVec3(corners[3], corners[2], u);
|
||||
return lerpVec3(topEdge, bottomEdge, v);
|
||||
}
|
||||
function pushRenderedFaceVertex(positions, normals, uvs, indices, vertex, normal, faceId, faceBounds, uvSize, uvState) {
|
||||
const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds);
|
||||
const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, uvState);
|
||||
positions.push(vertex.x, vertex.y, vertex.z);
|
||||
normals.push(normal.x, normal.y, normal.z);
|
||||
uvs.push(transformedUv.x, transformedUv.y);
|
||||
indices.push(indices.length);
|
||||
}
|
||||
export function getBoxBrushFaceVertexIds(faceId) {
|
||||
return FACE_VERTEX_IDS[faceId];
|
||||
}
|
||||
@@ -257,6 +281,7 @@ export function buildBoxBrushDerivedMeshData(brush) {
|
||||
const normal = computeNewellNormal(faceVertices);
|
||||
const faceBounds = computeFaceBounds(faceVertices);
|
||||
const uvSize = getFaceUvSize(faceId, faceBounds);
|
||||
const uvState = brush.faces[faceId].uv;
|
||||
const indexStart = indices.length;
|
||||
faceSurfaces.push({
|
||||
faceId,
|
||||
@@ -264,15 +289,32 @@ export function buildBoxBrushDerivedMeshData(brush) {
|
||||
triangles,
|
||||
normal
|
||||
});
|
||||
for (const triangle of triangles) {
|
||||
for (const vertexOffset of triangle) {
|
||||
const vertex = faceVertices[vertexOffset];
|
||||
const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds);
|
||||
const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, brush.faces[faceId].uv);
|
||||
positions.push(vertex.x, vertex.y, vertex.z);
|
||||
normals.push(normal.x, normal.y, normal.z);
|
||||
uvs.push(transformedUv.x, transformedUv.y);
|
||||
indices.push(indices.length);
|
||||
const useSubdividedWaterTopFace = brush.volume.mode === "water" && faceId === "posY" && brush.volume.water.surfaceDisplacementEnabled;
|
||||
if (useSubdividedWaterTopFace) {
|
||||
const faceCorners = faceVertices;
|
||||
for (let row = 0; row < WATER_TOP_FACE_RENDER_SEGMENTS; row += 1) {
|
||||
const v0 = row / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
const v1 = (row + 1) / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
for (let column = 0; column < WATER_TOP_FACE_RENDER_SEGMENTS; column += 1) {
|
||||
const u0 = column / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
const u1 = (column + 1) / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
const quadVertices = [
|
||||
interpolateQuadSurfaceVertex(faceCorners, u0, v0),
|
||||
interpolateQuadSurfaceVertex(faceCorners, u1, v0),
|
||||
interpolateQuadSurfaceVertex(faceCorners, u1, v1),
|
||||
interpolateQuadSurfaceVertex(faceCorners, u0, v1)
|
||||
];
|
||||
for (const vertex of [quadVertices[0], quadVertices[1], quadVertices[2], quadVertices[0], quadVertices[2], quadVertices[3]]) {
|
||||
pushRenderedFaceVertex(positions, normals, uvs, indices, vertex, normal, faceId, faceBounds, uvSize, uvState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const triangle of triangles) {
|
||||
for (const vertexOffset of triangle) {
|
||||
pushRenderedFaceVertex(positions, normals, uvs, indices, faceVertices[vertexOffset], normal, faceId, faceBounds, uvSize, uvState);
|
||||
}
|
||||
}
|
||||
}
|
||||
groups.push({
|
||||
|
||||
@@ -36,6 +36,8 @@ const EDGE_VERTEX_IDS: Record<BoxEdgeId, readonly [BoxVertexId, BoxVertexId]> =
|
||||
edgeZ_posX_posY: ["posX_posY_negZ", "posX_posY_posZ"]
|
||||
};
|
||||
|
||||
const WATER_TOP_FACE_RENDER_SEGMENTS = 12;
|
||||
|
||||
export interface BoxBrushGeometryDiagnostic {
|
||||
code: string;
|
||||
message: string;
|
||||
@@ -280,6 +282,50 @@ function computeFaceBounds(vertices: Vec3[]): { min: Vec3; max: Vec3 } {
|
||||
return { min, max };
|
||||
}
|
||||
|
||||
function lerpNumber(start: number, end: number, amount: number) {
|
||||
return start + (end - start) * amount;
|
||||
}
|
||||
|
||||
function lerpVec3(start: Vec3, end: Vec3, amount: number): Vec3 {
|
||||
return {
|
||||
x: lerpNumber(start.x, end.x, amount),
|
||||
y: lerpNumber(start.y, end.y, amount),
|
||||
z: lerpNumber(start.z, end.z, amount)
|
||||
};
|
||||
}
|
||||
|
||||
function interpolateQuadSurfaceVertex(
|
||||
corners: readonly [Vec3, Vec3, Vec3, Vec3],
|
||||
u: number,
|
||||
v: number
|
||||
): Vec3 {
|
||||
const topEdge = lerpVec3(corners[0], corners[1], u);
|
||||
const bottomEdge = lerpVec3(corners[3], corners[2], u);
|
||||
|
||||
return lerpVec3(topEdge, bottomEdge, v);
|
||||
}
|
||||
|
||||
function pushRenderedFaceVertex(
|
||||
positions: number[],
|
||||
normals: number[],
|
||||
uvs: number[],
|
||||
indices: number[],
|
||||
vertex: Vec3,
|
||||
normal: Vec3,
|
||||
faceId: BoxFaceId,
|
||||
faceBounds: { min: Vec3; max: Vec3 },
|
||||
uvSize: Vec2,
|
||||
uvState: FaceUvState
|
||||
) {
|
||||
const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds);
|
||||
const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, uvState);
|
||||
|
||||
positions.push(vertex.x, vertex.y, vertex.z);
|
||||
normals.push(normal.x, normal.y, normal.z);
|
||||
uvs.push(transformedUv.x, transformedUv.y);
|
||||
indices.push(indices.length);
|
||||
}
|
||||
|
||||
export function getBoxBrushFaceVertexIds(faceId: BoxFaceId): readonly [BoxVertexId, BoxVertexId, BoxVertexId, BoxVertexId] {
|
||||
return FACE_VERTEX_IDS[faceId];
|
||||
}
|
||||
@@ -322,6 +368,7 @@ export function buildBoxBrushDerivedMeshData(brush: BoxBrush): DerivedBoxBrushMe
|
||||
const normal = computeNewellNormal(faceVertices);
|
||||
const faceBounds = computeFaceBounds(faceVertices);
|
||||
const uvSize = getFaceUvSize(faceId, faceBounds);
|
||||
const uvState = brush.faces[faceId].uv as FaceUvState;
|
||||
const indexStart = indices.length;
|
||||
|
||||
faceSurfaces.push({
|
||||
@@ -331,15 +378,49 @@ export function buildBoxBrushDerivedMeshData(brush: BoxBrush): DerivedBoxBrushMe
|
||||
normal
|
||||
});
|
||||
|
||||
for (const triangle of triangles) {
|
||||
for (const vertexOffset of triangle) {
|
||||
const vertex = faceVertices[vertexOffset];
|
||||
const projectedUv = projectLocalVertexToFaceUv(vertex, faceId, faceBounds);
|
||||
const transformedUv = transformProjectedFaceUv(projectedUv, uvSize, brush.faces[faceId].uv as FaceUvState);
|
||||
positions.push(vertex.x, vertex.y, vertex.z);
|
||||
normals.push(normal.x, normal.y, normal.z);
|
||||
uvs.push(transformedUv.x, transformedUv.y);
|
||||
indices.push(indices.length);
|
||||
const useSubdividedWaterTopFace =
|
||||
brush.volume.mode === "water" &&
|
||||
faceId === "posY" &&
|
||||
brush.volume.water.surfaceDisplacementEnabled;
|
||||
|
||||
if (useSubdividedWaterTopFace) {
|
||||
const faceCorners = faceVertices as [Vec3, Vec3, Vec3, Vec3];
|
||||
|
||||
for (let row = 0; row < WATER_TOP_FACE_RENDER_SEGMENTS; row += 1) {
|
||||
const v0 = row / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
const v1 = (row + 1) / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
|
||||
for (let column = 0; column < WATER_TOP_FACE_RENDER_SEGMENTS; column += 1) {
|
||||
const u0 = column / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
const u1 = (column + 1) / WATER_TOP_FACE_RENDER_SEGMENTS;
|
||||
const quadVertices: [Vec3, Vec3, Vec3, Vec3] = [
|
||||
interpolateQuadSurfaceVertex(faceCorners, u0, v0),
|
||||
interpolateQuadSurfaceVertex(faceCorners, u1, v0),
|
||||
interpolateQuadSurfaceVertex(faceCorners, u1, v1),
|
||||
interpolateQuadSurfaceVertex(faceCorners, u0, v1)
|
||||
];
|
||||
|
||||
for (const vertex of [quadVertices[0], quadVertices[1], quadVertices[2], quadVertices[0], quadVertices[2], quadVertices[3]]) {
|
||||
pushRenderedFaceVertex(positions, normals, uvs, indices, vertex, normal, faceId, faceBounds, uvSize, uvState);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const triangle of triangles) {
|
||||
for (const vertexOffset of triangle) {
|
||||
pushRenderedFaceVertex(
|
||||
positions,
|
||||
normals,
|
||||
uvs,
|
||||
indices,
|
||||
faceVertices[vertexOffset],
|
||||
normal,
|
||||
faceId,
|
||||
faceBounds,
|
||||
uvSize,
|
||||
uvState
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -718,7 +718,6 @@ export function createWaterMaterial(options) {
|
||||
|
||||
varying vec2 vLocalSurfaceUv;
|
||||
varying vec3 vWaveNormal;
|
||||
varying vec3 vWorldPos;
|
||||
varying vec3 vViewDir;
|
||||
varying vec4 vReflectionCoord;
|
||||
#include <fog_pars_vertex>
|
||||
@@ -754,7 +753,6 @@ export function createWaterMaterial(options) {
|
||||
|
||||
vec4 worldPos = modelMatrix * vec4(transformedPosition, 1.0);
|
||||
vec4 mvPosition = viewMatrix * worldPos;
|
||||
vWorldPos = worldPos.xyz;
|
||||
vViewDir = normalize(cameraPosition - worldPos.xyz);
|
||||
vReflectionCoord = reflectionMatrix * worldPos;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
@@ -779,7 +777,6 @@ export function createWaterMaterial(options) {
|
||||
|
||||
varying vec2 vLocalSurfaceUv;
|
||||
varying vec3 vWaveNormal;
|
||||
varying vec3 vWorldPos;
|
||||
varying vec3 vViewDir;
|
||||
varying vec4 vReflectionCoord;
|
||||
#include <fog_pars_fragment>
|
||||
|
||||
@@ -975,7 +975,6 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
|
||||
varying vec2 vLocalSurfaceUv;
|
||||
varying vec3 vWaveNormal;
|
||||
varying vec3 vWorldPos;
|
||||
varying vec3 vViewDir;
|
||||
varying vec4 vReflectionCoord;
|
||||
#include <fog_pars_vertex>
|
||||
@@ -1011,7 +1010,6 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
|
||||
vec4 worldPos = modelMatrix * vec4(transformedPosition, 1.0);
|
||||
vec4 mvPosition = viewMatrix * worldPos;
|
||||
vWorldPos = worldPos.xyz;
|
||||
vViewDir = normalize(cameraPosition - worldPos.xyz);
|
||||
vReflectionCoord = reflectionMatrix * worldPos;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
@@ -1036,7 +1034,6 @@ export function createWaterMaterial(options: WaterMaterialOptions): WaterMateria
|
||||
|
||||
varying vec2 vLocalSurfaceUv;
|
||||
varying vec3 vWaveNormal;
|
||||
varying vec3 vWorldPos;
|
||||
varying vec3 vViewDir;
|
||||
varying vec4 vReflectionCoord;
|
||||
#include <fog_pars_fragment>
|
||||
|
||||
@@ -628,8 +628,19 @@ export class RuntimeHost {
|
||||
const previousAutoClear = this.renderer.autoClear;
|
||||
const previousRenderTarget = this.renderer.getRenderTarget();
|
||||
const previousFogDensity = this.underwaterSceneFog.density;
|
||||
const previousReflectionStates = this.runtimeWaterContactUniforms.map((waterBinding) => ({
|
||||
binding: waterBinding,
|
||||
enabled: waterBinding.reflectionEnabledUniform?.value ?? 0,
|
||||
texture: waterBinding.reflectionTextureUniform?.value ?? null
|
||||
}));
|
||||
try {
|
||||
this.underwaterSceneFog.density = 0;
|
||||
for (const state of previousReflectionStates) {
|
||||
if (state.binding.reflectionEnabledUniform !== null) {
|
||||
state.binding.reflectionEnabledUniform.value = 0;
|
||||
}
|
||||
}
|
||||
binding.reflectionTextureUniform.value = null;
|
||||
this.renderer.autoClear = true;
|
||||
this.renderer.setRenderTarget(binding.reflectionRenderTarget);
|
||||
this.renderer.clear();
|
||||
@@ -640,6 +651,14 @@ export class RuntimeHost {
|
||||
this.renderer.autoClear = previousAutoClear;
|
||||
this.modelGroup.visible = previousModelGroupVisibility;
|
||||
this.underwaterSceneFog.density = previousFogDensity;
|
||||
for (const state of previousReflectionStates) {
|
||||
if (state.binding.reflectionEnabledUniform !== null) {
|
||||
state.binding.reflectionEnabledUniform.value = state.enabled;
|
||||
}
|
||||
if (state.binding.reflectionTextureUniform !== null) {
|
||||
state.binding.reflectionTextureUniform.value = state.texture;
|
||||
}
|
||||
}
|
||||
for (const hiddenWaterMesh of hiddenWaterMeshes) {
|
||||
hiddenWaterMesh.mesh.visible = hiddenWaterMesh.visible;
|
||||
}
|
||||
|
||||
@@ -884,9 +884,20 @@ export class RuntimeHost {
|
||||
const previousAutoClear = this.renderer.autoClear;
|
||||
const previousRenderTarget = this.renderer.getRenderTarget();
|
||||
const previousFogDensity = this.underwaterSceneFog.density;
|
||||
const previousReflectionStates = this.runtimeWaterContactUniforms.map((waterBinding) => ({
|
||||
binding: waterBinding,
|
||||
enabled: waterBinding.reflectionEnabledUniform?.value ?? 0,
|
||||
texture: waterBinding.reflectionTextureUniform?.value ?? null
|
||||
}));
|
||||
|
||||
try {
|
||||
this.underwaterSceneFog.density = 0;
|
||||
for (const state of previousReflectionStates) {
|
||||
if (state.binding.reflectionEnabledUniform !== null) {
|
||||
state.binding.reflectionEnabledUniform.value = 0;
|
||||
}
|
||||
}
|
||||
binding.reflectionTextureUniform.value = null;
|
||||
this.renderer.autoClear = true;
|
||||
this.renderer.setRenderTarget(binding.reflectionRenderTarget);
|
||||
this.renderer.clear();
|
||||
@@ -896,6 +907,14 @@ export class RuntimeHost {
|
||||
this.renderer.autoClear = previousAutoClear;
|
||||
this.modelGroup.visible = previousModelGroupVisibility;
|
||||
this.underwaterSceneFog.density = previousFogDensity;
|
||||
for (const state of previousReflectionStates) {
|
||||
if (state.binding.reflectionEnabledUniform !== null) {
|
||||
state.binding.reflectionEnabledUniform.value = state.enabled;
|
||||
}
|
||||
if (state.binding.reflectionTextureUniform !== null) {
|
||||
state.binding.reflectionTextureUniform.value = state.texture;
|
||||
}
|
||||
}
|
||||
|
||||
for (const hiddenWaterMesh of hiddenWaterMeshes) {
|
||||
hiddenWaterMesh.mesh.visible = hiddenWaterMesh.visible;
|
||||
|
||||
@@ -3137,7 +3137,18 @@ export class ViewportHost {
|
||||
|
||||
const previousAutoClear = this.renderer.autoClear;
|
||||
const previousRenderTarget = this.renderer.getRenderTarget();
|
||||
const previousReflectionStates = this.viewportWaterSurfaceBindings.map((waterBinding) => ({
|
||||
binding: waterBinding,
|
||||
enabled: waterBinding.reflectionEnabledUniform?.value ?? 0,
|
||||
texture: waterBinding.reflectionTextureUniform?.value ?? null
|
||||
}));
|
||||
try {
|
||||
for (const state of previousReflectionStates) {
|
||||
if (state.binding.reflectionEnabledUniform !== null) {
|
||||
state.binding.reflectionEnabledUniform.value = 0;
|
||||
}
|
||||
}
|
||||
binding.reflectionTextureUniform.value = null;
|
||||
this.renderer.autoClear = true;
|
||||
this.renderer.setRenderTarget(binding.reflectionRenderTarget);
|
||||
this.renderer.clear();
|
||||
@@ -3145,6 +3156,14 @@ export class ViewportHost {
|
||||
} finally {
|
||||
this.renderer.setRenderTarget(previousRenderTarget);
|
||||
this.renderer.autoClear = previousAutoClear;
|
||||
for (const state of previousReflectionStates) {
|
||||
if (state.binding.reflectionEnabledUniform !== null) {
|
||||
state.binding.reflectionEnabledUniform.value = state.enabled;
|
||||
}
|
||||
if (state.binding.reflectionTextureUniform !== null) {
|
||||
state.binding.reflectionTextureUniform.value = state.texture;
|
||||
}
|
||||
}
|
||||
|
||||
for (const hiddenObject of hiddenObjects) {
|
||||
hiddenObject.object.visible = hiddenObject.visible;
|
||||
|
||||
Reference in New Issue
Block a user