add line-strips to lines geo

This commit is contained in:
Alexander Rose
2026-02-07 15:19:25 -08:00
parent 70ad32f62d
commit e7ecf98f13
4 changed files with 154 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -85,7 +85,7 @@ export namespace Geometry {
case 'spheres': return geometry.sphereCount * 6;
case 'cylinders': return geometry.cylinderCount * 6;
case 'text': return geometry.charCount * 4;
case 'lines': return geometry.lineCount * 4;
case 'lines': return geometry.vertexCount;
case 'direct-volume':
const [x, y, z] = geometry.gridDimension.ref.value;
return x * y * z;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -10,6 +10,15 @@ import { Lines } from './lines';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { Cage } from '../../primitive/cage';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const caAdd = ChunkedArray.add;
const caAdd2 = ChunkedArray.add2;
const caAdd3 = ChunkedArray.add3;
const tmpVecA = Vec3();
const tmpVecB = Vec3();
const tmpDir = Vec3();
export interface LinesBuilder {
add(startX: number, startY: number, startZ: number, endX: number, endY: number, endZ: number, group: number): void
addVec(start: Vec3, end: Vec3, group: number): void
@@ -19,14 +28,6 @@ export interface LinesBuilder {
getLines(): Lines
}
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 LinesBuilder {
export function create(initialCount = 2048, chunkSize = 1024, lines?: Lines): LinesBuilder {
const groups = ChunkedArray.create(Float32Array, 1, chunkSize, lines ? lines.groupBuffer.ref.value : initialCount);
@@ -89,13 +90,15 @@ export namespace LinesBuilder {
},
getLines: () => {
const lineCount = groups.elementCount / 4;
const vertexCount = groups.elementCount;
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 mb = lines && lineCount <= lines.lineCount ? lines.mappingBuffer.ref.value : new Float32Array(lineCount * 8);
const ib = lines && lineCount <= lines.lineCount ? lines.indexBuffer.ref.value : new Uint32Array(lineCount * 6);
if (!lines || lineCount > lines.lineCount) fillMappingAndIndices(lineCount, mb, ib);
return Lines.create(mb, ib, gb, sb, eb, lineCount, lines);
const mb = lines && lineCount <= lines.lineCount && lines.stripCount.ref.value === 0 ? lines.mappingBuffer.ref.value : new Float32Array(lineCount * 8);
const ib = lines && lineCount <= lines.lineCount && lines.stripCount.ref.value === 0 ? lines.indexBuffer.ref.value : new Uint32Array(lineCount * 6);
const ob = lines ? lines.stripBuffer.ref.value : new Uint32Array(0);
if (!lines || lineCount > lines.lineCount || lines.stripCount.ref.value > 0) fillMappingAndIndices(lineCount, mb, ib);
return Lines.create(mb, ib, gb, sb, eb, ob, lineCount, vertexCount, 0, lines);
}
};
}
@@ -117,3 +120,104 @@ function fillMappingAndIndices(n: number, mb: Float32Array, ib: Uint32Array) {
ib[io + 3] = o + 1; ib[io + 4] = o + 3; ib[io + 5] = o + 2;
}
}
//
export interface StripLinesBuilder {
start(group: number): void
add(x: number, y: number, z: number): void
addVec(v: Vec3): void
end(): void
getLines(): Lines
}
export namespace StripLinesBuilder {
export function create(initialCount = 2048, chunkSize = 1024, lines?: Lines): StripLinesBuilder {
const groups = ChunkedArray.create(Float32Array, 1, chunkSize, lines ? lines.groupBuffer.ref.value : initialCount);
const starts = ChunkedArray.create(Float32Array, 3, chunkSize, lines ? lines.startBuffer.ref.value : initialCount);
const ends = ChunkedArray.create(Float32Array, 3, chunkSize, lines ? lines.endBuffer.ref.value : initialCount);
const mapping = ChunkedArray.create(Float32Array, 2, chunkSize, lines ? lines.mappingBuffer.ref.value : initialCount);
const indices = ChunkedArray.create(Uint32Array, 3, chunkSize, lines ? lines.indexBuffer.ref.value : initialCount);
const strips = ChunkedArray.create(Uint32Array, 1, chunkSize, lines ? lines.stripBuffer.ref.value : initialCount);
let stripGroup = 0;
let pointCount = 0;
let firstVertexOffset = 0;
let prevX = 0, prevY = 0, prevZ = 0;
const addPoint = (x: number, y: number, z: number) => {
if (pointCount === 0) {
firstVertexOffset = groups.elementCount;
prevX = x; prevY = y; prevZ = z;
pointCount = 1;
return;
}
const vertexOffset = groups.elementCount;
if (pointCount === 1) {
caAdd3(starts, prevX, prevY, prevZ);
caAdd3(ends, x, y, z);
caAdd(groups, stripGroup);
caAdd2(mapping, -1, -1); // left, start
caAdd3(starts, prevX, prevY, prevZ);
caAdd3(ends, x, y, z);
caAdd(groups, stripGroup);
caAdd2(mapping, 1, -1); // right, start
}
caAdd3(starts, prevX, prevY, prevZ);
caAdd3(ends, x, y, z);
caAdd(groups, stripGroup);
caAdd2(mapping, -1, 1); // left, end
caAdd3(starts, prevX, prevY, prevZ);
caAdd3(ends, x, y, z);
caAdd(groups, stripGroup);
caAdd2(mapping, 1, 1); // right, end
const prevOffset = pointCount === 1 ? firstVertexOffset : vertexOffset - 2;
const currOffset = pointCount === 1 ? vertexOffset + 2 : vertexOffset;
// Triangle 1: prev-left, prev-right, curr-left
caAdd3(indices, prevOffset, prevOffset + 1, currOffset);
// Triangle 2: prev-right, curr-right, curr-left
caAdd3(indices, prevOffset + 1, currOffset + 1, currOffset);
prevX = x; prevY = y; prevZ = z;
pointCount++;
};
return {
start: (group: number) => {
stripGroup = group;
pointCount = 0;
if (strips.elementCount === 0) {
caAdd(strips, 0);
}
},
add: (x: number, y: number, z: number) => {
addPoint(x, y, z);
},
addVec: (v: Vec3) => {
addPoint(v[0], v[1], v[2]);
},
end: () => {
pointCount = 0;
caAdd(strips, groups.elementCount);
},
getLines: () => {
const lineCount = indices.elementCount / 2;
const vertexCount = groups.elementCount;
const stripCount = strips.elementCount - 1;
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 mb = ChunkedArray.compact(mapping, true) as Float32Array;
const ib = ChunkedArray.compact(indices, true) as Uint32Array;
const ob = ChunkedArray.compact(strips, true) as Uint32Array;
return Lines.create(mb, ib, gb, sb, eb, ob, lineCount, vertexCount, stripCount, lines);
}
};
}
}

View File

@@ -35,6 +35,8 @@ export interface Lines {
/** Number of lines */
lineCount: number,
/** Number of vertices */
vertexCount: number,
/** Mapping buffer as array of xy values wrapped in a value cell */
readonly mappingBuffer: ValueCell<Float32Array>,
@@ -47,6 +49,11 @@ export interface Lines {
/** Line end buffer as array of xyz values wrapped in a value cell */
readonly endBuffer: ValueCell<Float32Array>,
/** Number of strips wrapped in a value cell */
readonly stripCount: ValueCell<number>,
/** Strip buffer as array of vertex offsets wrapped in a value cell */
readonly stripBuffer: ValueCell<Uint32Array>,
/** Bounding sphere of the lines */
readonly boundingSphere: Sphere3D
/** Maps group ids to line indices */
@@ -57,10 +64,10 @@ export interface Lines {
}
export namespace Lines {
export function create(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, lineCount: number, lines?: Lines): Lines {
export function create(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, strips: Uint32Array, lineCount: number, vertexCount: number, stripCount: number, lines?: Lines): Lines {
return lines ?
update(mappings, indices, groups, starts, ends, lineCount, lines) :
fromArrays(mappings, indices, groups, starts, ends, lineCount);
update(mappings, indices, groups, starts, ends, strips, lineCount, vertexCount, stripCount, lines) :
fromArrays(mappings, indices, groups, starts, ends, strips, lineCount, vertexCount, stripCount);
}
export function createEmpty(lines?: Lines): Lines {
@@ -69,7 +76,8 @@ export namespace Lines {
const gb = lines ? lines.groupBuffer.ref.value : new Float32Array(0);
const sb = lines ? lines.startBuffer.ref.value : new Float32Array(0);
const eb = lines ? lines.endBuffer.ref.value : new Float32Array(0);
return create(mb, ib, gb, sb, eb, 0, lines);
const ob = lines ? lines.stripBuffer.ref.value : new Uint32Array(0);
return create(mb, ib, gb, sb, eb, ob, 0, 0, 0, lines);
}
export function fromMesh(mesh: Mesh, lines?: Lines) {
@@ -95,12 +103,14 @@ export namespace Lines {
function hashCode(lines: Lines) {
return hashFnv32a([
lines.lineCount, lines.mappingBuffer.ref.version, lines.indexBuffer.ref.version,
lines.groupBuffer.ref.version, lines.startBuffer.ref.version, lines.endBuffer.ref.version
lines.lineCount, lines.vertexCount,
lines.mappingBuffer.ref.version, lines.indexBuffer.ref.version,
lines.groupBuffer.ref.version, lines.startBuffer.ref.version, lines.endBuffer.ref.version,
lines.stripCount.ref.version, lines.stripBuffer.ref.version
]);
}
function fromArrays(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, lineCount: number): Lines {
function fromArrays(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, strips: Uint32Array, lineCount: number, vertexCount: number, stripCount: number): Lines {
const boundingSphere = Sphere3D();
let groupMapping: GroupMapping;
@@ -111,11 +121,14 @@ export namespace Lines {
const lines = {
kind: 'lines' as const,
lineCount,
vertexCount,
mappingBuffer: ValueCell.create(mappings),
indexBuffer: ValueCell.create(indices),
groupBuffer: ValueCell.create(groups),
startBuffer: ValueCell.create(starts),
endBuffer: ValueCell.create(ends),
stripCount: ValueCell.create(stripCount),
stripBuffer: ValueCell.create(strips),
get boundingSphere() {
const newHash = hashCode(lines);
if (newHash !== currentHash) {
@@ -145,24 +158,27 @@ export namespace Lines {
return lines;
}
function update(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, lineCount: number, lines: Lines) {
if (lineCount > lines.lineCount) {
function update(mappings: Float32Array, indices: Uint32Array, groups: Float32Array, starts: Float32Array, ends: Float32Array, strips: Uint32Array, lineCount: number, vertexCount: number, stripCount: number, lines: Lines) {
if (lineCount > lines.lineCount || stripCount !== lines.stripCount.ref.value || stripCount > 0) {
ValueCell.update(lines.mappingBuffer, mappings);
ValueCell.update(lines.indexBuffer, indices);
}
lines.lineCount = lineCount;
lines.vertexCount = vertexCount;
ValueCell.update(lines.groupBuffer, groups);
ValueCell.update(lines.startBuffer, starts);
ValueCell.update(lines.endBuffer, ends);
ValueCell.updateIfChanged(lines.stripCount, stripCount);
ValueCell.update(lines.stripBuffer, strips);
return lines;
}
export function transform(lines: Lines, t: Mat4) {
const start = lines.startBuffer.ref.value;
transformPositionArray(t, start, 0, lines.lineCount * 4);
transformPositionArray(t, start, 0, lines.vertexCount);
ValueCell.update(lines.startBuffer, start);
const end = lines.endBuffer.ref.value;
transformPositionArray(t, end, 0, lines.lineCount * 4);
transformPositionArray(t, end, 0, lines.vertexCount);
ValueCell.update(lines.endBuffer, end);
}
@@ -222,7 +238,7 @@ export namespace Lines {
const material = createEmptySubstance();
const clipping = createEmptyClipping();
const counts = { drawCount: lines.lineCount * 2 * 3, vertexCount: lines.lineCount * 4, groupCount, instanceCount };
const counts = { drawCount: lines.lineCount * 2 * 3, vertexCount: lines.vertexCount, groupCount, instanceCount };
const invariantBoundingSphere = Sphere3D.clone(lines.boundingSphere);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
@@ -253,6 +269,9 @@ export namespace Lines {
dLineSizeAttenuation: ValueCell.create(props.lineSizeAttenuation),
uDoubleSided: ValueCell.create(true),
dFlipSided: ValueCell.create(false),
stripCount: lines.stripCount,
stripOffsets: lines.stripBuffer,
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -7,7 +7,7 @@
import { Renderable, RenderableState, createRenderable } from '../renderable';
import { WebGLContext } from '../webgl/context';
import { createGraphicsRenderItem, Transparency } from '../webgl/render-item';
import { GlobalUniformSchema, BaseSchema, AttributeSpec, DefineSpec, Values, InternalSchema, SizeSchema, ElementsSpec, InternalValues, GlobalTextureSchema, UniformSpec, GlobalDefineValues, GlobalDefines, GlobalDefineSchema } from './schema';
import { GlobalUniformSchema, BaseSchema, AttributeSpec, DefineSpec, Values, InternalSchema, SizeSchema, ElementsSpec, InternalValues, GlobalTextureSchema, UniformSpec, GlobalDefineValues, GlobalDefines, GlobalDefineSchema, ValueSpec } from './schema';
import { ValueCell } from '../../mol-util';
import { LinesShaderCode } from '../shader-code';
@@ -22,6 +22,8 @@ export const LinesSchema = {
dLineSizeAttenuation: DefineSpec('boolean'),
uDoubleSided: UniformSpec('b', 'material'),
dFlipSided: DefineSpec('boolean'),
stripCount: ValueSpec('number'),
stripOffsets: ValueSpec('uint32'),
};
export type LinesSchema = typeof LinesSchema
export type LinesValues = Values<LinesSchema>