From 4c8edbd50dc60fff6216fe99648507df403195e2 Mon Sep 17 00:00:00 2001 From: Jeremy Maitin-Shepard Date: Thu, 31 Oct 2024 15:16:23 -0700 Subject: [PATCH] Add additional debugging checks and better octree validation --- .../datasource/graphene/backend.ts | 455 +++++++---- .../datasource/graphene/frontend.ts | 1 + src/neuroglancer/mesh/frontend.ts | 722 +++++++++++------- src/neuroglancer/mesh/multiscale.ts | 263 ++++--- src/neuroglancer/object_picking.ts | 31 +- src/neuroglancer/util/geom.ts | 234 ++++-- 6 files changed, 1090 insertions(+), 616 deletions(-) diff --git a/src/neuroglancer/datasource/graphene/backend.ts b/src/neuroglancer/datasource/graphene/backend.ts index 4f84fa977..88319d0af 100644 --- a/src/neuroglancer/datasource/graphene/backend.ts +++ b/src/neuroglancer/datasource/graphene/backend.ts @@ -14,105 +14,172 @@ * limitations under the License. */ -import {WithParameters} from 'neuroglancer/chunk_manager/backend'; -import {WithSharedCredentialsProviderCounterpart} from 'neuroglancer/credentials_provider/shared_counterpart'; -import {assignMeshFragmentData, assignMultiscaleMeshFragmentData, FragmentChunk, FragmentId, ManifestChunk, MeshSource, MultiscaleFragmentChunk, MultiscaleManifestChunk, MultiscaleMeshSource} from 'neuroglancer/mesh/backend'; -import {getGrapheneFragmentKey, MultiscaleMeshSourceParameters, responseIdentity} from 'neuroglancer/datasource/graphene/base'; -import {CancellationToken} from 'neuroglancer/util/cancellation'; -import {isNotFoundError, responseArrayBuffer, responseJson} from 'neuroglancer/util/http_request'; -import {cancellableFetchSpecialOk, SpecialProtocolCredentials, SpecialProtocolCredentialsProvider} from 'neuroglancer/util/special_protocol_request'; -import {Uint64} from 'neuroglancer/util/uint64'; -import {registerSharedObject} from 'neuroglancer/worker_rpc'; -import {ChunkedGraphSourceParameters, MeshSourceParameters} from 'neuroglancer/datasource/graphene/base'; -import {decodeManifestChunk} from 'neuroglancer/datasource/precomputed/backend'; -import {fetchSpecialHttpByteRange} from 'neuroglancer/util/byte_range_http_requests'; -import debounce from 'lodash/debounce'; -import {withChunkManager, Chunk, ChunkSource} from 'neuroglancer/chunk_manager/backend'; -import {ChunkPriorityTier, ChunkState} from 'neuroglancer/chunk_manager/base'; -import {TransformedSource, forEachPlaneIntersectingVolumetricChunk, getNormalizedChunkLayout, SliceViewProjectionParameters} from 'neuroglancer/sliceview/base'; -import {CHUNKED_GRAPH_LAYER_RPC_ID, ChunkedGraphChunkSpecification, CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID, RENDER_RATIO_LIMIT} from 'neuroglancer/datasource/graphene/base'; -import {Uint64Set} from 'neuroglancer/uint64_set'; -import {vec3, vec3Key} from 'neuroglancer/util/geom'; -import {registerRPC, RPC} from 'neuroglancer/worker_rpc'; - -import { deserializeTransformedSources, SliceViewChunkSourceBackend } from 'neuroglancer/sliceview/backend'; -import { getBasePriority, getPriorityTier, withSharedVisibility } from 'neuroglancer/visibility_priority/backend'; -import {isBaseSegmentId} from 'neuroglancer/datasource/graphene/base'; -import { withSegmentationLayerBackendState } from 'neuroglancer/segmentation_display_state/backend'; -import { RenderedViewBackend, RenderLayerBackend, RenderLayerBackendAttachment } from 'neuroglancer/render_layer_backend'; -import { SharedWatchableValue } from 'neuroglancer/shared_watchable_value'; -import { DisplayDimensionRenderInfo } from 'neuroglancer/navigation_state'; -import { forEachVisibleSegment } from 'neuroglancer/segmentation_display_state/base'; -import { computeChunkBounds } from 'neuroglancer/sliceview/volume/backend'; -import { verifyObject } from 'neuroglancer/util/json'; +import { WithParameters } from "neuroglancer/chunk_manager/backend"; +import { WithSharedCredentialsProviderCounterpart } from "neuroglancer/credentials_provider/shared_counterpart"; +import { + assignMeshFragmentData, + assignMultiscaleMeshFragmentData, + FragmentChunk, + FragmentId, + ManifestChunk, + MeshSource, + MultiscaleFragmentChunk, + MultiscaleManifestChunk, + MultiscaleMeshSource, +} from "neuroglancer/mesh/backend"; +import { + getGrapheneFragmentKey, + MultiscaleMeshSourceParameters, + responseIdentity, +} from "neuroglancer/datasource/graphene/base"; +import { CancellationToken } from "neuroglancer/util/cancellation"; +import { isNotFoundError, responseArrayBuffer, responseJson } from "neuroglancer/util/http_request"; +import { + cancellableFetchSpecialOk, + SpecialProtocolCredentials, + SpecialProtocolCredentialsProvider, +} from "neuroglancer/util/special_protocol_request"; +import { Uint64 } from "neuroglancer/util/uint64"; +import { registerSharedObject } from "neuroglancer/worker_rpc"; +import { + ChunkedGraphSourceParameters, + MeshSourceParameters, +} from "neuroglancer/datasource/graphene/base"; +import { decodeManifestChunk } from "neuroglancer/datasource/precomputed/backend"; +import { fetchSpecialHttpByteRange } from "neuroglancer/util/byte_range_http_requests"; +import debounce from "lodash/debounce"; +import { withChunkManager, Chunk, ChunkSource } from "neuroglancer/chunk_manager/backend"; +import { ChunkPriorityTier, ChunkState } from "neuroglancer/chunk_manager/base"; +import { + TransformedSource, + forEachPlaneIntersectingVolumetricChunk, + getNormalizedChunkLayout, + SliceViewProjectionParameters, +} from "neuroglancer/sliceview/base"; +import { + CHUNKED_GRAPH_LAYER_RPC_ID, + ChunkedGraphChunkSpecification, + CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID, + RENDER_RATIO_LIMIT, +} from "neuroglancer/datasource/graphene/base"; +import { Uint64Set } from "neuroglancer/uint64_set"; +import { vec3, vec3Key } from "neuroglancer/util/geom"; +import { registerRPC, RPC } from "neuroglancer/worker_rpc"; + +import { + deserializeTransformedSources, + SliceViewChunkSourceBackend, +} from "neuroglancer/sliceview/backend"; +import { + getBasePriority, + getPriorityTier, + withSharedVisibility, +} from "neuroglancer/visibility_priority/backend"; +import { isBaseSegmentId } from "neuroglancer/datasource/graphene/base"; +import { withSegmentationLayerBackendState } from "neuroglancer/segmentation_display_state/backend"; +import { + RenderedViewBackend, + RenderLayerBackend, + RenderLayerBackendAttachment, +} from "neuroglancer/render_layer_backend"; +import { SharedWatchableValue } from "neuroglancer/shared_watchable_value"; +import { DisplayDimensionRenderInfo } from "neuroglancer/navigation_state"; +import { forEachVisibleSegment } from "neuroglancer/segmentation_display_state/base"; +import { computeChunkBounds } from "neuroglancer/sliceview/volume/backend"; +import { verifyObject } from "neuroglancer/util/json"; function getVerifiedFragmentPromise( - credentialsProvider: SpecialProtocolCredentialsProvider, - fragmentId: string|null, - parameters: MeshSourceParameters|MultiscaleMeshSourceParameters, - cancellationToken: CancellationToken) { - if (fragmentId && fragmentId.charAt(0) === '~') { - let parts = fragmentId.substr(1).split(':'); - let startOffset: Uint64|number, endOffset: Uint64|number; + credentialsProvider: SpecialProtocolCredentialsProvider, + fragmentId: string | null, + parameters: MeshSourceParameters | MultiscaleMeshSourceParameters, + cancellationToken: CancellationToken, +) { + if (fragmentId && fragmentId.charAt(0) === "~") { + let parts = fragmentId.substr(1).split(":"); + let startOffset: Uint64 | number, endOffset: Uint64 | number; startOffset = Number(parts[1]); - endOffset = startOffset+Number(parts[2]); - return fetchSpecialHttpByteRange(credentialsProvider, + endOffset = startOffset + Number(parts[2]); + return fetchSpecialHttpByteRange( + credentialsProvider, `${parameters.fragmentUrl}/initial/${parts[0]}`, startOffset, endOffset, - cancellationToken + cancellationToken, ); } return cancellableFetchSpecialOk( credentialsProvider, - `${parameters.fragmentUrl}/dynamic/${fragmentId}`, {}, responseArrayBuffer, - cancellationToken); + `${parameters.fragmentUrl}/dynamic/${fragmentId}`, + {}, + responseArrayBuffer, + cancellationToken, + ); } function getFragmentDownloadPromise( - credentialsProvider: SpecialProtocolCredentialsProvider, - fragmentId: string|null, - parameters: MeshSourceParameters|MultiscaleMeshSourceParameters, - cancellationToken: CancellationToken) { + credentialsProvider: SpecialProtocolCredentialsProvider, + fragmentId: string | null, + parameters: MeshSourceParameters | MultiscaleMeshSourceParameters, + cancellationToken: CancellationToken, +) { let fragmentDownloadPromise; - if (parameters.sharding){ - fragmentDownloadPromise = getVerifiedFragmentPromise(credentialsProvider, fragmentId, parameters, cancellationToken); + if (parameters.sharding) { + fragmentDownloadPromise = getVerifiedFragmentPromise( + credentialsProvider, + fragmentId, + parameters, + cancellationToken, + ); } else { fragmentDownloadPromise = cancellableFetchSpecialOk( credentialsProvider, - `${parameters.fragmentUrl}/${fragmentId}`, {}, responseArrayBuffer, - cancellationToken); + `${parameters.fragmentUrl}/${fragmentId}`, + {}, + responseArrayBuffer, + cancellationToken, + ); } return fragmentDownloadPromise; } -async function decodeDracoFragmentChunk( - chunk: FragmentChunk, response: ArrayBuffer) { - const m = await import(/* webpackChunkName: "draco" */ 'neuroglancer/mesh/draco'); +async function decodeDracoFragmentChunk(chunk: FragmentChunk, response: ArrayBuffer) { + const m = await import(/* webpackChunkName: "draco" */ "neuroglancer/mesh/draco"); const rawMesh = await m.decodeDraco(new Uint8Array(response)); assignMeshFragmentData(chunk, rawMesh); } -@registerSharedObject() export class GrapheneMeshSource extends -(WithParameters(WithSharedCredentialsProviderCounterpart()(MeshSource), MeshSourceParameters)) { +@registerSharedObject() +export class GrapheneMeshSource extends WithParameters( + WithSharedCredentialsProviderCounterpart()(MeshSource), + MeshSourceParameters, +) { async download(chunk: ManifestChunk, cancellationToken: CancellationToken) { - const {parameters} = this; + const { parameters } = this; if (isBaseSegmentId(chunk.objectId, parameters.nBitsForLayerId)) { - return decodeManifestChunk(chunk, {fragments: []}); + return decodeManifestChunk(chunk, { fragments: [] }); } let url = `${parameters.manifestUrl}/manifest`; let manifestUrl = `${url}/${chunk.objectId}:${parameters.lod}?verify=1&prepend_seg_ids=1`; - await cancellableFetchSpecialOk(this.credentialsProvider, manifestUrl, {}, responseJson, cancellationToken) - .then(response => decodeManifestChunk(chunk, response)); + await cancellableFetchSpecialOk( + this.credentialsProvider, + manifestUrl, + {}, + responseJson, + cancellationToken, + ).then((response) => decodeManifestChunk(chunk, response)); } async downloadFragment(chunk: FragmentChunk, cancellationToken: CancellationToken) { - const {parameters} = this; + const { parameters } = this; try { const response = await getFragmentDownloadPromise( - undefined, chunk.fragmentId, parameters, cancellationToken); + undefined, + chunk.fragmentId, + parameters, + cancellationToken, + ); await decodeDracoFragmentChunk(chunk, response); } catch (e) { if (isNotFoundError(e)) { @@ -122,7 +189,7 @@ async function decodeDracoFragmentChunk( } } - getFragmentKey(objectKey: string|null, fragmentId: string) { + getFragmentKey(objectKey: string | null, fragmentId: string) { objectKey; return getGrapheneFragmentKey(fragmentId); } @@ -134,7 +201,7 @@ interface ShardInfo { } interface GrapheneMultiscaleManifestChunk extends MultiscaleManifestChunk { - fragmentIds: FragmentId[]|null; + fragmentIds: FragmentId[] | null; shardInfo?: ShardInfo; } @@ -148,51 +215,97 @@ function decodeMultiscaleManifestChunk(chunk: GrapheneMultiscaleManifestChunk, r vertexOffsets: new Float32Array(response.lodScales.length * 3), clipLowerBound: vec3.clone(response.clipLowerBound), clipUpperBound: vec3.clone(response.clipUpperBound), - } + }; + console.log("original chunkShape", chunk.manifest.chunkShape.join()); + chunk.manifest.chunkShape[0] /= 8; + chunk.manifest.chunkShape[1] /= 8; + chunk.manifest.chunkShape[2] /= 40; chunk.fragmentIds = response.fragments; chunk.manifest.clipLowerBound.fill(0); - chunk.manifest.clipUpperBound.fill(100000); - chunk.manifest.octree[5*(response.fragments.length-1) + 4] &= 0x7FFFFFFF; - chunk.manifest.octree[5*(response.fragments.length-1) + 3] |= 0x80000000; + chunk.manifest.clipUpperBound.fill(10000000); + chunk.manifest.octree[5 * (response.fragments.length - 1) + 4] &= 0x7fffffff; + chunk.manifest.octree[5 * (response.fragments.length - 1) + 3] |= 0x80000000; } async function decodeMultiscaleFragmentChunk( - chunk: MultiscaleFragmentChunk, response: ArrayBuffer) { - const {lod} = chunk; + chunk: MultiscaleFragmentChunk, + response: ArrayBuffer, +) { + const { lod } = chunk; const source = chunk.manifestChunk!.source! as GrapheneMultiscaleMeshSource; - const m = await import(/* webpackChunkName: "draco" */ 'neuroglancer/mesh/draco'); + const m = await import(/* webpackChunkName: "draco" */ "neuroglancer/mesh/draco"); const rawMesh = await m.decodeDracoPartitioned(new Uint8Array(response), 0, lod !== 0, false); + rawMesh.vertexPositions = new Float32Array( + rawMesh.vertexPositions.buffer, + rawMesh.vertexPositions.byteOffset, + rawMesh.vertexPositions.length, + ); assignMultiscaleMeshFragmentData(chunk, rawMesh, source.format.vertexPositionFormat); + const manifest = chunk.manifestChunk!.manifest!; + const minVertex = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], + maxVertex = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY]; + const vertexPositions = chunk.meshData!.vertexPositions; + for (let i = 0; i < vertexPositions.length; ++i) { + const v = (vertexPositions as Float32Array)[i]; + minVertex[i % 3] = Math.min(minVertex[i % 3], v); + maxVertex[i % 3] = Math.max(maxVertex[i % 3], v); + } + const { chunkShape } = manifest; + const expectedMin = [0, 0, 0], + expectedMax = [0, 0, 0]; + const row = chunk.chunkIndex; + for (let i = 0; i < 3; ++i) { + const size = chunkShape[i] * 2 ** lod; + expectedMin[i] = size * manifest.octree[row * 5 + i] + manifest.chunkGridSpatialOrigin[i]; + expectedMax[i] = expectedMin[i] + size; + } + console.log( + `lod=${lod} cell ${manifest.octree.slice(row * 5, row * 5 + 3).join(",")}: chunk shape=${chunkShape.join(",")} actual min=${minVertex}, max=${maxVertex}, expeted min=${expectedMin}, max=${expectedMax}`, + ); } - @registerSharedObject() -export class GrapheneMultiscaleMeshSource extends -(WithParameters(WithSharedCredentialsProviderCounterpart()(MultiscaleMeshSource), MultiscaleMeshSourceParameters)) { - async download(chunk: GrapheneMultiscaleManifestChunk, cancellationToken: CancellationToken): - Promise { - const {parameters} = this; +export class GrapheneMultiscaleMeshSource extends WithParameters( + WithSharedCredentialsProviderCounterpart()(MultiscaleMeshSource), + MultiscaleMeshSourceParameters, +) { + async download( + chunk: GrapheneMultiscaleManifestChunk, + cancellationToken: CancellationToken, + ): Promise { + const { parameters } = this; let url = `${parameters.manifestUrl}/manifest/multiscale`; let manifestUrl = `${url}/${chunk.objectId}?verify=1&prepend_seg_ids=1`; - await cancellableFetchSpecialOk(this.credentialsProvider, manifestUrl, {}, responseJson, cancellationToken) - .then(response => decodeMultiscaleManifestChunk(chunk, response)); + await cancellableFetchSpecialOk( + this.credentialsProvider, + manifestUrl, + {}, + responseJson, + cancellationToken, + ).then((response) => decodeMultiscaleManifestChunk(chunk, response)); } async downloadFragment( - chunk: MultiscaleFragmentChunk, cancellationToken: CancellationToken): Promise { - const {parameters} = this; + chunk: MultiscaleFragmentChunk, + cancellationToken: CancellationToken, + ): Promise { + const { parameters } = this; const manifestChunk = chunk.manifestChunk! as GrapheneMultiscaleManifestChunk; const chunkIndex = chunk.chunkIndex; - const {fragmentIds} = manifestChunk; + const { fragmentIds } = manifestChunk; try { let fragmentId = null; - if (fragmentIds !== null){ + if (fragmentIds !== null) { fragmentId = fragmentIds[chunkIndex]; - fragmentId = fragmentId.substring(fragmentId.indexOf(':') + 1) + fragmentId = fragmentId.substring(fragmentId.indexOf(":") + 1); } const response = await getFragmentDownloadPromise( - undefined, fragmentId, parameters, cancellationToken); + undefined, + fragmentId, + parameters, + cancellationToken, + ); await decodeMultiscaleFragmentChunk(chunk, response); } catch (e) { if (isNotFoundError(e)) { @@ -202,7 +315,7 @@ export class GrapheneMultiscaleMeshSource extends } } - getFragmentKey(objectKey: string|null, fragmentId: string) { + getFragmentKey(objectKey: string | null, fragmentId: string) { objectKey; return getGrapheneFragmentKey(fragmentId); } @@ -211,10 +324,10 @@ export class GrapheneMultiscaleMeshSource extends export class ChunkedGraphChunk extends Chunk { backendOnly = true; chunkGridPosition: Float32Array; - source: GrapheneChunkedGraphChunkSource|null = null; + source: GrapheneChunkedGraphChunkSource | null = null; segment: Uint64; leaves: Uint64[] = []; - chunkDataSize: Uint32Array|null; + chunkDataSize: Uint32Array | null; initializeVolumeChunk(key: string, chunkGridPosition: Float32Array) { super.initialize(key); @@ -252,8 +365,11 @@ function decodeChunkedGraphChunk(leaves: string[]) { return final; } -@registerSharedObject() export class GrapheneChunkedGraphChunkSource extends -(WithParameters(WithSharedCredentialsProviderCounterpart()(ChunkSource), ChunkedGraphSourceParameters)) { +@registerSharedObject() +export class GrapheneChunkedGraphChunkSource extends WithParameters( + WithSharedCredentialsProviderCounterpart()(ChunkSource), + ChunkedGraphSourceParameters, +) { spec: ChunkedGraphChunkSpecification; chunks: Map; tempChunkDataSize: Uint32Array; @@ -268,23 +384,30 @@ function decodeChunkedGraphChunk(leaves: string[]) { } async download(chunk: ChunkedGraphChunk, cancellationToken: CancellationToken): Promise { - let {parameters} = this; + let { parameters } = this; let chunkPosition = this.computeChunkBounds(chunk); let chunkDataSize = chunk.chunkDataSize!; - let bounds = `${chunkPosition[0]}-${chunkPosition[0] + chunkDataSize[0]}_` + - `${chunkPosition[1]}-${chunkPosition[1] + chunkDataSize[1]}_` + - `${chunkPosition[2]}-${chunkPosition[2] + chunkDataSize[2]}`; - - const request = cancellableFetchSpecialOk(this.credentialsProvider, - `${parameters.url}/${chunk.segment}/leaves?int64_as_str=1&bounds=${bounds}`, {}, responseIdentity, - cancellationToken); + let bounds = + `${chunkPosition[0]}-${chunkPosition[0] + chunkDataSize[0]}_` + + `${chunkPosition[1]}-${chunkPosition[1] + chunkDataSize[1]}_` + + `${chunkPosition[2]}-${chunkPosition[2] + chunkDataSize[2]}`; + + const request = cancellableFetchSpecialOk( + this.credentialsProvider, + `${parameters.url}/${chunk.segment}/leaves?int64_as_str=1&bounds=${bounds}`, + {}, + responseIdentity, + cancellationToken, + ); await this.withErrorMessage( - request, `Fetching leaves of segment ${chunk.segment} in region ${bounds}: `) - .then(res => res.json()) - .then(res => { - chunk.leaves = decodeChunkedGraphChunk(res['leaf_ids']) + request, + `Fetching leaves of segment ${chunk.segment} in region ${bounds}: `, + ) + .then((res) => res.json()) + .then((res) => { + chunk.leaves = decodeChunkedGraphChunk(res["leaf_ids"]); }) - .catch(err => console.error(err)); + .catch((err) => console.error(err)); } getChunk(chunkGridPosition: Float32Array, segment: Uint64) { @@ -310,7 +433,7 @@ function decodeChunkedGraphChunk(leaves: string[]) { } else { let msg: string; try { - msg = (await response.json())['message']; + msg = (await response.json())["message"]; } catch { msg = await response.text(); } @@ -321,8 +444,7 @@ function decodeChunkedGraphChunk(leaves: string[]) { interface ChunkedGraphRenderLayerAttachmentState { displayDimensionRenderInfo: DisplayDimensionRenderInfo; - transformedSource?: TransformedSource< - ChunkedGraphLayer, GrapheneChunkedGraphChunkSource>; + transformedSource?: TransformedSource; } const tempChunkPosition = vec3.create(); @@ -330,8 +452,9 @@ const tempCenter = vec3.create(); const tempChunkSize = vec3.create(); @registerSharedObject(CHUNKED_GRAPH_LAYER_RPC_ID) -export class ChunkedGraphLayer extends withSegmentationLayerBackendState -(withSharedVisibility(withChunkManager(RenderLayerBackend))) { +export class ChunkedGraphLayer extends withSegmentationLayerBackendState( + withSharedVisibility(withChunkManager(RenderLayerBackend)), +) { source: GrapheneChunkedGraphChunkSource; localPosition: SharedWatchableValue; leafRequestsActive: SharedWatchableValue; @@ -339,23 +462,33 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState constructor(rpc: RPC, options: any) { super(rpc, options); - this.source = this.registerDisposer(rpc.getRef(options['source'])); + this.source = this.registerDisposer( + rpc.getRef(options["source"]), + ); this.localPosition = rpc.get(options.localPosition); this.leafRequestsActive = rpc.get(options.leafRequestsActive); this.nBitsForLayerId = rpc.get(options.nBitsForLayerId); - this.registerDisposer(this.chunkManager.recomputeChunkPriorities.add(() => { - this.updateChunkPriorities(); - this.debouncedupdateDisplayState(); - })); + this.registerDisposer( + this.chunkManager.recomputeChunkPriorities.add(() => { + this.updateChunkPriorities(); + this.debouncedupdateDisplayState(); + }), + ); } - attach(attachment: RenderLayerBackendAttachment): void { + attach( + attachment: RenderLayerBackendAttachment< + RenderedViewBackend, + ChunkedGraphRenderLayerAttachmentState + >, + ): void { const scheduleUpdateChunkPriorities = () => this.chunkManager.scheduleUpdateChunkPriorities(); - const {view} = attachment; + const { view } = attachment; attachment.registerDisposer(scheduleUpdateChunkPriorities); attachment.registerDisposer( - view.projectionParameters.changed.add(scheduleUpdateChunkPriorities)); + view.projectionParameters.changed.add(scheduleUpdateChunkPriorities), + ); attachment.registerDisposer(view.visibility.changed.add(scheduleUpdateChunkPriorities)); attachment.state = { displayDimensionRenderInfo: view.projectionParameters.value.displayDimensionRenderInfo, @@ -369,17 +502,17 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState } private updateChunkPriorities() { - const {source, chunkManager} = this; + const { source, chunkManager } = this; chunkManager.registerLayer(this); for (const attachment of this.attachments.values()) { - const {view} = attachment; + const { view } = attachment; const visibility = view.visibility.value; if (visibility === Number.NEGATIVE_INFINITY) { continue; } const attachmentState = attachment.state! as ChunkedGraphRenderLayerAttachmentState; - const {transformedSource: tsource} = attachmentState; + const { transformedSource: tsource } = attachmentState; const projectionParameters = view.projectionParameters.value as SliceViewProjectionParameters; if (!tsource) { @@ -388,7 +521,8 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState const pixelSize = projectionParameters.pixelSize * 1.1; const smallestVoxelSize = tsource.effectiveVoxelSize; - this.leafRequestsActive.value = this.renderRatioLimit >= pixelSize / Math.min(...smallestVoxelSize); + this.leafRequestsActive.value = + this.renderRatioLimit >= pixelSize / Math.min(...smallestVoxelSize); if (!this.leafRequestsActive.value) { continue; } @@ -396,8 +530,8 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState const priorityTier = getPriorityTier(visibility); const basePriority = getBasePriority(visibility); - const {chunkLayout} = tsource; - const {size, finiteRank} = chunkLayout; + const { chunkLayout } = tsource; + const { size, finiteRank } = chunkLayout; const chunkSize = tempChunkSize; const localCenter = tempCenter; @@ -406,42 +540,48 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState chunkSize[i] = 0; localCenter[i] = 0; } - const {centerDataPosition} = projectionParameters; + const { centerDataPosition } = projectionParameters; chunkLayout.globalToLocalSpatial(localCenter, centerDataPosition); forEachPlaneIntersectingVolumetricChunk( - projectionParameters, this.localPosition.value, tsource, + projectionParameters, + this.localPosition.value, + tsource, getNormalizedChunkLayout(projectionParameters, chunkLayout), - positionInChunks => { - vec3.multiply(tempChunkPosition, positionInChunks, chunkSize); - const priority = -vec3.distance(localCenter, tempChunkPosition); - const {curPositionInChunks} = tsource; - - forEachVisibleSegment(this, (segment, _) => { - if (isBaseSegmentId(segment, this.nBitsForLayerId.value)) return; // TODO maybe support highBitRepresentation? - const chunk = source.getChunk(curPositionInChunks, segment.clone()); - chunkManager.requestChunk(chunk, priorityTier, basePriority + priority); - ++this.numVisibleChunksNeeded; - if (chunk.state === ChunkState.GPU_MEMORY) { - ++this.numVisibleChunksAvailable; - } - }); - }); + (positionInChunks) => { + vec3.multiply(tempChunkPosition, positionInChunks, chunkSize); + const priority = -vec3.distance(localCenter, tempChunkPosition); + const { curPositionInChunks } = tsource; + + forEachVisibleSegment(this, (segment, _) => { + if (isBaseSegmentId(segment, this.nBitsForLayerId.value)) return; // TODO maybe support highBitRepresentation? + const chunk = source.getChunk(curPositionInChunks, segment.clone()); + chunkManager.requestChunk(chunk, priorityTier, basePriority + priority); + ++this.numVisibleChunksNeeded; + if (chunk.state === ChunkState.GPU_MEMORY) { + ++this.numVisibleChunksAvailable; + } + }); + }, + ); } } private forEachSelectedRootWithLeaves( - callback: (rootObjectKey: string, leaves: Uint64[]) => void) { - const {source} = this; - - for (const chunk of source.chunks.values()) { - if (chunk.state === ChunkState.SYSTEM_MEMORY_WORKER && - chunk.priorityTier < ChunkPriorityTier.RECENT) { - if (this.visibleSegments.has(chunk.segment) && chunk.leaves.length) { - callback(chunk.segment.toString(), chunk.leaves); - } + callback: (rootObjectKey: string, leaves: Uint64[]) => void, + ) { + const { source } = this; + + for (const chunk of source.chunks.values()) { + if ( + chunk.state === ChunkState.SYSTEM_MEMORY_WORKER && + chunk.priorityTier < ChunkPriorityTier.RECENT + ) { + if (this.visibleSegments.has(chunk.segment) && chunk.leaves.length) { + callback(chunk.segment.toString(), chunk.leaves); } } + } } private debouncedupdateDisplayState = debounce(() => { @@ -479,7 +619,7 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState this.segmentEquivalences.delete([...this.segmentEquivalences.setElements(Uint64.parseString(root))].filter(x => !leaves.has(x) && !this.visibleSegments.has(x))); }*/ - const filteredLeaves = [...leaves].filter(x => !this.segmentEquivalences.has(x)); + const filteredLeaves = [...leaves].filter((x) => !this.segmentEquivalences.has(x)); const rootInt = Uint64.parseString(root); @@ -490,15 +630,20 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState } } -registerRPC(CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID, function(x) { +registerRPC(CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID, function (x) { const view = this.get(x.view) as RenderedViewBackend; const layer = this.get(x.layer) as ChunkedGraphLayer; - const attachment = layer.attachments.get(view)! as - RenderLayerBackendAttachment; + const attachment = layer.attachments.get(view)! as RenderLayerBackendAttachment< + RenderedViewBackend, + ChunkedGraphRenderLayerAttachmentState + >; attachment.state!.transformedSource = deserializeTransformedSources< - SliceViewChunkSourceBackend, ChunkedGraphLayer>( - this, x.sources, layer)[0][0] as unknown as TransformedSource< - ChunkedGraphLayer, GrapheneChunkedGraphChunkSource>; + SliceViewChunkSourceBackend, + ChunkedGraphLayer + >(this, x.sources, layer)[0][0] as unknown as TransformedSource< + ChunkedGraphLayer, + GrapheneChunkedGraphChunkSource + >; attachment.state!.displayDimensionRenderInfo = x.displayDimensionRenderInfo; layer.chunkManager.scheduleUpdateChunkPriorities(); }); diff --git a/src/neuroglancer/datasource/graphene/frontend.ts b/src/neuroglancer/datasource/graphene/frontend.ts index 408ece3ce..db4c16d99 100644 --- a/src/neuroglancer/datasource/graphene/frontend.ts +++ b/src/neuroglancer/datasource/graphene/frontend.ts @@ -342,6 +342,7 @@ async function getMeshSource( sharding: metadata.sharding, nBitsForLayerId: nBitsForLayerId, }; + console.log('transform', metadata.transform); return { source: chunkManager.getChunkSource(GrapheneMultiscaleMeshSource, { credentialsProvider, diff --git a/src/neuroglancer/mesh/frontend.ts b/src/neuroglancer/mesh/frontend.ts index 8595e9c3f..0349fcf5e 100644 --- a/src/neuroglancer/mesh/frontend.ts +++ b/src/neuroglancer/mesh/frontend.ts @@ -14,26 +14,62 @@ * limitations under the License. */ -import {ChunkState} from 'neuroglancer/chunk_manager/base'; -import {Chunk, ChunkManager, ChunkSource} from 'neuroglancer/chunk_manager/frontend'; -import {VisibleLayerInfo} from 'neuroglancer/layer'; -import {EncodedMeshData, FRAGMENT_SOURCE_RPC_ID, MESH_LAYER_RPC_ID, MULTISCALE_FRAGMENT_SOURCE_RPC_ID, MULTISCALE_MESH_LAYER_RPC_ID, MultiscaleFragmentFormat, VertexPositionFormat} from 'neuroglancer/mesh/base'; -import {getMultiscaleChunksToDraw, getMultiscaleFragmentKey, MultiscaleMeshManifest, validateOctree} from 'neuroglancer/mesh/multiscale'; -import {PerspectivePanel} from 'neuroglancer/perspective_view/panel'; -import {PerspectiveViewReadyRenderContext, PerspectiveViewRenderContext, PerspectiveViewRenderLayer} from 'neuroglancer/perspective_view/render_layer'; -import {ThreeDimensionalRenderLayerAttachmentState, update3dRenderLayerAttachment} from 'neuroglancer/renderlayer'; -import {forEachVisibleSegment, getObjectKey} from 'neuroglancer/segmentation_display_state/base'; -import {forEachVisibleSegmentToDraw, registerRedrawWhenSegmentationDisplayState3DChanged, SegmentationDisplayState3D, SegmentationLayerSharedObject} from 'neuroglancer/segmentation_display_state/frontend'; -import {makeCachedDerivedWatchableValue, WatchableValueInterface} from 'neuroglancer/trackable_value'; -import {Borrowed, RefCounted} from 'neuroglancer/util/disposable'; -import {getFrustrumPlanes, mat3, mat3FromMat4, mat4, scaleMat3Output, vec3, vec4} from 'neuroglancer/util/geom'; -import * as matrix from 'neuroglancer/util/matrix'; -import {Uint64} from 'neuroglancer/util/uint64'; -import {Buffer} from 'neuroglancer/webgl/buffer'; -import {GL} from 'neuroglancer/webgl/context'; -import {parameterizedEmitterDependentShaderGetter} from 'neuroglancer/webgl/dynamic_shader'; -import {ShaderBuilder, ShaderProgram} from 'neuroglancer/webgl/shader'; -import {registerSharedObjectOwner, RPC} from 'neuroglancer/worker_rpc'; +import { ChunkState } from "neuroglancer/chunk_manager/base"; +import { Chunk, ChunkManager, ChunkSource } from "neuroglancer/chunk_manager/frontend"; +import { VisibleLayerInfo } from "neuroglancer/layer"; +import { + EncodedMeshData, + FRAGMENT_SOURCE_RPC_ID, + MESH_LAYER_RPC_ID, + MULTISCALE_FRAGMENT_SOURCE_RPC_ID, + MULTISCALE_MESH_LAYER_RPC_ID, + MultiscaleFragmentFormat, + VertexPositionFormat, +} from "neuroglancer/mesh/base"; +import { + getMultiscaleChunksToDraw, + getMultiscaleFragmentKey, + MultiscaleMeshManifest, + validateOctree, +} from "neuroglancer/mesh/multiscale"; +import { PerspectivePanel } from "neuroglancer/perspective_view/panel"; +import { + PerspectiveViewReadyRenderContext, + PerspectiveViewRenderContext, + PerspectiveViewRenderLayer, +} from "neuroglancer/perspective_view/render_layer"; +import { + ThreeDimensionalRenderLayerAttachmentState, + update3dRenderLayerAttachment, +} from "neuroglancer/renderlayer"; +import { forEachVisibleSegment, getObjectKey } from "neuroglancer/segmentation_display_state/base"; +import { + forEachVisibleSegmentToDraw, + registerRedrawWhenSegmentationDisplayState3DChanged, + SegmentationDisplayState3D, + SegmentationLayerSharedObject, +} from "neuroglancer/segmentation_display_state/frontend"; +import { + makeCachedDerivedWatchableValue, + WatchableValueInterface, +} from "neuroglancer/trackable_value"; +import { Borrowed, RefCounted } from "neuroglancer/util/disposable"; +import { + getFrustrumPlanes, + mat3, + mat3FromMat4, + mat4, + scaleMat3Output, + vec3, + vec4, +} from "neuroglancer/util/geom"; +import * as matrix from "neuroglancer/util/matrix"; +import { Uint64 } from "neuroglancer/util/uint64"; +import { Buffer } from "neuroglancer/webgl/buffer"; +import { GL } from "neuroglancer/webgl/context"; +import { parameterizedEmitterDependentShaderGetter } from "neuroglancer/webgl/dynamic_shader"; +import { ShaderBuilder, ShaderProgram } from "neuroglancer/webgl/shader"; +import { registerSharedObjectOwner, RPC } from "neuroglancer/worker_rpc"; const tempMat4 = mat4.create(); const tempMat3 = mat3.create(); @@ -41,18 +77,30 @@ const tempMat3 = mat3.create(); // To validate the octrees and to determine the multiscale fragment responsible for each framebuffer // location, set `DEBUG_MULTISCALE_FRAGMENTS=true` and also set `DEBUG_PICKING=true` in // `src/neuroglancer/object_picking.ts`. -const DEBUG_MULTISCALE_FRAGMENTS = false; - -function copyMeshDataToGpu(gl: GL, chunk: FragmentChunk|MultiscaleFragmentChunk) { - chunk.vertexBuffer = - Buffer.fromData(gl, chunk.meshData.vertexPositions, gl.ARRAY_BUFFER, gl.STATIC_DRAW); - chunk.indexBuffer = - Buffer.fromData(gl, chunk.meshData.indices, gl.ELEMENT_ARRAY_BUFFER, gl.STATIC_DRAW); - chunk.normalBuffer = - Buffer.fromData(gl, chunk.meshData.vertexNormals, gl.ARRAY_BUFFER, gl.STATIC_DRAW); +const DEBUG_MULTISCALE_FRAGMENTS = true; + +function copyMeshDataToGpu(gl: GL, chunk: FragmentChunk | MultiscaleFragmentChunk) { + chunk.vertexBuffer = Buffer.fromData( + gl, + chunk.meshData.vertexPositions, + gl.ARRAY_BUFFER, + gl.STATIC_DRAW, + ); + chunk.indexBuffer = Buffer.fromData( + gl, + chunk.meshData.indices, + gl.ELEMENT_ARRAY_BUFFER, + gl.STATIC_DRAW, + ); + chunk.normalBuffer = Buffer.fromData( + gl, + chunk.meshData.vertexNormals, + gl.ARRAY_BUFFER, + gl.STATIC_DRAW, + ); } -function freeGpuMeshData(chunk: FragmentChunk|MultiscaleFragmentChunk) { +function freeGpuMeshData(chunk: FragmentChunk | MultiscaleFragmentChunk) { chunk.vertexBuffer.dispose(); chunk.indexBuffer.dispose(); chunk.normalBuffer.dispose(); @@ -111,34 +159,40 @@ export function decodeNormalOctahedronSnorm8(normals: Uint8Array) { interface VertexPositionFormatHandler { defineShader: (builder: ShaderBuilder) => void; - bind: - (gl: GL, shader: ShaderProgram, fragmentChunk: FragmentChunk|MultiscaleFragmentChunk) => void; + bind: ( + gl: GL, + shader: ShaderProgram, + fragmentChunk: FragmentChunk | MultiscaleFragmentChunk, + ) => void; endLayer: (gl: GL, shader: ShaderProgram) => void; } function getFloatPositionHandler(glAttributeType: number): VertexPositionFormatHandler { return { defineShader: (builder: ShaderBuilder) => { - builder.addAttribute('highp vec3', 'aVertexPosition'); + builder.addAttribute("highp vec3", "aVertexPosition"); builder.addVertexCode(`highp vec3 getVertexPosition() { return aVertexPosition; }`); }, - bind(_gl: GL, shader: ShaderProgram, fragmentChunk: FragmentChunk|MultiscaleFragmentChunk) { + bind(_gl: GL, shader: ShaderProgram, fragmentChunk: FragmentChunk | MultiscaleFragmentChunk) { fragmentChunk.vertexBuffer.bindToVertexAttrib( - shader.attribute('aVertexPosition'), - /*components=*/ 3, glAttributeType, /* normalized=*/ true); + shader.attribute("aVertexPosition"), + /*components=*/ 3, + glAttributeType, + /* normalized=*/ true, + ); }, endLayer: (gl: GL, shader: ShaderProgram) => { - gl.disableVertexAttribArray(shader.attribute('aVertexPosition')); - } + gl.disableVertexAttribArray(shader.attribute("aVertexPosition")); + }, }; } -const vertexPositionHandlers: {[format: number]: VertexPositionFormatHandler} = { +const vertexPositionHandlers: { [format: number]: VertexPositionFormatHandler } = { [VertexPositionFormat.float32]: getFloatPositionHandler(WebGL2RenderingContext.FLOAT), [VertexPositionFormat.uint16]: getFloatPositionHandler(WebGL2RenderingContext.UNSIGNED_SHORT), [VertexPositionFormat.uint10]: { defineShader: (builder: ShaderBuilder) => { - builder.addAttribute('highp uint', 'aVertexPosition'); + builder.addAttribute("highp uint", "aVertexPosition"); builder.addVertexCode(` highp vec3 getVertexPosition() { return vec3(float(aVertexPosition & 1023u), @@ -147,14 +201,16 @@ highp vec3 getVertexPosition() { } `); }, - bind(_gl: GL, shader: ShaderProgram, fragmentChunk: FragmentChunk|MultiscaleFragmentChunk) { + bind(_gl: GL, shader: ShaderProgram, fragmentChunk: FragmentChunk | MultiscaleFragmentChunk) { fragmentChunk.vertexBuffer.bindToVertexAttribI( - shader.attribute('aVertexPosition'), - /*components=*/ 1, WebGL2RenderingContext.UNSIGNED_INT); + shader.attribute("aVertexPosition"), + /*components=*/ 1, + WebGL2RenderingContext.UNSIGNED_INT, + ); }, endLayer: (gl: GL, shader: ShaderProgram) => { - gl.disableVertexAttribArray(shader.attribute('aVertexPosition')); - } + gl.disableVertexAttribArray(shader.attribute("aVertexPosition")); + }, }, }; @@ -162,75 +218,100 @@ export class MeshShaderManager { private tempLightVec = new Float32Array(4); private vertexPositionHandler = vertexPositionHandlers[this.vertexPositionFormat]; constructor( - public fragmentRelativeVertices: boolean, public vertexPositionFormat: VertexPositionFormat) { - } + public fragmentRelativeVertices: boolean, + public vertexPositionFormat: VertexPositionFormat, + ) {} beginLayer( - gl: GL, shader: ShaderProgram, renderContext: PerspectiveViewRenderContext, - displayState: MeshDisplayState) { - let {lightDirection, ambientLighting, directionalLighting} = renderContext; + gl: GL, + shader: ShaderProgram, + renderContext: PerspectiveViewRenderContext, + displayState: MeshDisplayState, + ) { + let { lightDirection, ambientLighting, directionalLighting } = renderContext; let lightVec = this.tempLightVec; vec3.scale(lightVec, lightDirection, directionalLighting); lightVec[3] = ambientLighting; - gl.uniform4fv(shader.uniform('uLightDirection'), lightVec); + gl.uniform4fv(shader.uniform("uLightDirection"), lightVec); const silhouetteRendering = displayState.silhouetteRendering.value; if (silhouetteRendering > 0) { - gl.uniform1f(shader.uniform('uSilhouettePower'), silhouetteRendering); + gl.uniform1f(shader.uniform("uSilhouettePower"), silhouetteRendering); } } setColor(gl: GL, shader: ShaderProgram, color: vec4) { - gl.uniform4fv(shader.uniform('uColor'), color); + gl.uniform4fv(shader.uniform("uColor"), color); } setPickID(gl: GL, shader: ShaderProgram, pickID: number) { - gl.uniform1ui(shader.uniform('uPickID'), pickID); + gl.uniform1ui(shader.uniform("uPickID"), pickID); } beginModel( - gl: GL, shader: ShaderProgram, renderContext: PerspectiveViewRenderContext, modelMat: mat4) { - const {projectionParameters} = renderContext; + gl: GL, + shader: ShaderProgram, + renderContext: PerspectiveViewRenderContext, + modelMat: mat4, + ) { + const { projectionParameters } = renderContext; gl.uniformMatrix4fv( - shader.uniform('uModelViewProjection'), false, - mat4.multiply(tempMat4, projectionParameters.viewProjectionMat, modelMat)); + shader.uniform("uModelViewProjection"), + false, + mat4.multiply(tempMat4, projectionParameters.viewProjectionMat, modelMat), + ); mat3FromMat4(tempMat3, modelMat); scaleMat3Output( - tempMat3, tempMat3, projectionParameters.displayDimensionRenderInfo.canonicalVoxelFactors); + tempMat3, + tempMat3, + projectionParameters.displayDimensionRenderInfo.canonicalVoxelFactors, + ); mat3.invert(tempMat3, tempMat3); mat3.transpose(tempMat3, tempMat3); - gl.uniformMatrix3fv(shader.uniform('uNormalMatrix'), false, tempMat3); + gl.uniformMatrix3fv(shader.uniform("uNormalMatrix"), false, tempMat3); } drawFragmentHelper( - gl: GL, shader: ShaderProgram, fragmentChunk: FragmentChunk|MultiscaleFragmentChunk, - indexBegin: number, indexEnd: number) { + gl: GL, + shader: ShaderProgram, + fragmentChunk: FragmentChunk | MultiscaleFragmentChunk, + indexBegin: number, + indexEnd: number, + ) { this.vertexPositionHandler.bind(gl, shader, fragmentChunk); - const {meshData} = fragmentChunk; + const { meshData } = fragmentChunk; fragmentChunk.normalBuffer.bindToVertexAttrib( - shader.attribute('aVertexNormal'), - /*components=*/ 2, WebGL2RenderingContext.BYTE, /*normalized=*/ true); + shader.attribute("aVertexNormal"), + /*components=*/ 2, + WebGL2RenderingContext.BYTE, + /*normalized=*/ true, + ); fragmentChunk.indexBuffer.bind(); - const {indices} = meshData; + const { indices } = meshData; + if (indexEnd < indexBegin) { + debugger; + } gl.drawElements( - meshData.strips ? WebGL2RenderingContext.TRIANGLE_STRIP : WebGL2RenderingContext.TRIANGLES, - indexEnd - indexBegin, - indices.BYTES_PER_ELEMENT === 2 ? WebGL2RenderingContext.UNSIGNED_SHORT : - WebGL2RenderingContext.UNSIGNED_INT, - indexBegin * indices.BYTES_PER_ELEMENT); + meshData.strips ? WebGL2RenderingContext.TRIANGLE_STRIP : WebGL2RenderingContext.TRIANGLES, + indexEnd - indexBegin, + indices.BYTES_PER_ELEMENT === 2 + ? WebGL2RenderingContext.UNSIGNED_SHORT + : WebGL2RenderingContext.UNSIGNED_INT, + indexBegin * indices.BYTES_PER_ELEMENT, + ); } drawFragment(gl: GL, shader: ShaderProgram, fragmentChunk: FragmentChunk) { - const {meshData} = fragmentChunk; - const {indices} = meshData; + const { meshData } = fragmentChunk; + const { indices } = meshData; this.drawFragmentHelper(gl, shader, fragmentChunk, 0, indices.length); } drawMultiscaleFragment( - gl: GL, - shader: ShaderProgram, - fragmentChunk: MultiscaleFragmentChunk, - subChunkBegin: number, - subChunkEnd: number, + gl: GL, + shader: ShaderProgram, + fragmentChunk: MultiscaleFragmentChunk, + subChunkBegin: number, + subChunkEnd: number, ) { const indexBegin = fragmentChunk.meshData.subChunkOffsets[subChunkBegin]; const indexEnd = fragmentChunk.meshData.subChunkOffsets[subChunkEnd]; @@ -239,31 +320,31 @@ export class MeshShaderManager { endLayer(gl: GL, shader: ShaderProgram) { this.vertexPositionHandler.endLayer(gl, shader); - gl.disableVertexAttribArray(shader.attribute('aVertexNormal')); + gl.disableVertexAttribArray(shader.attribute("aVertexNormal")); } - makeGetter(layer: RefCounted&{gl: GL, displayState: MeshDisplayState}) { + makeGetter(layer: RefCounted & { gl: GL; displayState: MeshDisplayState }) { const silhouetteRenderingEnabled = layer.registerDisposer( - makeCachedDerivedWatchableValue(x => x > 0, [layer.displayState.silhouetteRendering])); + makeCachedDerivedWatchableValue((x) => x > 0, [layer.displayState.silhouetteRendering]), + ); return parameterizedEmitterDependentShaderGetter(layer, layer.gl, { - memoizeKey: - `mesh/MeshShaderManager/${this.fragmentRelativeVertices}/${this.vertexPositionFormat}`, + memoizeKey: `mesh/MeshShaderManager/${this.fragmentRelativeVertices}/${this.vertexPositionFormat}`, parameters: silhouetteRenderingEnabled, defineShader: (builder, silhouetteRenderingEnabled) => { this.vertexPositionHandler.defineShader(builder); - builder.addAttribute('highp vec2', 'aVertexNormal'); - builder.addVarying('highp vec4', 'vColor'); - builder.addUniform('highp vec4', 'uLightDirection'); - builder.addUniform('highp vec4', 'uColor'); - builder.addUniform('highp mat3', 'uNormalMatrix'); - builder.addUniform('highp mat4', 'uModelViewProjection'); - builder.addUniform('highp uint', 'uPickID'); + builder.addAttribute("highp vec2", "aVertexNormal"); + builder.addVarying("highp vec4", "vColor"); + builder.addUniform("highp vec4", "uLightDirection"); + builder.addUniform("highp vec4", "uColor"); + builder.addUniform("highp mat3", "uNormalMatrix"); + builder.addUniform("highp mat4", "uModelViewProjection"); + builder.addUniform("highp uint", "uPickID"); if (silhouetteRenderingEnabled) { - builder.addUniform('highp float', 'uSilhouettePower'); + builder.addUniform("highp float", "uSilhouettePower"); } if (this.fragmentRelativeVertices) { - builder.addUniform('highp vec3', 'uFragmentOrigin'); - builder.addUniform('highp vec3', 'uFragmentShape'); + builder.addUniform("highp vec3", "uFragmentOrigin"); + builder.addUniform("highp vec3", "uFragmentShape"); } builder.addVertexCode(glsl_decodeNormalOctahedronSnorm8); let vertexMain = ``; @@ -302,33 +383,37 @@ export interface MeshDisplayState extends SegmentationDisplayState3D { silhouetteRendering: WatchableValueInterface; } -export class MeshLayer extends - PerspectiveViewRenderLayer { - protected meshShaderManager = - new MeshShaderManager(/*fragmentRelativeVertices=*/ false, VertexPositionFormat.float32); +export class MeshLayer extends PerspectiveViewRenderLayer { + protected meshShaderManager = new MeshShaderManager( + /*fragmentRelativeVertices=*/ false, + VertexPositionFormat.float32, + ); private getShader = this.meshShaderManager.makeGetter(this); backend: SegmentationLayerSharedObject; constructor( - public chunkManager: ChunkManager, public source: MeshSource, - public displayState: MeshDisplayState) { + public chunkManager: ChunkManager, + public source: MeshSource, + public displayState: MeshDisplayState, + ) { super(); registerRedrawWhenSegmentationDisplayState3DChanged(displayState, this); this.registerDisposer(displayState.silhouetteRendering.changed.add(this.redrawNeeded.dispatch)); - let sharedObject = this.backend = this.registerDisposer( - new SegmentationLayerSharedObject(chunkManager, displayState, this.layerChunkProgressInfo)); + let sharedObject = (this.backend = this.registerDisposer( + new SegmentationLayerSharedObject(chunkManager, displayState, this.layerChunkProgressInfo), + )); sharedObject.RPC_TYPE_ID = MESH_LAYER_RPC_ID; sharedObject.initializeCounterpartWithChunkManager({ - 'source': source.addCounterpartRef(), + source: source.addCounterpartRef(), }); sharedObject.visibility.add(this.visibility); this.registerDisposer(displayState.renderScaleHistogram.visibility.add(this.visibility)); } get isTransparent() { - const {displayState} = this; + const { displayState } = this; return displayState.objectAlpha.value < 1.0 || displayState.silhouetteRendering.value > 0; } @@ -341,24 +426,27 @@ export class MeshLayer extends } draw( - renderContext: PerspectiveViewRenderContext, - attachment: VisibleLayerInfo) { + renderContext: PerspectiveViewRenderContext, + attachment: VisibleLayerInfo, + ) { if (!renderContext.emitColor && renderContext.alreadyEmittedPickID) { // No need for a separate pick ID pass. return; } - const {gl, displayState, meshShaderManager} = this; + const { gl, displayState, meshShaderManager } = this; if (displayState.objectAlpha.value <= 0.0) { // Skip drawing. return; } const modelMatrix = update3dRenderLayerAttachment( - displayState.transform.value, renderContext.projectionParameters.displayDimensionRenderInfo, - attachment); + displayState.transform.value, + renderContext.projectionParameters.displayDimensionRenderInfo, + attachment, + ); if (modelMatrix === undefined) { return; } - const {shader} = this.getShader(renderContext.emitter); + const { shader } = this.getShader(renderContext.emitter); if (shader === null) return; shader.bind(); meshShaderManager.beginLayer(gl, shader, renderContext, this.displayState); @@ -366,52 +454,60 @@ export class MeshLayer extends const manifestChunks = this.source.chunks; - let totalChunks = 0, presentChunks = 0; - const {renderScaleHistogram} = this.displayState; + let totalChunks = 0, + presentChunks = 0; + const { renderScaleHistogram } = this.displayState; const fragmentChunks = this.source.fragmentSource.chunks; forEachVisibleSegmentToDraw( - displayState, this, renderContext.emitColor, - renderContext.emitPickID ? renderContext.pickIDs : undefined, - (objectId, color, pickIndex) => { - const key = getObjectKey(objectId); - const manifestChunk = manifestChunks.get(key); - ++totalChunks; - if (manifestChunk === undefined) return; - ++presentChunks; - if (renderContext.emitColor) { - meshShaderManager.setColor(gl, shader, color!); - } - if (renderContext.emitPickID) { - meshShaderManager.setPickID(gl, shader, pickIndex!); - } - totalChunks += manifestChunk.fragmentIds.length; - - for (const fragmentId of manifestChunk.fragmentIds) { - const {key: fragmentKey} = this.source.getFragmentKey(key, fragmentId); - const fragment = fragmentChunks.get(fragmentKey); - if (fragment !== undefined && fragment.state === ChunkState.GPU_MEMORY) { - meshShaderManager.drawFragment(gl, shader, fragment); - ++presentChunks; - } + displayState, + this, + renderContext.emitColor, + renderContext.emitPickID ? renderContext.pickIDs : undefined, + (objectId, color, pickIndex) => { + const key = getObjectKey(objectId); + const manifestChunk = manifestChunks.get(key); + ++totalChunks; + if (manifestChunk === undefined) return; + ++presentChunks; + if (renderContext.emitColor) { + meshShaderManager.setColor(gl, shader, color!); + } + if (renderContext.emitPickID) { + meshShaderManager.setPickID(gl, shader, pickIndex!); + } + totalChunks += manifestChunk.fragmentIds.length; + + for (const fragmentId of manifestChunk.fragmentIds) { + const { key: fragmentKey } = this.source.getFragmentKey(key, fragmentId); + const fragment = fragmentChunks.get(fragmentKey); + if (fragment !== undefined && fragment.state === ChunkState.GPU_MEMORY) { + meshShaderManager.drawFragment(gl, shader, fragment); + ++presentChunks; } - }); + } + }, + ); if (renderContext.emitColor) { renderScaleHistogram.begin( - this.chunkManager.chunkQueueManager.frameNumberCounter.frameNumber); + this.chunkManager.chunkQueueManager.frameNumberCounter.frameNumber, + ); renderScaleHistogram.add( - Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, presentChunks, - totalChunks - presentChunks); + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + presentChunks, + totalChunks - presentChunks, + ); } meshShaderManager.endLayer(gl, shader); } isReady() { - const {displayState, source} = this; + const { displayState, source } = this; let ready = true; const fragmentChunks = source.fragmentSource.chunks; - forEachVisibleSegment(displayState.segmentationGroupState.value, objectId => { + forEachVisibleSegment(displayState.segmentationGroupState.value, (objectId) => { const key = getObjectKey(objectId); const manifestChunk = source.chunks.get(key); if (manifestChunk === undefined) { @@ -419,7 +515,7 @@ export class MeshLayer extends return; } for (const fragmentId of manifestChunk.fragmentIds) { - const {key: fragmentKey} = this.source.getFragmentKey(key, fragmentId); + const { key: fragmentKey } = this.source.getFragmentKey(key, fragmentId); const fragmentChunk = fragmentChunks.get(fragmentKey); if (fragmentChunk === undefined || fragmentChunk.state !== ChunkState.GPU_MEMORY) { ready = false; @@ -475,14 +571,14 @@ export class MeshSource extends ChunkSource { initializeCounterpart(rpc: RPC, options: any) { this.fragmentSource.initializeCounterpart(this.chunkManager.rpc!, {}); - options['fragmentSource'] = this.fragmentSource.addCounterpartRef(); + options["fragmentSource"] = this.fragmentSource.addCounterpartRef(); super.initializeCounterpart(rpc, options); } getChunk(x: any) { return new ManifestChunk(this, x); } getFragmentKey(objectKey: string, fragmentId: string) { - return {key:`${objectKey}/${fragmentId}`, fragmentId: fragmentId} + return { key: `${objectKey}/${fragmentId}`, fragmentId: fragmentId }; } } @@ -492,7 +588,10 @@ export class FragmentSource extends ChunkSource { get key() { return this.meshSource.key; } - constructor(chunkManager: ChunkManager, public meshSource: MeshSource) { + constructor( + chunkManager: ChunkManager, + public meshSource: MeshSource, + ) { super(chunkManager); } getChunk(x: any) { @@ -500,42 +599,47 @@ export class FragmentSource extends ChunkSource { } } - function hasFragmentChunk( - fragmentChunks: Map, objectKey: string, lod: number, - chunkIndex: number) { + fragmentChunks: Map, + objectKey: string, + lod: number, + chunkIndex: number, +) { const fragmentChunk = fragmentChunks.get(getMultiscaleFragmentKey(objectKey, lod, chunkIndex)); return fragmentChunk !== undefined && fragmentChunk.state === ChunkState.GPU_MEMORY; } -export class MultiscaleMeshLayer extends - PerspectiveViewRenderLayer { +export class MultiscaleMeshLayer extends PerspectiveViewRenderLayer { protected meshShaderManager = new MeshShaderManager( - /*fragmentRelativeVertices=*/ this.source.format.fragmentRelativeVertices, - this.source.format.vertexPositionFormat); + /*fragmentRelativeVertices=*/ this.source.format.fragmentRelativeVertices, + this.source.format.vertexPositionFormat, + ); private getShader = this.meshShaderManager.makeGetter(this); backend: SegmentationLayerSharedObject; constructor( - public chunkManager: ChunkManager, public source: MultiscaleMeshSource, - public displayState: MeshDisplayState) { + public chunkManager: ChunkManager, + public source: MultiscaleMeshSource, + public displayState: MeshDisplayState, + ) { super(); registerRedrawWhenSegmentationDisplayState3DChanged(displayState, this); this.registerDisposer(displayState.silhouetteRendering.changed.add(this.redrawNeeded.dispatch)); - let sharedObject = this.backend = this.registerDisposer( - new SegmentationLayerSharedObject(chunkManager, displayState, this.layerChunkProgressInfo)); + let sharedObject = (this.backend = this.registerDisposer( + new SegmentationLayerSharedObject(chunkManager, displayState, this.layerChunkProgressInfo), + )); sharedObject.RPC_TYPE_ID = MULTISCALE_MESH_LAYER_RPC_ID; sharedObject.initializeCounterpartWithChunkManager({ - 'source': source.addCounterpartRef(), + source: source.addCounterpartRef(), }); sharedObject.visibility.add(this.visibility); this.registerDisposer(displayState.renderScaleHistogram.visibility.add(this.visibility)); } get isTransparent() { - const {displayState} = this; + const { displayState } = this; return displayState.objectAlpha.value < 1.0 || displayState.silhouetteRendering.value > 0; } @@ -548,50 +652,61 @@ export class MultiscaleMeshLayer extends } draw( - renderContext: PerspectiveViewRenderContext, - attachment: VisibleLayerInfo) { + renderContext: PerspectiveViewRenderContext, + attachment: VisibleLayerInfo, + ) { if (!renderContext.emitColor && renderContext.alreadyEmittedPickID) { // No need for a separate pick ID pass. return; } - const {gl, displayState, meshShaderManager} = this; + const { gl, displayState, meshShaderManager } = this; if (displayState.objectAlpha.value <= 0.0) { // Skip drawing. return; } const modelMatrix = update3dRenderLayerAttachment( - displayState.transform.value, renderContext.projectionParameters.displayDimensionRenderInfo, - attachment); + displayState.transform.value, + renderContext.projectionParameters.displayDimensionRenderInfo, + attachment, + ); if (modelMatrix === undefined) return; - const {shader} = this.getShader(renderContext.emitter); + const { shader } = this.getShader(renderContext.emitter); if (shader === null) return; shader.bind(); meshShaderManager.beginLayer(gl, shader, renderContext, this.displayState); - const {renderScaleHistogram} = this.displayState; + const { renderScaleHistogram } = this.displayState; if (renderContext.emitColor) { renderScaleHistogram.begin( - this.chunkManager.chunkQueueManager.frameNumberCounter.frameNumber); + this.chunkManager.chunkQueueManager.frameNumberCounter.frameNumber, + ); } mat3FromMat4(tempMat3, modelMatrix); scaleMat3Output( - tempMat3, tempMat3, - renderContext.projectionParameters.displayDimensionRenderInfo.voxelPhysicalScales); + tempMat3, + tempMat3, + renderContext.projectionParameters.displayDimensionRenderInfo.voxelPhysicalScales, + ); const scaleMultiplier = Math.pow(Math.abs(mat3.determinant(tempMat3)), 1 / 3); - const {chunks} = this.source; + const { chunks } = this.source; const fragmentChunks = this.source.fragmentSource.chunks; - const {projectionParameters} = renderContext; + const { projectionParameters } = renderContext; - const modelViewProjection = - mat4.multiply(mat4.create(), projectionParameters.viewProjectionMat, modelMatrix); + const modelViewProjection = mat4.multiply( + mat4.create(), + projectionParameters.viewProjectionMat, + modelMatrix, + ); + + console.log("modelMatrix", modelMatrix); const clippingPlanes = getFrustrumPlanes(new Float32Array(24), modelViewProjection); const detailCutoff = this.displayState.renderScaleTarget.value; - const {fragmentRelativeVertices} = this.source.format; + const { fragmentRelativeVertices } = this.source.format; meshShaderManager.beginModel(gl, shader, renderContext, modelMatrix); @@ -599,109 +714,145 @@ export class MultiscaleMeshLayer extends let presentManifestChunks = 0; forEachVisibleSegmentToDraw( - displayState, this, renderContext.emitColor, - renderContext.emitPickID ? renderContext.pickIDs : undefined, - (objectId, color, pickIndex) => { - const key = getObjectKey(objectId); - const manifestChunk = chunks.get(key); - ++totalManifestChunks; - if (manifestChunk === undefined) return; - ++presentManifestChunks; - const {manifest} = manifestChunk; - const {octree, chunkShape, chunkGridSpatialOrigin, vertexOffsets} = manifest; - if (DEBUG_MULTISCALE_FRAGMENTS) { - try { - validateOctree(octree); - } catch (e) { - console.log(`invalid octree for object=${objectId}: ${e.message}`) - } - } - if (renderContext.emitColor) { - meshShaderManager.setColor(gl, shader, color!); + displayState, + this, + renderContext.emitColor, + renderContext.emitPickID ? renderContext.pickIDs : undefined, + (objectId, color, pickIndex) => { + const key = getObjectKey(objectId); + const manifestChunk = chunks.get(key); + ++totalManifestChunks; + if (manifestChunk === undefined) return; + ++presentManifestChunks; + const { manifest } = manifestChunk; + const { octree, chunkShape, chunkGridSpatialOrigin, vertexOffsets } = manifest; + if (DEBUG_MULTISCALE_FRAGMENTS) { + try { + validateOctree(octree, /*allowDuplicateChildren=*/ true); + } catch (e) { + console.log(`invalid octree for object=${objectId}: ${e.message}`); } - if (renderContext.emitPickID) { - meshShaderManager.setPickID(gl, shader, pickIndex!); - } - getMultiscaleChunksToDraw( - manifest, modelViewProjection, clippingPlanes, detailCutoff, - projectionParameters.width, projectionParameters.height, - (lod, chunkIndex, renderScale) => { - const has = hasFragmentChunk(fragmentChunks, key, lod, chunkIndex); - if (renderContext.emitColor) { - renderScaleHistogram.add( - manifest.lodScales[lod] * scaleMultiplier, renderScale, has ? 1 : 0, - has ? 0 : 1); - } - return has; - }, - (lod, chunkIndex, subChunkBegin, subChunkEnd) => { - const fragmentKey = getMultiscaleFragmentKey(key, lod, chunkIndex); - const fragmentChunk = fragmentChunks.get(fragmentKey)!; - const x = octree[5 * chunkIndex], y = octree[5 * chunkIndex + 1], - z = octree[5 * chunkIndex + 2]; - const scale = 1 << lod; - if (fragmentRelativeVertices) { - gl.uniform3f( - shader.uniform('uFragmentOrigin'), - chunkGridSpatialOrigin[0] + (x * chunkShape[0]) * scale + - vertexOffsets[lod * 3 + 0], - chunkGridSpatialOrigin[1] + (y * chunkShape[1]) * scale + - vertexOffsets[lod * 3 + 1], - chunkGridSpatialOrigin[2] + (z * chunkShape[2]) * scale + - vertexOffsets[lod * 3 + 2]); - gl.uniform3f( - shader.uniform('uFragmentShape'), chunkShape[0] * scale, - chunkShape[1] * scale, chunkShape[2] * scale); - } - if (DEBUG_MULTISCALE_FRAGMENTS) { - const message = `lod=${lod}, chunkIndex=${chunkIndex}, subChunkBegin=${ - subChunkBegin}, subChunkEnd=${subChunkEnd}, uFragmentOrigin=${ - [chunkGridSpatialOrigin[0] + (x * chunkShape[0]) * scale + - vertexOffsets[lod * 3 + 0], - chunkGridSpatialOrigin[1] + (y * chunkShape[1]) * scale + - vertexOffsets[lod * 3 + 1], - chunkGridSpatialOrigin[2] + (z * chunkShape[2]) * scale + - vertexOffsets[lod * 3 + 2]]}, uFragmentShape=${ - [chunkShape[0] * scale, chunkShape[1] * scale, chunkShape[2] * scale]}`; - const pickIndex = - renderContext.pickIDs.registerUint64(this, objectId, 1, message); - if (renderContext.emitPickID) { - meshShaderManager.setPickID(gl, shader, pickIndex!); - } - } - meshShaderManager.drawMultiscaleFragment( - gl, - shader, - fragmentChunk, - subChunkBegin, - subChunkEnd, - ); - }); - }); + } + if (renderContext.emitColor) { + meshShaderManager.setColor(gl, shader, color!); + } + if (renderContext.emitPickID) { + meshShaderManager.setPickID(gl, shader, pickIndex!); + } + getMultiscaleChunksToDraw( + manifest, + modelViewProjection, + clippingPlanes, + detailCutoff, + projectionParameters.width, + projectionParameters.height, + (lod, chunkIndex, renderScale) => { + const has = hasFragmentChunk(fragmentChunks, key, lod, chunkIndex); + if (renderContext.emitColor) { + renderScaleHistogram.add( + manifest.lodScales[lod] * scaleMultiplier, + renderScale, + has ? 1 : 0, + has ? 0 : 1, + ); + } + return has; + }, + (lod, chunkIndex, subChunkBegin, subChunkEnd) => { + const fragmentKey = getMultiscaleFragmentKey(key, lod, chunkIndex); + const fragmentChunk = fragmentChunks.get(fragmentKey)!; + const x = octree[5 * chunkIndex], + y = octree[5 * chunkIndex + 1], + z = octree[5 * chunkIndex + 2]; + const scale = 1 << lod; + if (fragmentRelativeVertices) { + gl.uniform3f( + shader.uniform("uFragmentOrigin"), + chunkGridSpatialOrigin[0] + x * chunkShape[0] * scale + vertexOffsets[lod * 3 + 0], + chunkGridSpatialOrigin[1] + y * chunkShape[1] * scale + vertexOffsets[lod * 3 + 1], + chunkGridSpatialOrigin[2] + z * chunkShape[2] * scale + vertexOffsets[lod * 3 + 2], + ); + gl.uniform3f( + shader.uniform("uFragmentShape"), + chunkShape[0] * scale, + chunkShape[1] * scale, + chunkShape[2] * scale, + ); + } + if (DEBUG_MULTISCALE_FRAGMENTS) { + let minVertex = [ + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + ]; + let maxVertex = [ + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + ]; + const { vertexPositions } = fragmentChunk.meshData; + for (let i = 0; i < vertexPositions.length; ++i) { + minVertex[i % 3] = Math.min(minVertex[i % 3], vertexPositions[i]); + maxVertex[i % 3] = Math.max(maxVertex[i % 3], vertexPositions[i]); + } + const message = `lod=${lod}, chunkIndex=${chunkIndex}, subChunkBegin=${subChunkBegin}, subChunkEnd=${subChunkEnd}, uFragmentOrigin=${[ + chunkGridSpatialOrigin[0] + x * chunkShape[0] * scale + vertexOffsets[lod * 3 + 0], + chunkGridSpatialOrigin[1] + y * chunkShape[1] * scale + vertexOffsets[lod * 3 + 1], + chunkGridSpatialOrigin[2] + z * chunkShape[2] * scale + vertexOffsets[lod * 3 + 2], + ]}, uFragmentShape=${[ + chunkShape[0] * scale, + chunkShape[1] * scale, + chunkShape[2] * scale, + ]}, scale=${scale}, chunkShape=${chunkShape.join()}, actualMin=${minVertex.join()}, actualMax=${maxVertex.join()}`; + const pickIndex = renderContext.pickIDs.registerUint64(this, objectId, 1, message); + if (renderContext.emitPickID) { + meshShaderManager.setPickID(gl, shader, pickIndex!); + } + } + meshShaderManager.drawMultiscaleFragment( + gl, + shader, + fragmentChunk, + subChunkBegin, + subChunkEnd, + ); + }, + ); + }, + ); renderScaleHistogram.add( - Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, presentManifestChunks, - totalManifestChunks - presentManifestChunks); + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + presentManifestChunks, + totalManifestChunks - presentManifestChunks, + ); meshShaderManager.endLayer(gl, shader); } isReady( - renderContext: PerspectiveViewReadyRenderContext, - attachment: VisibleLayerInfo) { - let {displayState} = this; + renderContext: PerspectiveViewReadyRenderContext, + attachment: VisibleLayerInfo, + ) { + let { displayState } = this; if (displayState.objectAlpha.value <= 0.0) { // Skip drawing. return true; } const modelMatrix = update3dRenderLayerAttachment( - displayState.transform.value, renderContext.projectionParameters.displayDimensionRenderInfo, - attachment); + displayState.transform.value, + renderContext.projectionParameters.displayDimensionRenderInfo, + attachment, + ); if (modelMatrix === undefined) return false; - const {chunks} = this.source; + const { chunks } = this.source; const fragmentChunks = this.source.fragmentSource.chunks; - const {projectionParameters} = renderContext; - const modelViewProjection = - mat4.multiply(mat4.create(), projectionParameters.viewProjectionMat, modelMatrix); + const { projectionParameters } = renderContext; + const modelViewProjection = mat4.multiply( + mat4.create(), + projectionParameters.viewProjectionMat, + modelMatrix, + ); const clippingPlanes = getFrustrumPlanes(new Float32Array(24), modelViewProjection); @@ -717,25 +868,32 @@ export class MultiscaleMeshLayer extends hasAllChunks = false; return; } - const {manifest} = manifestChunk; + const { manifest } = manifestChunk; getMultiscaleChunksToDraw( - manifest, modelViewProjection, clippingPlanes, detailCutoff, projectionParameters.width, - projectionParameters.height, (lod, chunkIndex) => { - hasAllChunks = hasAllChunks && hasFragmentChunk(fragmentChunks, key, lod, chunkIndex); - return hasAllChunks; - }, () => {}); + manifest, + modelViewProjection, + clippingPlanes, + detailCutoff, + projectionParameters.width, + projectionParameters.height, + (lod, chunkIndex) => { + hasAllChunks = hasAllChunks && hasFragmentChunk(fragmentChunks, key, lod, chunkIndex); + return hasAllChunks; + }, + () => {}, + ); }); return hasAllChunks; } - getObjectPosition(id: Uint64): Float32Array|undefined { + getObjectPosition(id: Uint64): Float32Array | undefined { const transform = this.displayState.transform.value; if (transform.error !== undefined) return undefined; const chunk = this.source.chunks.get(getObjectKey(id)); if (chunk === undefined) return undefined; - const {manifest} = chunk; - const {clipLowerBound, clipUpperBound} = manifest; - const {rank} = transform; + const { manifest } = chunk; + const { clipLowerBound, clipUpperBound } = manifest; + const { rank } = transform; // Center position, in model coordinates. const modelCenter = new Float32Array(rank); for (let i = 0; i < 3; ++i) { @@ -743,7 +901,12 @@ export class MultiscaleMeshLayer extends } const layerCenter = new Float32Array(rank); matrix.transformPoint( - layerCenter, transform.modelToRenderLayerTransform, rank + 1, modelCenter, rank); + layerCenter, + transform.modelToRenderLayerTransform, + rank + 1, + modelCenter, + rank, + ); return layerCenter; } } @@ -754,12 +917,12 @@ export class MultiscaleManifestChunk extends Chunk { constructor(source: MultiscaleMeshSource, x: any) { super(source); - this.manifest = x['manifest']; + this.manifest = x["manifest"]; } } export class MultiscaleFragmentChunk extends Chunk { - meshData: EncodedMeshData&{subChunkOffsets: Uint32Array}; + meshData: EncodedMeshData & { subChunkOffsets: Uint32Array }; source: MultiscaleFragmentSource; vertexBuffer: Buffer; indexBuffer: Buffer; @@ -796,13 +959,13 @@ export class MultiscaleMeshSource extends ChunkSource { } static encodeOptions(options: MultiscaleMeshSourceOptions) { - return {format: options.format, ...super.encodeOptions(options)}; + return { format: options.format, ...super.encodeOptions(options) }; } initializeCounterpart(rpc: RPC, options: any) { this.fragmentSource.initializeCounterpart(this.chunkManager.rpc!, {}); - options['fragmentSource'] = this.fragmentSource.addCounterpartRef(); - options['format'] = this.format; + options["fragmentSource"] = this.fragmentSource.addCounterpartRef(); + options["format"] = this.format; super.initializeCounterpart(rpc, options); } getChunk(x: any) { @@ -816,7 +979,10 @@ export class MultiscaleFragmentSource extends ChunkSource { get key() { return this.meshSource.key; } - constructor(chunkManager: ChunkManager, public meshSource: MultiscaleMeshSource) { + constructor( + chunkManager: ChunkManager, + public meshSource: MultiscaleMeshSource, + ) { super(chunkManager); } getChunk(x: any) { diff --git a/src/neuroglancer/mesh/multiscale.ts b/src/neuroglancer/mesh/multiscale.ts index 64250efbb..bb3063cae 100644 --- a/src/neuroglancer/mesh/multiscale.ts +++ b/src/neuroglancer/mesh/multiscale.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import {isAABBVisible, mat4, vec3} from 'neuroglancer/util/geom'; -import {getOctreeChildIndex} from 'neuroglancer/util/zorder'; +import { isAABBVisible, mat4, vec3 } from "neuroglancer/util/geom"; +import { getOctreeChildIndex } from "neuroglancer/util/zorder"; -const DEBUG_CHUNKS_TO_DRAW = false; +const DEBUG_CHUNKS_TO_DRAW = true; export interface MultiscaleMeshManifest { /** @@ -83,34 +83,53 @@ export interface MultiscaleMeshManifest { * mesh level of detail down to 500 nm will be requested for that portion of the object. */ export function getDesiredMultiscaleMeshChunks( - manifest: MultiscaleMeshManifest, modelViewProjection: mat4, clippingPlanes: Float32Array, - detailCutoff: number, viewportWidth: number, viewportHeight: number, - callback: (lod: number, row: number, renderScale: number, empty: number) => void) { - const {octree, lodScales, chunkGridSpatialOrigin, chunkShape} = manifest; + manifest: MultiscaleMeshManifest, + modelViewProjection: mat4, + clippingPlanes: Float32Array, + detailCutoff: number, + viewportWidth: number, + viewportHeight: number, + callback: (lod: number, row: number, renderScale: number, empty: number) => void, +) { + const { octree, lodScales, chunkGridSpatialOrigin, chunkShape } = manifest; const maxLod = lodScales.length - 1; - const m00 = modelViewProjection[0], m01 = modelViewProjection[4], m02 = modelViewProjection[8], - m10 = modelViewProjection[1], m11 = modelViewProjection[5], m12 = modelViewProjection[9], - m30 = modelViewProjection[3], m31 = modelViewProjection[7], m32 = modelViewProjection[11], - m33 = modelViewProjection[15]; + const m00 = modelViewProjection[0], + m01 = modelViewProjection[4], + m02 = modelViewProjection[8], + m10 = modelViewProjection[1], + m11 = modelViewProjection[5], + m12 = modelViewProjection[9], + m30 = modelViewProjection[3], + m31 = modelViewProjection[7], + m32 = modelViewProjection[11], + m33 = modelViewProjection[15]; const minWXcoeff = m30 > 0 ? 0 : 1; const minWYcoeff = m31 > 0 ? 0 : 1; const minWZcoeff = m32 > 0 ? 0 : 1; - - const nearA = clippingPlanes[4 * 4], nearB = clippingPlanes[4 * 4 + 1], - nearC = clippingPlanes[4 * 4 + 2], nearD = clippingPlanes[4 * 4 + 3]; + const nearA = clippingPlanes[4 * 4], + nearB = clippingPlanes[4 * 4 + 1], + nearC = clippingPlanes[4 * 4 + 2], + nearD = clippingPlanes[4 * 4 + 3]; function getPointW(x: number, y: number, z: number) { return m30 * x + m31 * y + m32 * z + m33; } function getBoxW( - xLower: number, yLower: number, zLower: number, xUpper: number, yUpper: number, - zUpper: number) { + xLower: number, + yLower: number, + zLower: number, + xUpper: number, + yUpper: number, + zUpper: number, + ) { return getPointW( - xLower + minWXcoeff * (xUpper - xLower), yLower + minWYcoeff * (yUpper - yLower), - zLower + minWZcoeff * (zUpper - zLower)); + xLower + minWXcoeff * (xUpper - xLower), + yLower + minWYcoeff * (yUpper - yLower), + zLower + minWZcoeff * (zUpper - zLower), + ); } /** @@ -119,10 +138,12 @@ export function getDesiredMultiscaleMeshChunks( */ const minWClip = getPointW(-nearD * nearA, -nearD * nearB, -nearD * nearC); - const objectXLower = manifest.clipLowerBound[0], objectYLower = manifest.clipLowerBound[1], - objectZLower = manifest.clipLowerBound[2]; - const objectXUpper = manifest.clipUpperBound[0], objectYUpper = manifest.clipUpperBound[1], - objectZUpper = manifest.clipUpperBound[2]; + const objectXLower = manifest.clipLowerBound[0], + objectYLower = manifest.clipLowerBound[1], + objectZLower = manifest.clipLowerBound[2]; + const objectXUpper = manifest.clipUpperBound[0], + objectYUpper = manifest.clipUpperBound[1], + objectZUpper = manifest.clipUpperBound[2]; const xScale = Math.sqrt((m00 * viewportWidth) ** 2 + (m10 * viewportHeight) ** 2); const yScale = Math.sqrt((m01 * viewportWidth) ** 2 + (m11 * viewportHeight) ** 2); @@ -133,57 +154,94 @@ export function getDesiredMultiscaleMeshChunks( function handleChunk(lod: number, row: number, priorLodScale: number) { const size = 1 << lod; const rowOffset = row * 5; - const gridX = octree[rowOffset], gridY = octree[rowOffset + 1], gridZ = octree[rowOffset + 2], - childBeginAndVirtual = octree[rowOffset + 3], childEndAndEmpty = octree[rowOffset + 4]; + const gridX = octree[rowOffset], + gridY = octree[rowOffset + 1], + gridZ = octree[rowOffset + 2], + childBeginAndVirtual = octree[rowOffset + 3], + childEndAndEmpty = octree[rowOffset + 4]; let xLower = gridX * size * chunkShape[0] + chunkGridSpatialOrigin[0], - yLower = gridY * size * chunkShape[1] + chunkGridSpatialOrigin[1], - zLower = gridZ * size * chunkShape[2] + chunkGridSpatialOrigin[2]; - let xUpper = xLower + size * chunkShape[0], yUpper = yLower + size * chunkShape[1], - zUpper = zLower + size * chunkShape[2]; + yLower = gridY * size * chunkShape[1] + chunkGridSpatialOrigin[1], + zLower = gridZ * size * chunkShape[2] + chunkGridSpatialOrigin[2]; + let xUpper = xLower + size * chunkShape[0], + yUpper = yLower + size * chunkShape[1], + zUpper = zLower + size * chunkShape[2]; xLower = Math.max(xLower, objectXLower); yLower = Math.max(yLower, objectYLower); zLower = Math.max(zLower, objectZLower); xUpper = Math.min(xUpper, objectXUpper); yUpper = Math.min(yUpper, objectYUpper); zUpper = Math.min(zUpper, objectZUpper); - + if (typeof window !== "undefined") { + // console.log("manifest", manifest, window); + console.log({ + lod, + size, + chunkShape, + gridX, + gridY, + gridZ, + objectXLower, + objectXUpper, + objectYLower, + objectYUpper, + objectZLower, + objectZUpper, + }); + console.log({ xLower, yLower, zLower, xUpper, yUpper, zUpper }); + } if (isAABBVisible(xLower, yLower, zLower, xUpper, yUpper, zUpper, clippingPlanes)) { + if (typeof window !== "undefined") { + console.log(" bounding box is visible"); + } const minW = Math.max(minWClip, getBoxW(xLower, yLower, zLower, xUpper, yUpper, zUpper)); const pixelSize = minW / scaleFactor; if (priorLodScale === 0 || pixelSize * detailCutoff < priorLodScale) { let lodScale = lodScales[lod]; if (lodScale !== 0) { - const virtual = (childBeginAndVirtual >>> 31); + const virtual = childBeginAndVirtual >>> 31; if (virtual) { lodScale = 0; } - const empty = (childEndAndEmpty >>> 31); + const empty = childEndAndEmpty >>> 31; callback(lod, row, lodScale / pixelSize, empty | virtual); } if (lod > 0 && (lodScale === 0 || pixelSize * detailCutoff < lodScale)) { const nextPriorLodScale = lodScale === 0 ? priorLodScale : lodScale; - const childBegin = (childBeginAndVirtual & 0x7FFFFFFF) >>> 0; - const childEnd = (childEndAndEmpty & 0x7FFFFFFF) >>> 0; + const childBegin = (childBeginAndVirtual & 0x7fffffff) >>> 0; + const childEnd = (childEndAndEmpty & 0x7fffffff) >>> 0; for (let childRow = childBegin; childRow < childEnd; ++childRow) { handleChunk(lod - 1, childRow, nextPriorLodScale); } } } + } else { + if (typeof window !== "undefined") { + console.log(" bounding box is not visible"); + } } } handleChunk(maxLod, octree.length / 5 - 1, 0); } export function getMultiscaleChunksToDraw( - manifest: MultiscaleMeshManifest, modelViewProjection: mat4, clippingPlanes: Float32Array, - detailCutoff: number, viewportWidth: number, viewportHeight: number, - hasChunk: (lod: number, row: number, renderScale: number) => boolean, - callback: ( - lod: number, row: number, subChunkBegin: number, subChunkEnd: number, - renderScale: number) => void) { - const {lodScales} = manifest; + manifest: MultiscaleMeshManifest, + modelViewProjection: mat4, + clippingPlanes: Float32Array, + detailCutoff: number, + viewportWidth: number, + viewportHeight: number, + hasChunk: (lod: number, row: number, renderScale: number) => boolean, + callback: ( + lod: number, + row: number, + subChunkBegin: number, + subChunkEnd: number, + renderScale: number, + ) => void, +) { + const { lodScales } = manifest; let maxLod = 0; while (maxLod + 1 < lodScales.length && lodScales[maxLod + 1] !== 0) { ++maxLod; @@ -197,9 +255,9 @@ export function getMultiscaleChunksToDraw( let priorSubChunkIndex = 0; function emitChunksUpTo(targetStackIndex: number, subChunkIndex: number) { if (DEBUG_CHUNKS_TO_DRAW) { - console.log(`emitChunksUpTo: stackDepth=${stackDepth}, targetStackIndex=${ - targetStackIndex}, subChunkIndex=${subChunkIndex}, priorSubChunkIndex=${ - priorSubChunkIndex}`); + console.log( + `emitChunksUpTo: stackDepth=${stackDepth}, targetStackIndex=${targetStackIndex}, subChunkIndex=${subChunkIndex}, priorSubChunkIndex=${priorSubChunkIndex}`, + ); } while (true) { if (stackDepth === 0) return; @@ -214,10 +272,11 @@ export function getMultiscaleChunksToDraw( if (targetStackIndex === stackDepth) { const endSubChunk = subChunkIndex & (numSubChunks - 1); - if (priorSubChunkIndex !== endSubChunk && entryRow !== -1) { + if (priorSubChunkIndex < endSubChunk && entryRow !== -1) { if (DEBUG_CHUNKS_TO_DRAW) { - console.log(` drawing chunk because priorSubChunkIndex (${ - priorSubChunkIndex}) != endSubChunk (${endSubChunk})`); + console.log( + ` drawing chunk because priorSubChunkIndex (${priorSubChunkIndex}) != endSubChunk (${endSubChunk})`, + ); } callback(entryLod, entryRow, priorSubChunkIndex, endSubChunk, entryRenderScale); } @@ -234,68 +293,90 @@ export function getMultiscaleChunksToDraw( let priorMissingLod = 0; if (DEBUG_CHUNKS_TO_DRAW) { - console.log(''); - console.log('Starting to draw'); + console.log(""); + console.log("Starting to draw"); } - const {octree} = manifest; + const { octree } = manifest; getDesiredMultiscaleMeshChunks( - manifest, modelViewProjection, clippingPlanes, detailCutoff, viewportWidth, viewportHeight, - (lod, row, renderScale, empty) => { - if (!empty && !hasChunk(lod, row, renderScale)) { - priorMissingLod = Math.max(lod, priorMissingLod); - return; - } - if (lod < priorMissingLod) { - // A parent chunk (containing chunk at coarser level-of-detail) is missing. We can't draw - // chunks at this level-of-detail because we would not be able to fill in gaps. - return; - } - priorMissingLod = 0; - const rowOffset = row * 5; - const x = octree[rowOffset], y = octree[rowOffset + 1], z = octree[rowOffset + 2]; - const subChunkIndex = getOctreeChildIndex(x, y, z); - const stackIndex = maxLod - lod; - emitChunksUpTo(stackIndex, subChunkIndex); - const stackOffset = stackIndex * stackEntryStride; - stack[stackOffset] = empty ? -1 : row; - stack[stackOffset + 1] = subChunkIndex; - stack[stackOffset + 2] = renderScale; - if (DEBUG_CHUNKS_TO_DRAW) { - console.log(`Adding to stack: lod=${lod}, row=${stack[stackOffset]}, subChunkIndex=${ - subChunkIndex}`); - } - priorSubChunkIndex = 0; - stackDepth = stackIndex + 1; - }); + manifest, + modelViewProjection, + clippingPlanes, + detailCutoff, + viewportWidth, + viewportHeight, + (lod, row, renderScale, empty) => { + if (!empty && !hasChunk(lod, row, renderScale)) { + priorMissingLod = Math.max(lod, priorMissingLod); + return; + } + if (lod < priorMissingLod) { + // A parent chunk (containing chunk at coarser level-of-detail) is missing. We can't draw + // chunks at this level-of-detail because we would not be able to fill in gaps. + return; + } + priorMissingLod = 0; + const rowOffset = row * 5; + const x = octree[rowOffset], + y = octree[rowOffset + 1], + z = octree[rowOffset + 2]; + const subChunkIndex = getOctreeChildIndex(x, y, z); + const stackIndex = maxLod - lod; + emitChunksUpTo(stackIndex, subChunkIndex); + const stackOffset = stackIndex * stackEntryStride; + stack[stackOffset] = empty ? -1 : row; + stack[stackOffset + 1] = subChunkIndex; + stack[stackOffset + 2] = renderScale; + if (DEBUG_CHUNKS_TO_DRAW) { + console.log( + `Adding to stack: lod=${lod}, row=${stack[stackOffset]}, subChunkIndex=${subChunkIndex}`, + ); + } + priorSubChunkIndex = 0; + stackDepth = stackIndex + 1; + }, + ); emitChunksUpTo(0, 0); } -export function validateOctree(octree: Uint32Array) { +export function validateOctree(octree: Uint32Array, allowDuplicateChildren: boolean = false) { if (octree.length % 5 !== 0) { - throw new Error('Invalid length'); + throw new Error("Invalid length"); } const numNodes = octree.length / 5; const seenNodes = new Set(); function exploreNode(node: number) { if (seenNodes.has(node)) { - throw new Error('Previously seen node'); + throw new Error(`Previously seen node: ${node}`); } seenNodes.add(node); if (node < 0 || node >= numNodes) { - throw new Error('Invalid node reference'); + throw new Error(`Invalid node reference: ${node}`); } - const x = octree[node * 5], y = octree[node * 5 + 1], z = octree[node * 5 + 2], - beginChild = octree[node * 5 + 3], endChild = octree[node * 5 + 4]; - if (beginChild < 0 || endChild < 0 || endChild < beginChild || endChild > numNodes || - beginChild + 8 < endChild) { - throw new Error('Invalid child references'); + const x = octree[node * 5], + y = octree[node * 5 + 1], + z = octree[node * 5 + 2], + beginChild = (octree[node * 5 + 3] & 0x7fffffff) >>> 0, + endChild = (octree[node * 5 + 4] & 0x7fffffff) >>> 0; + if ( + beginChild < 0 || + endChild < 0 || + endChild < beginChild || + endChild > numNodes || + (!allowDuplicateChildren && beginChild + 8 < endChild) + ) { + throw new Error( + `Invalid child references: node ${node} specifies beginChild=${beginChild}, endChild=${endChild}`, + ); } for (let child = beginChild; child < endChild; ++child) { - const childX = octree[child * 5], childY = octree[child * 5 + 1], - childZ = octree[child * 5 + 2]; - if ((childX >>> 1) !== x || (childY >>> 1) !== y || (childZ >>> 1) != z) { - throw new Error('invalid child'); + const childX = octree[child * 5], + childY = octree[child * 5 + 1], + childZ = octree[child * 5 + 2]; + if (childX >>> 1 !== x || childY >>> 1 !== y || childZ >>> 1 != z) { + throw new Error( + `invalid child position: parent=${node} child=${child} childX=${childX} childY=${childY} childZ=${childZ} parentX=${x} parentY=${y} parentZ=${z}`, + ); } exploreNode(child); } @@ -303,7 +384,7 @@ export function validateOctree(octree: Uint32Array) { if (numNodes === 0) return; exploreNode(numNodes - 1); if (seenNodes.size !== numNodes) { - throw new Error('Orphan nodes in octree'); + throw new Error("Orphan nodes in octree"); } } diff --git a/src/neuroglancer/object_picking.ts b/src/neuroglancer/object_picking.ts index cf843ee7c..3a995539f 100644 --- a/src/neuroglancer/object_picking.ts +++ b/src/neuroglancer/object_picking.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import {MouseSelectionState} from 'neuroglancer/layer'; -import {RenderLayer} from 'neuroglancer/renderlayer'; -import {Uint64} from 'neuroglancer/util/uint64'; +import { MouseSelectionState } from "neuroglancer/layer"; +import { RenderLayer } from "neuroglancer/renderlayer"; +import { Uint64 } from "neuroglancer/util/uint64"; -const DEBUG_PICKING = false; +const DEBUG_PICKING = true; export class PickIDManager { /** * This specifies the render layer corresponding to each registered entry. */ - private renderLayers: (RenderLayer|null)[] = [null]; + private renderLayers: (RenderLayer | null)[] = [null]; private pickData: any[] = [null]; @@ -51,7 +51,7 @@ export class PickIDManager { } register(renderLayer: RenderLayer, count = 1, low = 0, high = 0, data: any = null): number { - let {renderLayers, values} = this; + let { renderLayers, values } = this; let pickID = this.nextPickID; this.nextPickID += count; let index = renderLayers.length; @@ -69,8 +69,9 @@ export class PickIDManager { */ setMouseState(mouseState: MouseSelectionState, pickID: number) { // Binary search to find largest registered index with a pick ID <= pickID. - const {renderLayers, values} = this; - let lower = 0, upper = renderLayers.length - 1; + const { renderLayers, values } = this; + let lower = 0, + upper = renderLayers.length - 1; while (lower < upper) { const mid = Math.ceil(lower + (upper - lower) / 2); if (values[mid * 3] > pickID) { @@ -79,14 +80,17 @@ export class PickIDManager { lower = mid; } } - const pickedRenderLayer = mouseState.pickedRenderLayer = renderLayers[lower]; + const pickedRenderLayer = (mouseState.pickedRenderLayer = renderLayers[lower]); const valuesOffset = lower * 3; - const pickedOffset = mouseState.pickedOffset = pickID - values[valuesOffset]; + const pickedOffset = (mouseState.pickedOffset = pickID - values[valuesOffset]); if (DEBUG_PICKING) { console.log( - `Looking up pick ID ${pickID}: renderLayer`, pickedRenderLayer, `offset=${pickedOffset}`); + `Looking up pick ID ${pickID}: renderLayer`, + pickedRenderLayer, + `offset=${pickedOffset}`, + ); } - let {pickedValue} = mouseState; + let { pickedValue } = mouseState; pickedValue.low = values[valuesOffset + 1]; pickedValue.high = values[valuesOffset + 2]; mouseState.pickedAnnotationId = undefined; @@ -100,7 +104,8 @@ export class PickIDManager { if (pickedRenderLayer !== null) { if (DEBUG_PICKING) { console.log( - `Picked value=${pickedValue}, offset=${pickedOffset}, data=${this.pickData[lower]}`); + `Picked value=${pickedValue}, offset=${pickedOffset}, data=${this.pickData[lower]}`, + ); } pickedRenderLayer.updateMouseState(mouseState, pickedValue, pickedOffset, data); } diff --git a/src/neuroglancer/util/geom.ts b/src/neuroglancer/util/geom.ts index d53ece138..ea6c4a254 100644 --- a/src/neuroglancer/util/geom.ts +++ b/src/neuroglancer/util/geom.ts @@ -14,20 +14,18 @@ * limitations under the License. */ -import {mat3, mat4, quat, vec3, vec4} from 'gl-matrix'; -import {findMatchingIndices, TypedArray} from 'neuroglancer/util/array'; +import { mat3, mat4, quat, vec3, vec4 } from "gl-matrix"; +import { findMatchingIndices, TypedArray } from "neuroglancer/util/array"; -export {mat2, mat3, mat4, quat, vec2, vec3, vec4} from 'gl-matrix'; +export { mat2, mat3, mat4, quat, vec2, vec3, vec4 } from "gl-matrix"; export const identityMat4 = mat4.create(); -export const AXES_NAMES = ['x', 'y', 'z']; +export const AXES_NAMES = ["x", "y", "z"]; -export const kAxes = [ - vec3.fromValues(1, 0, 0), - vec3.fromValues(0, 1, 0), - vec3.fromValues(0, 0, 1), -]; +const DEBUG = false; + +export const kAxes = [vec3.fromValues(1, 0, 0), vec3.fromValues(0, 1, 0), vec3.fromValues(0, 0, 1)]; export const kZeroVec = vec3.fromValues(0, 0, 0); export const kZeroVec4 = vec4.fromValues(0, 0, 0, 0); export const kOneVec = vec3.fromValues(1, 1, 1); @@ -55,7 +53,10 @@ export function vec3Key(x: ArrayLike) { * Transforms `a` by a 180-degree rotation about X, stores result in `out`. */ export function quatRotateX180(out: quat, a: quat) { - let x = a[0], y = a[1], z = a[2], w = a[3]; + let x = a[0], + y = a[1], + z = a[2], + w = a[3]; out[0] = w; out[1] = z; out[2] = -y; @@ -66,7 +67,10 @@ export function quatRotateX180(out: quat, a: quat) { * Transforms `a` by a 180-degree rotation about Y, stores result in `out`. */ export function quatRotateY180(out: quat, a: quat) { - let x = a[0], y = a[1], z = a[2], w = a[3]; + let x = a[0], + y = a[1], + z = a[2], + w = a[3]; out[0] = -z; out[1] = w; out[2] = x; @@ -77,20 +81,24 @@ export function quatRotateY180(out: quat, a: quat) { * Transforms `a` by a 180-degree rotation about Z, stores result in `out`. */ export function quatRotateZ180(out: quat, a: quat) { - let x = a[0], y = a[1], z = a[2], w = a[3]; + let x = a[0], + y = a[1], + z = a[2], + w = a[3]; out[0] = y; out[1] = -x; out[2] = w; out[3] = -z; } - /** * Transforms a vector `a` by a homogenous transformation matrix `m`. The translation component of * `m` is ignored. */ export function transformVectorByMat4(out: vec3, a: vec3, m: mat4) { - let x = a[0], y = a[1], z = a[2]; + let x = a[0], + y = a[1], + z = a[2]; out[0] = m[0] * x + m[4] * y + m[8] * z; out[1] = m[1] * x + m[5] * y + m[9] * z; out[2] = m[2] * x + m[6] * y + m[10] * z; @@ -102,7 +110,9 @@ export function transformVectorByMat4(out: vec3, a: vec3, m: mat4) { * translation component of `m` is ignored. */ export function transformVectorByMat4Transpose(out: vec3, a: vec3, m: mat4) { - let x = a[0], y = a[1], z = a[2]; + let x = a[0], + y = a[1], + z = a[2]; out[0] = m[0] * x + m[1] * y + m[2] * z; out[1] = m[4] * x + m[5] * y + m[6] * z; out[2] = m[8] * x + m[9] * y + m[10] * z; @@ -110,7 +120,12 @@ export function transformVectorByMat4Transpose(out: vec3, a: vec3, m: mat4) { } export function translationRotationScaleZReflectionToMat4( - out: mat4, translation: vec3, rotation: quat, scale: vec3, zReflection: number) { + out: mat4, + translation: vec3, + rotation: quat, + scale: vec3, + zReflection: number, +) { const temp: Float32Array = out; out[0] = scale[0]; out[1] = scale[1]; @@ -122,7 +137,10 @@ export function translationRotationScaleZReflectionToMat4( * Returns the value of `t` that minimizes `(p - (a + t * (b - a)))`. */ export function findClosestParameterizedLinePosition( - a: Float32Array, b: Float32Array, p: Float32Array) { + a: Float32Array, + b: Float32Array, + p: Float32Array, +) { // http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html // Compute t: -dot(a-p, b-a) / |b - a|^2 const rank = p.length; @@ -142,7 +160,11 @@ export function findClosestParameterizedLinePosition( * Sets `out` to the position on the line segment `[a, b]` closest to `p`. */ export function projectPointToLineSegment( - out: Float32Array, a: Float32Array, b: Float32Array, p: Float32Array) { + out: Float32Array, + a: Float32Array, + b: Float32Array, + p: Float32Array, +) { const rank = out.length; let t = findClosestParameterizedLinePosition(a, b, p); t = Math.max(0.0, Math.min(1.0, t)); @@ -154,8 +176,15 @@ export function projectPointToLineSegment( } export function mat3FromMat4(out: mat3, m: mat4) { - const m00 = m[0], m01 = m[1], m02 = m[2], m10 = m[4], m11 = m[5], m12 = m[6], m20 = m[8], - m21 = m[9], m22 = m[10]; + const m00 = m[0], + m01 = m[1], + m02 = m[2], + m10 = m[4], + m11 = m[5], + m12 = m[6], + m20 = m[8], + m21 = m[9], + m22 = m[10]; out[0] = m00; out[1] = m01; out[2] = m02; @@ -178,39 +207,52 @@ export function mat3FromMat4(out: mat3, m: mat4) { */ export function getFrustrumPlanes(out: Float32Array, m: mat4): Float32Array { // http://web.archive.org/web/20120531231005/http://crazyjoke.free.fr/doc/3D/plane%20extraction.pdf - const m00 = m[0], m10 = m[1], m20 = m[2], m30 = m[3], m01 = m[4], m11 = m[5], m21 = m[6], - m31 = m[7], m02 = m[8], m12 = m[9], m22 = m[10], m32 = m[11], m03 = m[12], m13 = m[13], - m23 = m[14], m33 = m[15]; - - out[0] = m30 + m00; // left: a - out[1] = m31 + m01; // left: b - out[2] = m32 + m02; // left: c - out[3] = m33 + m03; // left: d - - out[4] = m30 - m00; // right: a - out[5] = m31 - m01; // right: b - out[6] = m32 - m02; // right: c - out[7] = m33 - m03; // right: d - - out[8] = m30 + m10; // bottom: a - out[9] = m31 + m11; // bottom: b - out[10] = m32 + m12; // bottom: c - out[11] = m33 + m13; // bottom: d - - out[12] = m30 - m10; // top: a - out[13] = m31 - m11; // top: b - out[14] = m32 - m12; // top: c - out[15] = m33 - m13; // top: d - - const nearA = m30 + m20; // near: a - const nearB = m31 + m21; // near: b - const nearC = m32 + m22; // near: c - const nearD = m33 + m23; // near: d - - const farA = m30 - m20; // far: a - const farB = m31 - m21; // far: b - const farC = m32 - m22; // far: c - const farD = m33 - m23; // far: d + const m00 = m[0], + m10 = m[1], + m20 = m[2], + m30 = m[3], + m01 = m[4], + m11 = m[5], + m21 = m[6], + m31 = m[7], + m02 = m[8], + m12 = m[9], + m22 = m[10], + m32 = m[11], + m03 = m[12], + m13 = m[13], + m23 = m[14], + m33 = m[15]; + + out[0] = m30 + m00; // left: a + out[1] = m31 + m01; // left: b + out[2] = m32 + m02; // left: c + out[3] = m33 + m03; // left: d + + out[4] = m30 - m00; // right: a + out[5] = m31 - m01; // right: b + out[6] = m32 - m02; // right: c + out[7] = m33 - m03; // right: d + + out[8] = m30 + m10; // bottom: a + out[9] = m31 + m11; // bottom: b + out[10] = m32 + m12; // bottom: c + out[11] = m33 + m13; // bottom: d + + out[12] = m30 - m10; // top: a + out[13] = m31 - m11; // top: b + out[14] = m32 - m12; // top: c + out[15] = m33 - m13; // top: d + + const nearA = m30 + m20; // near: a + const nearB = m31 + m21; // near: b + const nearC = m32 + m22; // near: c + const nearD = m33 + m23; // near: d + + const farA = m30 - m20; // far: a + const farB = m31 - m21; // far: b + const farC = m32 - m22; // far: c + const farD = m33 - m23; // far: d // Normalize near plane const nearNorm = Math.sqrt(nearA ** 2 + nearB ** 2 + nearC ** 2); @@ -236,14 +278,30 @@ export function getFrustrumPlanes(out: Float32Array, m: mat4): Float32Array { * computed by `getFrustrumPlanes` */ export function isAABBVisible( - xLower: number, yLower: number, zLower: number, xUpper: number, yUpper: number, zUpper: number, - clippingPlanes: Float32Array) { + xLower: number, + yLower: number, + zLower: number, + xUpper: number, + yUpper: number, + zUpper: number, + clippingPlanes: Float32Array, +) { for (let i = 0; i < 6; ++i) { - const a = clippingPlanes[i * 4], b = clippingPlanes[i * 4 + 1], c = clippingPlanes[i * 4 + 2], - d = clippingPlanes[i * 4 + 3]; - const sum = Math.max(a * xLower, a * xUpper) + Math.max(b * yLower, b * yUpper) + - Math.max(c * zLower, c * zUpper) + d; + const a = clippingPlanes[i * 4], + b = clippingPlanes[i * 4 + 1], + c = clippingPlanes[i * 4 + 2], + d = clippingPlanes[i * 4 + 3]; + const sum = + Math.max(a * xLower, a * xUpper) + + Math.max(b * yLower, b * yUpper) + + Math.max(c * zLower, c * zUpper) + + d; if (sum < 0) { + if (DEBUG) { + console.log( + `aabb not visible due to plane ${i}, a=${a}, b=${b}, c=${c}, d=${d}, sum=${sum}`, + ); + } return false; } } @@ -251,39 +309,58 @@ export function isAABBVisible( } export function isAABBIntersectingPlane( - xLower: number, yLower: number, zLower: number, xUpper: number, yUpper: number, zUpper: number, - clippingPlanes: Float32Array) { + xLower: number, + yLower: number, + zLower: number, + xUpper: number, + yUpper: number, + zUpper: number, + clippingPlanes: Float32Array, +) { for (let i = 0; i < 4; ++i) { - const a = clippingPlanes[i * 4], b = clippingPlanes[i * 4 + 1], c = clippingPlanes[i * 4 + 2], - d = clippingPlanes[i * 4 + 3]; - const sum = Math.max(a * xLower, a * xUpper) + Math.max(b * yLower, b * yUpper) + - Math.max(c * zLower, c * zUpper) + d; + const a = clippingPlanes[i * 4], + b = clippingPlanes[i * 4 + 1], + c = clippingPlanes[i * 4 + 2], + d = clippingPlanes[i * 4 + 3]; + const sum = + Math.max(a * xLower, a * xUpper) + + Math.max(b * yLower, b * yUpper) + + Math.max(c * zLower, c * zUpper) + + d; if (sum < 0) { return false; } } { const i = 5; - const a = clippingPlanes[i * 4], b = clippingPlanes[i * 4 + 1], c = clippingPlanes[i * 4 + 2], - d = clippingPlanes[i * 4 + 3]; - const maxSum = Math.max(a * xLower, a * xUpper) + Math.max(b * yLower, b * yUpper) + - Math.max(c * zLower, c * zUpper); - const minSum = Math.min(a * xLower, a * xUpper) + Math.min(b * yLower, b * yUpper) + - Math.min(c * zLower, c * zUpper); + const a = clippingPlanes[i * 4], + b = clippingPlanes[i * 4 + 1], + c = clippingPlanes[i * 4 + 2], + d = clippingPlanes[i * 4 + 3]; + const maxSum = + Math.max(a * xLower, a * xUpper) + + Math.max(b * yLower, b * yUpper) + + Math.max(c * zLower, c * zUpper); + const minSum = + Math.min(a * xLower, a * xUpper) + + Math.min(b * yLower, b * yUpper) + + Math.min(c * zLower, c * zUpper); const epsilon = Math.abs(d) * 1e-6; if (minSum > -d + epsilon || maxSum < -d - epsilon) return false; } return true; } - /** * Returns the list (in sorted order) of input dimensions that depend on any of the specified output * dimensions. */ export function getDependentTransformInputDimensions( - transform: Float32Array|Float64Array, rank: number, outputDimensions: readonly number[], - transpose: boolean = false): number[] { + transform: Float32Array | Float64Array, + rank: number, + outputDimensions: readonly number[], + transpose: boolean = false, +): number[] { const numOutputDimensions = outputDimensions.length; const isDependentInputDimension: boolean[] = []; const inputStride = transpose ? 1 : rank + 1; @@ -332,11 +409,11 @@ export function getViewFrustrumVolume(projectionMat: mat4) { // b = 2 * far * near / (near - far); const a = projectionMat[10]; const b = projectionMat[14]; - const near = 2 * b / (2 * a - 2); + const near = (2 * b) / (2 * a - 2); const far = ((a - 1) * near) / (a + 1); const baseArea = 4 / (projectionMat[0] * projectionMat[5]); - return baseArea / 3 * (Math.abs(far) ** 3 - Math.abs(near) ** 3); + return (baseArea / 3) * (Math.abs(far) ** 3 - Math.abs(near) ** 3); } export function getViewFrustrumDepthRange(projectionMat: mat4) { @@ -350,7 +427,7 @@ export function getViewFrustrumDepthRange(projectionMat: mat4) { // b = 2 * far * near / (near - far); const a = projectionMat[10]; const b = projectionMat[14]; - const near = 2 * b / (2 * a - 2); + const near = (2 * b) / (2 * a - 2); const far = ((a - 1) * near) / (a + 1); const depth = Math.abs(far - near); return depth; @@ -371,8 +448,7 @@ const tempVec3 = vec3.create(); // matrix. // // https://gamedev.stackexchange.com/questions/29999/how-do-i-create-a-bounding-frustum-from-a-view-projection-matrix -export function getViewFrustrumWorldBounds( - invViewProjectionMat: mat4, bounds: Float32Array) { +export function getViewFrustrumWorldBounds(invViewProjectionMat: mat4, bounds: Float32Array) { bounds[0] = bounds[1] = bounds[2] = Number.POSITIVE_INFINITY; bounds[3] = bounds[4] = bounds[5] = Number.NEGATIVE_INFINITY; for (let i = 0; i < 8; ++i) {