Skip to content
Open
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
72 changes: 72 additions & 0 deletions examples/scripts/test/clip-dist-zoom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Regression scene for bonds vanishing on extreme zoom with clipDist = 0.
//
// Bonds (and ball+stick sticks) are drawn as cylinder impostors: a 4-triangle
// bounding box that ray-casts the cylinder per pixel. Setting clipDist = 0
// removes the floor on the perspective near-plane (viewer __updateClipping),
// letting the camera get right up to a bond. Before the fix, the impostor box
// corners were pinned to a clip-space depth near the far plane, so corners that
// were still in front of the camera got clipped on zoom - half the bond would
// disappear, then all of it. After the fix they stay visible until the camera
// actually reaches the bond.
//
// How to test: run the scene, then scroll-zoom hard onto a single bond.
// Press 'c' to toggle clipDist between 0 (zoom freely) and 10 (default floor).
// Press 'o' to toggle perspective / orthographic and check both camera modes.
// (In orthographic mode zooming magnifies rather than moving the camera in,
// and gl_Position.w is always 1, so the near-clipping the fix addresses does
// not arise there - the toggle is here to confirm no regression.)

stage.loadFile('data://1crn.pdb').then(function (o) {
o.addRepresentation('licorice', { radius: 0.15 })
o.addRepresentation('ball+stick', { visible: false })
// Focus tightly on a few atoms so a bond fills the view straight away.
o.autoView('1-3')

stage.setParameters({ clipDist: 0 })
})

var textDiv = document.createElement('div')
Object.assign(textDiv.style, {
position: 'absolute',
zIndex: 10,
top: '20px',
left: '20px',
color: 'grey',
fontFamily: 'monospace',
whiteSpace: 'pre',
pointerEvents: 'none'
})
stage.viewer.container.appendChild(textDiv)

function _f (x) {
return (x || x === 0) ? x.toFixed(2) : '-'
}

function updateDiv () {
var sp = stage.getParameters()
var camera = stage.viewer.camera
textDiv.innerHTML = [
'Scroll to zoom hard onto a bond.',
"Press 'c' to toggle clipDist (0 <-> 10).",
"Press 'o' to toggle perspective / orthographic.",
'',
'camera: ' + sp.cameraType,
'clipDist: ' + _f(sp.clipDist),
'camera near: ' + _f(camera.near),
'cDist: ' + _f(stage.viewer.cDist),
'bRadius: ' + _f(stage.viewer.bRadius)
].join('\n')
}
stage.viewer.signals.rendered.add(updateDiv)

document.addEventListener('keydown', function (e) {
if (e.key === 'c') {
var next = stage.getParameters().clipDist === 0 ? 10 : 0
stage.setParameters({ clipDist: next })
} else if (e.key === 'o') {
var mode = stage.getParameters().cameraType === 'perspective'
? 'orthographic'
: 'perspective'
stage.setParameters({ cameraType: mode })
}
})
17 changes: 14 additions & 3 deletions src/shader/CylinderImpostor.vert
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,19 @@ void main(){

gl_Position = projectionMatrix * w;

// avoid clipping (1.0 seems to induce flickering with some drivers)
// Is this required?
gl_Position.z = 0.99;
// Pin the impostor box to a fixed clip-space depth so its faces are never
// z-clipped; the true per-pixel depth is written later via gl_FragDepthEXT.
//
// The value matters: gl_Position.z is a raw clip coordinate while
// gl_Position.w still varies per box corner, and a vertex survives clipping
// only while -w <= z <= w. A value near the far plane (the old 0.99) forces
// w >= 0.99, so as the camera zooms in, box corners that are still well in
// front of it (small positive w) fail the far-plane test and get clipped -
// and because the box is only 4 triangles, losing a corner drops half, then
// all, of the cylinder. Pinning to 0.0 (the centre of the clip range) only
// requires w > 0, so corners are kept as long as they are in front of the
// camera. 0.0 is also the furthest from either clip boundary, avoiding the
// driver flickering that was seen at 1.0.
gl_Position.z = 0.0;

}
11 changes: 9 additions & 2 deletions src/shader/HyperballStickImpostor.vert
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,14 @@ void main(){

matrix_near = mat4( i_near, i_far, focus, e3 );

// avoid clipping
gl_Position.z = 1.0;
// Pin the impostor box to a fixed clip-space depth so its faces are never
// z-clipped; the true per-pixel depth is written later via gl_FragDepthEXT.
// gl_Position.z is a raw clip coordinate while gl_Position.w still varies
// per corner, and a vertex is kept only while -w <= z <= w. The old value
// of 1.0 forced w >= 1.0, so on extreme zoom box corners still in front of
// the camera (small positive w) were clipped, taking whole bonds with them.
// Pinning to 0.0 only requires w > 0, keeping every corner that is in front
// of the camera. See CylinderImpostor.vert for the full explanation.
gl_Position.z = 0.0;

}
Loading