Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cadjoint/viewer/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="data:," />
<title>CADJOINT Playground</title>
<script type="module" crossorigin src="/assets/main-0Bz4AUvN.js"></script>
<script type="module" crossorigin src="/assets/main-DDW0F_Y0.js"></script>
<link rel="modulepreload" crossorigin href="/assets/panels-DNNFhkG6.js">
<link rel="stylesheet" crossorigin href="/assets/panels-BhdWhxca.css">
<link rel="stylesheet" crossorigin href="/assets/main-ByvcS6tS.css">
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/viewer/overlay.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ struct Overlay {
viewport : vec4<f32>,
// depth nudge fraction, casing (px per side; 0 = none), unused, unused
params : vec4<f32>,
// camera forward axis; w = 1 under an orthographic projection
view_dir : vec4<f32>,
};
@group(0) @binding(0) var<uniform> o: Overlay;

Expand Down Expand Up @@ -444,7 +446,14 @@ fn fs_brep(input: BrepVertex) -> @location(0) vec4<f32> {
let facet = normalize(cross(dpdx(input.world), dpdy(input.world)));
// A zero normal means none was supplied: fall back to the facet's.
let normal = select(facet, normalize(input.normal), dot(input.normal, input.normal) > 0.25);
let eye = normalize(o.camera_pos.xyz - input.world);
// Toward the eye: the eye point under perspective, and under an
// orthographic projection the one direction every ray shares — from the
// eye point a flat plane shaded as a radial glow.
let eye = select(
normalize(o.camera_pos.xyz - input.world),
-normalize(o.view_dir.xyz),
o.view_dir.w > 0.5,
);
// Facing ratio, sign-free: a triangle wound either way shades the same,
// so a face the tessellation turned inside out is still readable.
let facing = abs(dot(normal, eye));
Expand Down
20 changes: 15 additions & 5 deletions frontend/src/viewer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
} from "./frameConstants";
import type { AxisIndex } from "./gizmo";
import {
cameraBasis,
cameraPosition,
orthoHeightFor,
viewProjection,
Expand All @@ -67,7 +68,12 @@ import {
import type { ShaderProgramPayload } from "./shaderProgram";
import { ScenePrograms } from "./scenePrograms";
import { SimulationLayer, type IndexRange } from "./simulationLayer";
import { packGraticuleUniforms, packOverlayUniforms, packSceneUniforms } from "./uniforms";
import {
OVERLAY_UNIFORM_FLOATS,
packGraticuleUniforms,
packOverlayUniforms,
packSceneUniforms,
} from "./uniforms";

/** What the shader path has done this session. See `Renderer.shaderStats`. */
export interface ShaderStats {
Expand Down Expand Up @@ -380,8 +386,8 @@ export class Renderer {

this.uniformBuffer = this.createUniform(112); // 7 x vec4, see Uniforms in _webgpu.py
this.viewBuffer = this.createUniform(64);
this.overlayBuffer = this.createUniform(112);
this.meshOverlayBuffer = this.createUniform(112);
this.overlayBuffer = this.createUniform(OVERLAY_UNIFORM_FLOATS * 4);
this.meshOverlayBuffer = this.createUniform(OVERLAY_UNIFORM_FLOATS * 4);
this.graticuleBuffer = this.createUniform(GRATICULE_UNIFORM_SIZE);
this.buildPipelines();

Expand Down Expand Up @@ -775,18 +781,22 @@ export class Renderer {

const matrix = viewProjection(this.view);
device.queue.writeBuffer(this.viewBuffer, 0, matrix);
const looking = {
forward: cameraBasis(position, this.camera.target).forward,
orthographic: this.display.projection === "orthographic",
};
device.queue.writeBuffer(
this.overlayBuffer,
0,
packOverlayUniforms(matrix, position, size, DEPTH_NUDGE, CASING_PX),
packOverlayUniforms(matrix, position, size, DEPTH_NUDGE, CASING_PX, looking),
);
// Mesh edges sit exactly on the surface creases the construction
// wireframe also traces; a smaller nudge keeps them consistently behind
// coincident construction lines instead of z-fighting into dashes.
device.queue.writeBuffer(
this.meshOverlayBuffer,
0,
packOverlayUniforms(matrix, position, size, DEPTH_NUDGE * 0.4, 0),
packOverlayUniforms(matrix, position, size, DEPTH_NUDGE * 0.4, 0, looking),
);

if (this.display.showGraticule) {
Expand Down
17 changes: 14 additions & 3 deletions frontend/src/viewer/uniforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,35 @@ export function packSceneUniforms(frame: {
}

/**
* The overlay block: the view-projection, the eye, the stroke metrics, and
* the depth nudge and casing that tell construction strokes from mesh edges.
* The overlay block: the view-projection, the eye, the stroke metrics, the
* depth nudge and casing that tell construction strokes from mesh edges, and
* the view direction.
*
* The direction is the camera's forward axis, with `w` 1 under an
* orthographic projection: there every ray is parallel to it, and a surface
* shaded by the vector from the eye *point* instead got a radial gradient
* across a flat plane, brightest where the plane passes nearest the eye.
*/
export function packOverlayUniforms(
matrix: Float32Array,
position: Vec3,
size: FrameSize,
depthNudge: number,
casing: number,
view: { forward: Vec3; orthographic: boolean },
): Float32Array<ArrayBuffer> {
const overlay = new Float32Array(28);
const overlay = new Float32Array(OVERLAY_UNIFORM_FLOATS);
overlay.set(matrix, 0);
overlay.set([position[0], position[1], position[2], 0], 16);
overlay.set([size.width, size.height, LINE_WIDTH_PX, HANDLE_RADIUS_PX], 20);
overlay.set([depthNudge, casing, 0, 0], 24);
overlay.set([view.forward[0], view.forward[1], view.forward[2], view.orthographic ? 1 : 0], 28);
return overlay;
}

/** The overlay block's size: view-projection, then four vec4s. */
export const OVERLAY_UNIFORM_FLOATS = 32;

/**
* The FEM surface block: the view-projection, the clip plane, the field
* range, and the ramp choice with the eye.
Expand Down
16 changes: 13 additions & 3 deletions frontend/test/uniforms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,20 @@ describe("uniform blocks", () => {
});

it("packs the overlay block with its nudge and casing last", () => {
const overlay = packOverlayUniforms(matrix, position, size, 0.5, 0.25);
expect(overlay.byteLength).toBe(112);
const overlay = packOverlayUniforms(matrix, position, size, 0.5, 0.25, {
forward: [0, 0, -1],
orthographic: true,
});
expect(overlay.byteLength).toBe(128);
expect(Array.from(overlay.slice(0, 16))).toEqual(Array.from(matrix));
expect(Array.from(overlay.slice(24))).toEqual([0.5, 0.25, 0, 0]);
expect(Array.from(overlay.slice(24, 28))).toEqual([0.5, 0.25, 0, 0]);
// The view direction last, flagged orthographic: a flat face shades flat.
expect(Array.from(overlay.slice(28))).toEqual([0, 0, -1, 1]);
const perspective = packOverlayUniforms(matrix, position, size, 0.5, 0.25, {
forward: [0, 0, -1],
orthographic: false,
});
expect(perspective[31]).toBe(0);
});

it("packs the simulation block, differing between passes only in params.z", () => {
Expand Down
22 changes: 18 additions & 4 deletions tests/viewer/test_shaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
from cadjoint.viewer._webgpu import build_viewer_shader # noqa: E402

OVERLAY_WGSL = Path(__file__).resolve().parents[2] / "frontend/src/viewer/overlay.wgsl"
#: The overlay uniform block (``struct Overlay``; ``packOverlayUniforms``):
#: the view-projection and four vec4s.
OVERLAY_UNIFORM_BYTES = 128
SIMULATION_WGSL = Path(__file__).resolve().parents[2] / "frontend/src/viewer/simulation.wgsl"
GRATICULE_WGSL = Path(__file__).resolve().parents[2] / "frontend/src/viewer/graticule.wgsl"
# The design tokens the frontend prepends to every module (pipelines.ts
Expand Down Expand Up @@ -265,11 +268,20 @@ def test_overlay_pipelines_match_the_renderer(
"depth_compare": "less-equal",
},
)
overlay_uniforms = device.create_buffer(size=112, usage=wgpu.BufferUsage.UNIFORM)
overlay_uniforms = device.create_buffer(
size=OVERLAY_UNIFORM_BYTES, usage=wgpu.BufferUsage.UNIFORM
)
device.create_bind_group(
layout=pipeline.get_bind_group_layout(0),
entries=[
{"binding": 0, "resource": {"buffer": overlay_uniforms, "offset": 0, "size": 112}}
{
"binding": 0,
"resource": {
"buffer": overlay_uniforms,
"offset": 0,
"size": OVERLAY_UNIFORM_BYTES,
},
}
],
)

Expand Down Expand Up @@ -457,9 +469,11 @@ def overlay_pipeline(entry_vertex, entry_fragment, stride, attributes):
{
"binding": 0,
"resource": {
"buffer": device.create_buffer(size=112, usage=wgpu.BufferUsage.UNIFORM),
"buffer": device.create_buffer(
size=OVERLAY_UNIFORM_BYTES, usage=wgpu.BufferUsage.UNIFORM
),
"offset": 0,
"size": 112,
"size": OVERLAY_UNIFORM_BYTES,
},
}
],
Expand Down
Loading