diff --git a/src/mol-geo/geometry/cylinders/cylinders-builder.ts b/src/mol-geo/geometry/cylinders/cylinders-builder.ts new file mode 100644 index 0000000000000000000000000000000000000000..d398e59621b32a7f7340adfb2895c0fa0b80b892 --- /dev/null +++ b/src/mol-geo/geometry/cylinders/cylinders-builder.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ChunkedArray } from '../../../mol-data/util'; +import { Cylinders } from './cylinders'; +import { Vec3 } from '../../../mol-math/linear-algebra'; + +export interface CylindersBuilder { + add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number): void + addFixedCountDashes(start: Vec3, end: Vec3, segmentCount: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number): void + addFixedLengthDashes(start: Vec3, end: Vec3, segmentLength: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number): void + getCylinders(): Cylinders +} + +const tmpVecA = Vec3(); +const tmpVecB = Vec3(); +const tmpDir = Vec3(); + +// avoiding namespace lookup improved performance in Chrome (Aug 2020) +const caAdd = ChunkedArray.add; +const caAdd3 = ChunkedArray.add3; + +export namespace CylindersBuilder { + export function create(initialCount = 2048, chunkSize = 1024, cylinders?: Cylinders): CylindersBuilder { + const groups = ChunkedArray.create(Float32Array, 1, chunkSize, cylinders ? cylinders.groupBuffer.ref.value : initialCount); + const starts = ChunkedArray.create(Float32Array, 3, chunkSize, cylinders ? cylinders.startBuffer.ref.value : initialCount); + const ends = ChunkedArray.create(Float32Array, 3, chunkSize, cylinders ? cylinders.endBuffer.ref.value : initialCount); + const scales = ChunkedArray.create(Float32Array, 1, chunkSize, cylinders ? cylinders.scaleBuffer.ref.value : initialCount); + const caps = ChunkedArray.create(Float32Array, 1, chunkSize, cylinders ? cylinders.capBuffer.ref.value : initialCount); + + const add = (startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number) => { + for (let i = 0; i < 6; ++i) { + caAdd3(starts, startX, startY, startZ); + caAdd3(ends, endX, endY, endZ); + caAdd(groups, group); + caAdd(scales, radiusScale); + caAdd(caps, (topCap ? 1 : 0) + (bottomCap ? 2 : 0)); + } + }; + + const addFixedCountDashes = (start: Vec3, end: Vec3, segmentCount: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number) => { + const d = Vec3.distance(start, end); + const s = Math.floor(segmentCount / 2); + const step = 1 / segmentCount; + + Vec3.sub(tmpDir, end, start); + for (let j = 0; j < s; ++j) { + const f = step * (j * 2 + 1); + Vec3.setMagnitude(tmpDir, tmpDir, d * f); + Vec3.add(tmpVecA, start, tmpDir); + Vec3.setMagnitude(tmpDir, tmpDir, d * step * ((j + 1) * 2)); + Vec3.add(tmpVecB, start, tmpDir); + add(tmpVecA[0], tmpVecA[1], tmpVecA[2], tmpVecB[0], tmpVecB[1], tmpVecB[2], radiusScale, topCap, bottomCap, group); + } + }; + + return { + add, + addFixedCountDashes, + addFixedLengthDashes: (start: Vec3, end: Vec3, segmentLength: number, radiusScale: number, topCap: boolean, bottomCap: boolean, group: number) => { + const d = Vec3.distance(start, end); + addFixedCountDashes(start, end, d / segmentLength, radiusScale, topCap, bottomCap, group); + }, + getCylinders: () => { + const cylinderCount = groups.elementCount / 6; + const gb = ChunkedArray.compact(groups, true) as Float32Array; + const sb = ChunkedArray.compact(starts, true) as Float32Array; + const eb = ChunkedArray.compact(ends, true) as Float32Array; + const ab = ChunkedArray.compact(scales, true) as Float32Array; + const cb = ChunkedArray.compact(caps, true) as Float32Array; + const mb = cylinders && cylinderCount <= cylinders.cylinderCount ? cylinders.mappingBuffer.ref.value : new Float32Array(cylinderCount * 18); + const ib = cylinders && cylinderCount <= cylinders.cylinderCount ? cylinders.indexBuffer.ref.value : new Uint32Array(cylinderCount * 12); + if (!cylinders || cylinderCount > cylinders.cylinderCount) fillMappingAndIndices(cylinderCount, mb, ib); + return Cylinders.create(mb, ib, gb, sb, eb, ab, cb, cylinderCount, cylinders); + } + }; + } +} + +function fillMappingAndIndices(n: number, mb: Float32Array, ib: Uint32Array) { + for (let i = 0; i < n; ++i) { + const mo = i * 18; + mb[mo] = -1; mb[mo + 1] = 1; mb[mo + 2] = -1; + mb[mo + 3] = -1; mb[mo + 4] = -1; mb[mo + 5] = -1; + mb[mo + 6] = 1; mb[mo + 7] = 1; mb[mo + 8] = -1; + mb[mo + 9] = 1; mb[mo + 10] = 1; mb[mo + 11] = 1; + mb[mo + 12] = 1; mb[mo + 13] = -1; mb[mo + 14] = -1; + mb[mo + 15] = 1; mb[mo + 16] = -1; mb[mo + 17] = 1; + } + + for (let i = 0; i < n; ++i) { + const o = i * 6; + const io = i * 12; + ib[io] = o; ib[io + 1] = o + 1; ib[io + 2] = o + 2; + ib[io + 3] = o + 1; ib[io + 4] = o + 4; ib[io + 5] = o + 2; + ib[io + 6] = o + 2; ib[io + 7] = o + 4; ib[io + 8] = o + 3; + ib[io + 9] = o + 4; ib[io + 10] = o + 5; ib[io + 11] = o + 3; + } +} diff --git a/src/mol-geo/geometry/cylinders/cylinders.ts b/src/mol-geo/geometry/cylinders/cylinders.ts new file mode 100644 index 0000000000000000000000000000000000000000..242ada6fba5924a218ee4d12ba84a8821b703197 --- /dev/null +++ b/src/mol-geo/geometry/cylinders/cylinders.ts @@ -0,0 +1,278 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ValueCell } from '../../../mol-util'; +import { Mat4, Vec3, Vec4 } from '../../../mol-math/linear-algebra'; +import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util'; +import { GeometryUtils } from '../geometry'; +import { createColors } from '../color-data'; +import { createMarkers } from '../marker-data'; +import { createSizes, getMaxSize } from '../size-data'; +import { TransformData } from '../transform-data'; +import { LocationIterator, PositionLocation } from '../../util/location-iterator'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util'; +import { Sphere3D } from '../../../mol-math/geometry'; +import { Theme } from '../../../mol-theme/theme'; +import { Color } from '../../../mol-util/color'; +import { BaseGeometry } from '../base'; +import { createEmptyOverpaint } from '../overpaint-data'; +import { createEmptyTransparency } from '../transparency-data'; +import { hashFnv32a } from '../../../mol-data/util'; +import { createEmptyClipping } from '../clipping-data'; +import { CylindersValues } from '../../../mol-gl/renderable/cylinders'; +import { RenderableState } from '../../../mol-gl/renderable'; + +export interface Cylinders { + readonly kind: 'cylinders', + + /** Number of cylinders */ + cylinderCount: number, + + /** Mapping buffer as array of uvw values wrapped in a value cell */ + readonly mappingBuffer: ValueCell<Float32Array>, + /** Index buffer as array of vertex index triplets wrapped in a value cell */ + readonly indexBuffer: ValueCell<Uint32Array>, + /** Group buffer as array of group ids for each vertex wrapped in a value cell */ + readonly groupBuffer: ValueCell<Float32Array>, + /** Cylinder start buffer as array of xyz values wrapped in a value cell */ + readonly startBuffer: ValueCell<Float32Array>, + /** Cylinder end buffer as array of xyz values wrapped in a value cell */ + readonly endBuffer: ValueCell<Float32Array>, + /** Cylinder scale buffer as array of scaling factors wrapped in a value cell */ + readonly scaleBuffer: ValueCell<Float32Array>, + /** Cylinder cap buffer as array of cap flags wrapped in a value cell */ + readonly capBuffer: ValueCell<Float32Array>, + + /** Bounding sphere of the cylinders */ + readonly boundingSphere: Sphere3D + /** Maps group ids to cylinder indices */ + readonly groupMapping: GroupMapping + + setBoundingSphere(boundingSphere: Sphere3D): void +} + +export namespace Cylinders { + export function create(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, scales: Float32Array, caps: Float32Array, cylinderCount: number, cylinders?: Cylinders): Cylinders { + return cylinders ? + update(mappings, indices, groups, starts, ends, scales, caps, cylinderCount, cylinders) : + fromArrays(mappings, indices, groups, starts, ends, scales, caps, cylinderCount); + } + + export function createEmpty(cylinders?: Cylinders): Cylinders { + const mb = cylinders ? cylinders.mappingBuffer.ref.value : new Float32Array(0); + const ib = cylinders ? cylinders.indexBuffer.ref.value : new Uint32Array(0); + const gb = cylinders ? cylinders.groupBuffer.ref.value : new Float32Array(0); + const sb = cylinders ? cylinders.startBuffer.ref.value : new Float32Array(0); + const eb = cylinders ? cylinders.endBuffer.ref.value : new Float32Array(0); + const ab = cylinders ? cylinders.scaleBuffer.ref.value : new Float32Array(0); + const cb = cylinders ? cylinders.capBuffer.ref.value : new Float32Array(0); + return create(mb, ib, gb, sb, eb, ab, cb, 0, cylinders); + } + + function hashCode(cylinders: Cylinders) { + return hashFnv32a([ + cylinders.cylinderCount, cylinders.mappingBuffer.ref.version, cylinders.indexBuffer.ref.version, + cylinders.groupBuffer.ref.version, cylinders.startBuffer.ref.version, cylinders.endBuffer.ref.version, cylinders.scaleBuffer.ref.version, cylinders.capBuffer.ref.version + ]); + } + + function fromArrays(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, scales: Float32Array, caps: Float32Array, cylinderCount: number): Cylinders { + + const boundingSphere = Sphere3D(); + let groupMapping: GroupMapping; + + let currentHash = -1; + let currentGroup = -1; + + const cylinders = { + kind: 'cylinders' as const, + cylinderCount, + mappingBuffer: ValueCell.create(mappings), + indexBuffer: ValueCell.create(indices), + groupBuffer: ValueCell.create(groups), + startBuffer: ValueCell.create(starts), + endBuffer: ValueCell.create(ends), + scaleBuffer: ValueCell.create(scales), + capBuffer: ValueCell.create(caps), + get boundingSphere() { + const newHash = hashCode(cylinders); + if (newHash !== currentHash) { + const s = calculateInvariantBoundingSphere(cylinders.startBuffer.ref.value, cylinders.cylinderCount * 6, 6); + const e = calculateInvariantBoundingSphere(cylinders.endBuffer.ref.value, cylinders.cylinderCount * 6, 6); + + Sphere3D.expandBySphere(boundingSphere, s, e); + currentHash = newHash; + } + return boundingSphere; + }, + get groupMapping() { + if (cylinders.groupBuffer.ref.version !== currentGroup) { + groupMapping = createGroupMapping(cylinders.groupBuffer.ref.value, cylinders.cylinderCount, 6); + currentGroup = cylinders.groupBuffer.ref.version; + } + return groupMapping; + }, + setBoundingSphere(sphere: Sphere3D) { + Sphere3D.copy(boundingSphere, sphere); + currentHash = hashCode(cylinders); + } + }; + return cylinders; + } + + function update(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, scales: Float32Array, caps: Float32Array, cylinderCount: number, cylinders: Cylinders) { + if (cylinderCount > cylinders.cylinderCount) { + ValueCell.update(cylinders.mappingBuffer, mappings); + ValueCell.update(cylinders.indexBuffer, indices); + } + cylinders.cylinderCount = cylinderCount; + ValueCell.update(cylinders.groupBuffer, groups); + ValueCell.update(cylinders.startBuffer, starts); + ValueCell.update(cylinders.endBuffer, ends); + ValueCell.update(cylinders.scaleBuffer, scales); + ValueCell.update(cylinders.capBuffer, caps); + return cylinders; + } + + export function transform(cylinders: Cylinders, t: Mat4) { + const start = cylinders.startBuffer.ref.value; + transformPositionArray(t, start, 0, cylinders.cylinderCount * 6); + ValueCell.update(cylinders.startBuffer, start); + const end = cylinders.endBuffer.ref.value; + transformPositionArray(t, end, 0, cylinders.cylinderCount * 6); + ValueCell.update(cylinders.endBuffer, end); + } + + // + + export const Params = { + ...BaseGeometry.Params, + sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }), + sizeAspectRatio: PD.Numeric(1, { min: 0, max: 3, step: 0.01 }), + doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo), + ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory), + xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory), + }; + export type Params = typeof Params + + export const Utils: GeometryUtils<Cylinders, Params> = { + Params, + createEmpty, + createValues, + createValuesSimple, + updateValues, + updateBoundingSphere, + createRenderableState, + updateRenderableState, + createPositionIterator + }; + + function createPositionIterator(cylinders: Cylinders, transform: TransformData): LocationIterator { + const groupCount = cylinders.cylinderCount * 6; + const instanceCount = transform.instanceCount.ref.value; + const location = PositionLocation(); + const p = location.position; + const s = cylinders.startBuffer.ref.value; + const e = cylinders.endBuffer.ref.value; + const m = transform.aTransform.ref.value; + const getLocation = (groupIndex: number, instanceIndex: number) => { + const v = groupIndex % 6 === 0 ? s : e; + if (instanceIndex < 0) { + Vec3.fromArray(p, v, groupIndex * 3); + } else { + Vec3.transformMat4Offset(p, v, m, 0, groupIndex * 3, instanceIndex * 16); + } + return location; + }; + return LocationIterator(groupCount, instanceCount, 2, getLocation); + } + + function createValues(cylinders: Cylinders, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): CylindersValues { + const { instanceCount, groupCount } = locationIt; + const positionIt = createPositionIterator(cylinders, transform); + + const color = createColors(locationIt, positionIt, theme.color); + const size = createSizes(locationIt, theme.size); + const marker = createMarkers(instanceCount * groupCount); + const overpaint = createEmptyOverpaint(); + const transparency = createEmptyTransparency(); + const clipping = createEmptyClipping(); + + const counts = { drawCount: cylinders.cylinderCount * 4 * 3, vertexCount: cylinders.cylinderCount * 6, groupCount, instanceCount }; + + const padding = getMaxSize(size) * props.sizeFactor; + const invariantBoundingSphere = Sphere3D.clone(cylinders.boundingSphere); + const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount); + + return { + aMapping: cylinders.mappingBuffer, + aGroup: cylinders.groupBuffer, + aStart: cylinders.startBuffer, + aEnd: cylinders.endBuffer, + aScale: cylinders.scaleBuffer, + aCap: cylinders.capBuffer, + elements: cylinders.indexBuffer, + boundingSphere: ValueCell.create(boundingSphere), + invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), + ...color, + ...size, + ...marker, + ...overpaint, + ...transparency, + ...clipping, + ...transform, + + padding: ValueCell.create(padding), + + ...BaseGeometry.createValues(props, counts), + uSizeFactor: ValueCell.create(props.sizeFactor * props.sizeAspectRatio), + dDoubleSided: ValueCell.create(props.doubleSided), + dIgnoreLight: ValueCell.create(props.ignoreLight), + dXrayShaded: ValueCell.create(props.xrayShaded), + }; + } + + function createValuesSimple(cylinders: Cylinders, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) { + const s = BaseGeometry.createSimple(colorValue, sizeValue, transform); + const p = { ...PD.getDefaultValues(Params), ...props }; + return createValues(cylinders, s.transform, s.locationIterator, s.theme, p); + } + + function updateValues(values: CylindersValues, props: PD.Values<Params>) { + BaseGeometry.updateValues(values, props); + ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor * props.sizeAspectRatio); + ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided); + ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight); + ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded); + } + + function updateBoundingSphere(values: CylindersValues, cylinders: Cylinders) { + const invariantBoundingSphere = Sphere3D.clone(cylinders.boundingSphere); + const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value); + + if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) { + ValueCell.update(values.boundingSphere, boundingSphere); + } + if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { + ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere); + ValueCell.update(values.uInvariantBoundingSphere, Vec4.fromSphere(values.uInvariantBoundingSphere.ref.value, invariantBoundingSphere)); + } + } + + function createRenderableState(props: PD.Values<Params>): RenderableState { + const state = BaseGeometry.createRenderableState(props); + updateRenderableState(state, props); + return state; + } + + function updateRenderableState(state: RenderableState, props: PD.Values<Params>) { + BaseGeometry.updateRenderableState(state, props); + state.opaque = state.opaque && !props.xrayShaded; + state.writeDepth = state.opaque; + } +} \ No newline at end of file diff --git a/src/mol-geo/geometry/geometry.ts b/src/mol-geo/geometry/geometry.ts index 8a0993ee97af21237d6709092ab03012b8faffc2..44c3fd9ceb39613c7c71f3b48c18f1b638b97055 100644 --- a/src/mol-geo/geometry/geometry.ts +++ b/src/mol-geo/geometry/geometry.ts @@ -22,28 +22,31 @@ import { Theme } from '../../mol-theme/theme'; import { RenderObjectValues } from '../../mol-gl/render-object'; import { TextureMesh } from './texture-mesh/texture-mesh'; import { Image } from './image/image'; +import { Cylinders } from './cylinders/cylinders'; -export type GeometryKind = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh' +export type GeometryKind = 'mesh' | 'points' | 'spheres' | 'cylinders' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh' export type Geometry<T extends GeometryKind = GeometryKind> = T extends 'mesh' ? Mesh : T extends 'points' ? Points : T extends 'spheres' ? Spheres : - T extends 'text' ? Text : - T extends 'lines' ? Lines : - T extends 'direct-volume' ? DirectVolume : - T extends 'image' ? Image : - T extends 'texture-mesh' ? TextureMesh : never + T extends 'cylinders' ? Cylinders : + T extends 'text' ? Text : + T extends 'lines' ? Lines : + T extends 'direct-volume' ? DirectVolume : + T extends 'image' ? Image : + T extends 'texture-mesh' ? TextureMesh : never type GeometryParams<T extends GeometryKind> = T extends 'mesh' ? Mesh.Params : T extends 'points' ? Points.Params : T extends 'spheres' ? Spheres.Params : - T extends 'text' ? Text.Params : - T extends 'lines' ? Lines.Params : - T extends 'direct-volume' ? DirectVolume.Params : - T extends 'image' ? Image.Params : - T extends 'texture-mesh' ? TextureMesh.Params : never + T extends 'cylinders' ? Cylinders.Params : + T extends 'text' ? Text.Params : + T extends 'lines' ? Lines.Params : + T extends 'direct-volume' ? DirectVolume.Params : + T extends 'image' ? Image.Params : + T extends 'texture-mesh' ? TextureMesh.Params : never export interface GeometryUtils<G extends Geometry, P extends PD.Params = GeometryParams<G['kind']>, V = RenderObjectValues<G['kind']>> { Params: P @@ -65,6 +68,7 @@ export namespace Geometry { case 'mesh': return geometry.triangleCount * 3; case 'points': return geometry.pointCount; case 'spheres': return geometry.sphereCount * 2 * 3; + case 'cylinders': return geometry.cylinderCount * 4 * 3; case 'text': return geometry.charCount * 2 * 3; case 'lines': return geometry.lineCount * 2 * 3; case 'direct-volume': return 12 * 3; @@ -78,6 +82,7 @@ export namespace Geometry { case 'mesh': return geometry.vertexCount; case 'points': return geometry.pointCount; case 'spheres': return geometry.sphereCount * 4; + case 'cylinders': return geometry.cylinderCount * 6; case 'text': return geometry.charCount * 4; case 'lines': return geometry.lineCount * 4; case 'direct-volume': @@ -93,6 +98,7 @@ export namespace Geometry { case 'mesh': case 'points': case 'spheres': + case 'cylinders': case 'text': case 'lines': return getDrawCount(geometry) === 0 ? 0 : (arrayMax(geometry.groupBuffer.ref.value) + 1); @@ -111,6 +117,7 @@ export namespace Geometry { case 'mesh': return Mesh.Utils as any; case 'points': return Points.Utils as any; case 'spheres': return Spheres.Utils as any; + case 'cylinders': return Cylinders.Utils as any; case 'text': return Text.Utils as any; case 'lines': return Lines.Utils as any; case 'direct-volume': return DirectVolume.Utils as any; diff --git a/src/mol-gl/render-object.ts b/src/mol-gl/render-object.ts index 9319243e6134d37cd83a5c974b12e0c62f7f6428..058ccb63febe472619e8dfbe9c26aefc6ad94004 100644 --- a/src/mol-gl/render-object.ts +++ b/src/mol-gl/render-object.ts @@ -15,6 +15,7 @@ import { SpheresValues, SpheresRenderable } from './renderable/spheres'; import { TextValues, TextRenderable } from './renderable/text'; import { TextureMeshValues, TextureMeshRenderable } from './renderable/texture-mesh'; import { ImageValues, ImageRenderable } from './renderable/image'; +import { CylindersRenderable, CylindersValues } from './renderable/cylinders'; const getNextId = idFactory(0, 0x7FFFFFFF); @@ -28,17 +29,18 @@ export interface GraphicsRenderObject<T extends RenderObjectType = RenderObjectT readonly materialId: number } -export type RenderObjectType = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh' +export type RenderObjectType = 'mesh' | 'points' | 'spheres' | 'cylinders' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh' export type RenderObjectValues<T extends RenderObjectType> = T extends 'mesh' ? MeshValues : T extends 'points' ? PointsValues : T extends 'spheres' ? SpheresValues : - T extends 'text' ? TextValues : - T extends 'lines' ? LinesValues : - T extends 'direct-volume' ? DirectVolumeValues : - T extends 'image' ? ImageValues : - T extends 'texture-mesh' ? TextureMeshValues : never + T extends 'cylinders' ? CylindersValues : + T extends 'text' ? TextValues : + T extends 'lines' ? LinesValues : + T extends 'direct-volume' ? DirectVolumeValues : + T extends 'image' ? ImageValues : + T extends 'texture-mesh' ? TextureMeshValues : never // @@ -51,6 +53,7 @@ export function createRenderable<T extends RenderObjectType>(ctx: WebGLContext, case 'mesh': return MeshRenderable(ctx, o.id, o.values as MeshValues, o.state, o.materialId); case 'points': return PointsRenderable(ctx, o.id, o.values as PointsValues, o.state, o.materialId); case 'spheres': return SpheresRenderable(ctx, o.id, o.values as SpheresValues, o.state, o.materialId); + case 'cylinders': return CylindersRenderable(ctx, o.id, o.values as CylindersValues, o.state, o.materialId); case 'text': return TextRenderable(ctx, o.id, o.values as TextValues, o.state, o.materialId); case 'lines': return LinesRenderable(ctx, o.id, o.values as LinesValues, o.state, o.materialId); case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values as DirectVolumeValues, o.state, o.materialId); diff --git a/src/mol-gl/renderable/cylinders.ts b/src/mol-gl/renderable/cylinders.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d4c260e6103d432274f450c38897e8dc79fb329 --- /dev/null +++ b/src/mol-gl/renderable/cylinders.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Renderable, RenderableState, createRenderable } from '../renderable'; +import { WebGLContext } from '../webgl/context'; +import { createGraphicsRenderItem } from '../webgl/render-item'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema } from './schema'; +import { CylindersShaderCode } from '../shader-code'; +import { ValueCell } from '../../mol-util'; + +export const CylindersSchema = { + ...BaseSchema, + ...SizeSchema, + aStart: AttributeSpec('float32', 3, 0), + aEnd: AttributeSpec('float32', 3, 0), + aMapping: AttributeSpec('float32', 3, 0), + aScale: AttributeSpec('float32', 1, 0), + aCap: AttributeSpec('float32', 1, 0), + elements: ElementsSpec('uint32'), + + padding: ValueSpec('number'), + dDoubleSided: DefineSpec('boolean'), + dIgnoreLight: DefineSpec('boolean'), + dXrayShaded: DefineSpec('boolean'), +}; +export type CylindersSchema = typeof CylindersSchema +export type CylindersValues = Values<CylindersSchema> + +export function CylindersRenderable(ctx: WebGLContext, id: number, values: CylindersValues, state: RenderableState, materialId: number): Renderable<CylindersValues> { + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...CylindersSchema }; + const internalValues: InternalValues = { + uObjectId: ValueCell.create(id), + }; + const shaderCode = CylindersShaderCode; + const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId); + return createRenderable(renderItem, values, state); +} \ No newline at end of file diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 4c2593b2c4ffb4c1ba9d61ca5ffe65685a52b36d..4165880d15e14dd6e168c18ee8a3a9f41eec55c5 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -117,6 +117,8 @@ export function ShaderCode(name: string, vert: string, frag: string, extensions: return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions }; } +// Note: `drawBuffers` need to be 'optional' for wboit + import points_vert from './shader/points.vert'; import points_frag from './shader/points.frag'; export const PointsShaderCode = ShaderCode('points', points_vert, points_frag, { drawBuffers: 'optional' }); @@ -125,6 +127,10 @@ import spheres_vert from './shader/spheres.vert'; import spheres_frag from './shader/spheres.frag'; export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: 'required', drawBuffers: 'optional' }); +import cylinders_vert from './shader/cylinders.vert'; +import cylinders_frag from './shader/cylinders.frag'; +export const CylindersShaderCode = ShaderCode('cylinders', cylinders_vert, cylinders_frag, { fragDepth: 'required', drawBuffers: 'optional' }); + import text_vert from './shader/text.vert'; import text_frag from './shader/text.frag'; export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required', drawBuffers: 'optional' }); diff --git a/src/mol-gl/shader/cylinders.frag.ts b/src/mol-gl/shader/cylinders.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..bfa204d53c292d6c3ab030b9dafdd37d1ab04af4 --- /dev/null +++ b/src/mol-gl/shader/cylinders.frag.ts @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export default ` +precision highp float; +precision highp int; + +uniform mat4 uView; + +varying mat4 vTransform; +varying vec3 vStart; +varying vec3 vEnd; +varying float vSize; +varying float vCap; + +uniform vec3 uCameraDir; +uniform float uIsOrtho; +uniform vec3 uCameraPosition; + +#include common +#include common_frag_params +#include color_frag_params +#include light_frag_params +#include common_clip +#include wboit_params + +// adapted from https://www.shadertoy.com/view/4lcSRn +// The MIT License, Copyright 2016 Inigo Quilez +bool CylinderImpostor( + in vec3 rayOrigin, in vec3 rayDir, + in vec3 start, in vec3 end, in float radius, + out vec4 intersection, out bool interior +){ + vec3 ba = end - start; + vec3 oc = rayOrigin - start; + + float baba = dot(ba, ba); + float bard = dot(ba, rayDir); + float baoc = dot(ba, oc); + + float k2 = baba - bard*bard; + float k1 = baba * dot(oc, rayDir) - baoc * bard; + float k0 = baba * dot(oc, oc) - baoc * baoc - radius * radius * baba; + + float h = k1 * k1 - k2 * k0; + if (h < 0.0) return false; + + bool topCap = (vCap > 0.9 && vCap < 1.1) || vCap >= 2.9; + bool bottomCap = (vCap > 1.9 && vCap < 2.1) || vCap >= 2.9; + + // body outside + h = sqrt(h); + float t = (-k1 - h) / k2; + float y = baoc + t * bard; + if (y > 0.0 && y < baba) { + interior = false; + intersection = vec4(t, (oc + t * rayDir - ba * y / baba) / radius); + return true; + } + + if (topCap && y < 0.0) { + // top cap + t = -baoc / bard; + if (abs(k1 + k2 * t) < h) { + interior = false; + intersection = vec4(t, ba * sign(y) / baba); + return true; + } + } else if(bottomCap && y >= 0.0) { + // bottom cap + t = (baba - baoc) / bard; + if (abs(k1 + k2 * t) < h) { + interior = false; + intersection = vec4(t, ba * sign(y) / baba); + return true; + } + } + + #ifdef dDoubleSided + // body inside + h = -h; + t = (-k1 - h) / k2; + y = baoc + t * bard; + if (y > 0.0 && y < baba) { + interior = true; + intersection = vec4(t, (oc + t * rayDir - ba * y / baba) / radius); + return true; + } + + // TODO: handle inside caps??? + #endif + + return false; +} + +void main() { + #include clip_pixel + + vec3 rayDir = mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho); + + vec4 intersection; + bool interior; + bool hit = CylinderImpostor(vModelPosition, rayDir, vStart, vEnd, vSize, intersection, interior); + if (!hit) discard; + + vec3 vViewPosition = vModelPosition + intersection.x * rayDir; + vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz; + gl_FragDepthEXT = calcDepth(vViewPosition); + + // bugfix (mac only?) + if (gl_FragDepthEXT < 0.0) discard; + if (gl_FragDepthEXT > 1.0) discard; + + #include assign_material_color + + #if defined(dRenderVariant_pick) + #include check_picking_alpha + gl_FragColor = material; + #elif defined(dRenderVariant_depth) + gl_FragColor = material; + #elif defined(dRenderVariant_color) + #ifdef dIgnoreLight + gl_FragColor = material; + #else + mat3 normalMatrix = transpose3(inverse3(mat3(uView))); + vec3 normal = normalize(normalMatrix * -normalize(intersection.yzw)); + #include apply_light_color + #endif + + #include apply_interior_color + #include apply_marker_color + #include apply_fog + + float fragmentDepth = gl_FragDepthEXT; + #include wboit_write + #endif +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/cylinders.vert.ts b/src/mol-gl/shader/cylinders.vert.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c4842c603115d3350ae4ff562c65d6ea2eced8c --- /dev/null +++ b/src/mol-gl/shader/cylinders.vert.ts @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export default ` +precision highp float; +precision highp int; + +#include common +#include read_from_texture +#include common_vert_params +#include color_vert_params +#include size_vert_params +#include common_clip + +uniform mat4 uModelView; + +attribute mat4 aTransform; +attribute float aInstance; +attribute float aGroup; + +attribute vec3 aMapping; +attribute vec3 aStart; +attribute vec3 aEnd; +attribute float aScale; +attribute float aCap; + +varying mat4 vTransform; +varying vec3 vStart; +varying vec3 vEnd; +varying float vSize; +varying float vCap; + +uniform float uIsOrtho; +uniform vec3 uCameraDir; + +void main() { + #include assign_group + #include assign_color_varying + #include assign_marker_varying + #include assign_clipping_varying + #include assign_size + + mat4 modelTransform = uModel * aTransform; + + vTransform = aTransform; + vStart = (modelTransform * vec4(aStart, 1.0)).xyz; + vEnd = (modelTransform * vec4(aEnd, 1.0)).xyz; + vSize = size * aScale; + vCap = aCap; + + vModelPosition = (vStart + vEnd) * 0.5; + vec3 camDir = -mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho); + vec3 dir = vEnd - vStart; + // ensure cylinder 'dir' is pointing towards the camera + if(dot(camDir, dir) < 0.0) dir = -dir; + + vec3 left = cross(camDir, dir); + vec3 up = cross(left, dir); + left = vSize * normalize(left); + up = vSize * normalize(up); + + // move vertex in object-space from center to corner + vModelPosition += aMapping.x * dir + aMapping.y * left + aMapping.z * up; + + vec4 mvPosition = uView * vec4(vModelPosition, 1.0); + vViewPosition = mvPosition.xyz; + gl_Position = uProjection * mvPosition; + + #include clip_instance +} +`; \ No newline at end of file