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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 76 additions & 3 deletions packages/engine/Source/Scene/BufferPolylineMaterial.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// @ts-check

import Color from "../Core/Color.js";
import Frozen from "../Core/Frozen.js";
import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js";

/** @import Color from "../Core/Color.js"; */
/** @import BufferPolyline from "./BufferPolyline.js"; */

/**
Expand All @@ -12,6 +12,10 @@ import BufferPrimitiveMaterial from "./BufferPrimitiveMaterial.js";
* @property {Color} [outlineColor=Color.WHITE] Color of outline.
* @property {number} [outlineWidth=0.0] Width of outline, 0-255px.
* @property {number} [width=1.0] Width of line, 0-255px.
* @property {Color} [gapColor=Color.TRANSPARENT] Color drawn in the gaps between dashes.
* @property {number} [dashRepeat=1] Number of dash-pattern cycles along the whole line, 0-255. The pattern is anchored to the line geometry (not the screen), so bands stay put as the camera pans and scale with the line as it zooms.
* @property {number} [dashOffset=0.0] Phase offset as a fraction of one cycle, 0-1. Shifts where the pattern starts relative to the origin of the line.
* @property {number} [dashPattern=65535] 16-bit on/off bitmask sampled within each cycle. 65535 (0xFFFF) is fully solid.
*/

/**
Expand All @@ -29,7 +33,11 @@ class BufferPolylineMaterial extends BufferPrimitiveMaterial {
static Layout = {
...BufferPrimitiveMaterial.Layout,
WIDTH_U8: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH,
__BYTE_LENGTH: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 4,
GAP_COLOR_U32: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 4,
DASH_REPEAT_U8: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 8,
DASH_OFFSET_U8: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 9,
DASH_PATTERN_U16: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 10,
__BYTE_LENGTH: BufferPrimitiveMaterial.Layout.__BYTE_LENGTH + 12,
};

/**
Expand All @@ -49,6 +57,37 @@ class BufferPolylineMaterial extends BufferPrimitiveMaterial {
* @type {number}
*/
this.width = options.width ?? 1;

/**
* Color drawn in the gaps between dashes. Defaults to transparent, which
* discards, so gaps read as empty space.
* @type {Color}
*/
this.gapColor = Color.clone(options.gapColor ?? Color.TRANSPARENT);

/**
* Number of dash-pattern cycles along the whole line, 0–255. The pattern is
* anchored to the line geometry, so bands stay put as the camera pans and
* scale with the line as it zooms (rather than crawling in screen space).
* @type {number}
*/
this.dashRepeat = options.dashRepeat ?? 1;

/**
* Phase offset as a fraction of one cycle, 0–1. Shifts where the pattern
* starts relative to the origin of the line (e.g. 0.25 lands a solid slot
* on the origin instead of a gap).
* @type {number}
*/
this.dashOffset = options.dashOffset ?? 0;

/**
* 16-bit on/off bitmask sampled within each cycle — each bit is one of 16
* slots, 1 draws {@link BufferPolylineMaterial#color}, 0 draws
* {@link BufferPolylineMaterial#gapColor}. 65535 (0xFFFF) is fully solid.
* @type {number}
*/
this.dashPattern = options.dashPattern ?? 65535;
}

/**
Expand All @@ -60,6 +99,22 @@ class BufferPolylineMaterial extends BufferPrimitiveMaterial {
static pack(material, view, byteOffset) {
super.pack(material, view, byteOffset);
view.setUint8(this.Layout.WIDTH_U8 + byteOffset, material.width);
view.setUint32(
this.Layout.GAP_COLOR_U32 + byteOffset,
material.gapColor.toRgba(),
true,
);
view.setUint8(this.Layout.DASH_REPEAT_U8 + byteOffset, material.dashRepeat);
// Offset is a [0,1) fraction stored in 256ths so 0.25 round-trips exactly.
view.setUint8(
this.Layout.DASH_OFFSET_U8 + byteOffset,
Math.round(material.dashOffset * 256),
);
view.setUint16(
this.Layout.DASH_PATTERN_U16 + byteOffset,
material.dashPattern,
true,
);
}

/**
Expand All @@ -72,6 +127,17 @@ class BufferPolylineMaterial extends BufferPrimitiveMaterial {
static unpack(view, byteOffset, result) {
super.unpack(view, byteOffset, result);
result.width = view.getUint8(this.Layout.WIDTH_U8 + byteOffset);
Color.fromRgba(
view.getUint32(this.Layout.GAP_COLOR_U32 + byteOffset, true),
result.gapColor,
);
result.dashRepeat = view.getUint8(this.Layout.DASH_REPEAT_U8 + byteOffset);
result.dashOffset =
view.getUint8(this.Layout.DASH_OFFSET_U8 + byteOffset) / 256;
result.dashPattern = view.getUint16(
this.Layout.DASH_PATTERN_U16 + byteOffset,
true,
);
return result;
}

Expand All @@ -86,7 +152,14 @@ class BufferPolylineMaterial extends BufferPrimitiveMaterial {
* @returns {Object} JSON-serializable object.
*/
toJSON() {
return { ...super.toJSON(), width: this.width };
return {
...super.toJSON(),
width: this.width,
gapColor: this.gapColor.toCssHexString(),
dashRepeat: this.dashRepeat,
dashOffset: this.dashOffset,
dashPattern: this.dashPattern,
};
}
}

Expand Down
30 changes: 29 additions & 1 deletion packages/engine/Source/Scene/renderBufferPolylineCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import BlendOption from "./BlendOption.js";

/**
* TODO(PR#13211): Need 'keyof' syntax to avoid duplicating attribute names.
* @typedef {'positionHigh' | 'positionLow' | 'prevPositionHigh' | 'prevPositionLow' | 'nextPositionHigh' | 'nextPositionLow' | 'pickColor' | 'showColorWidthAndTexCoord' | 'alpha'} BufferPolylineAttribute
* @typedef {'positionHigh' | 'positionLow' | 'prevPositionHigh' | 'prevPositionLow' | 'nextPositionHigh' | 'nextPositionLow' | 'pickColor' | 'showColorWidthAndTexCoord' | 'alpha' | 'dashParams'} BufferPolylineAttribute
* @ignore
*/

Expand All @@ -49,6 +49,7 @@ const BufferPolylineAttributeLocationsFloat64 = {
pickColor: 6,
showColorWidthAndTexCoord: 7,
alpha: 8,
dashParams: 9,
};

/**
Expand All @@ -63,6 +64,7 @@ const BufferPolylineAttributeLocations = {
pickColor: 3,
showColorWidthAndTexCoord: 4,
alpha: 5,
dashParams: 6,
};

/**
Expand Down Expand Up @@ -162,6 +164,8 @@ function renderBufferPolylineCollection(collection, frameState, renderContext) {
pickColor: new Uint8Array(vertexCountMax * 4),
showColorWidthAndTexCoord: new Float32Array(vertexCountMax * 4),
alpha: new Uint8Array(vertexCountMax),
// (encodedGapColorRGB8, gapAlpha, dashRepeat + dashOffset, dashPattern)
dashParams: new Float32Array(vertexCountMax * 4),
};
}

Expand All @@ -175,6 +179,7 @@ function renderBufferPolylineCollection(collection, frameState, renderContext) {
pickColor: pickColorArray,
showColorWidthAndTexCoord: showColorWidthAndTexCoordArray,
alpha: alphaArray,
dashParams: dashParamsArray,

// 8-32 bit.
position,
Expand All @@ -200,6 +205,14 @@ function renderBufferPolylineCollection(collection, frameState, renderContext) {
polyline.getMaterial(material);
const encodedColor = AttributeCompression.encodeRGB8(material.color);
const colorAlpha = material.color.alpha;
const encodedGapColor = AttributeCompression.encodeRGB8(
material.gapColor,
);
const gapAlpha = material.gapColor.alpha;
// Pack the integer cycle count and the [0,1) phase offset into one float;
// the shader splits them with floor()/fract().
const dashRepeatAndOffset = material.dashRepeat + material.dashOffset;
const dashPattern = material.dashPattern;
Color.fromRgba(polyline._pickId, pickColor);
const show = polyline.show;

Expand Down Expand Up @@ -311,6 +324,11 @@ function renderBufferPolylineCollection(collection, frameState, renderContext) {

alphaArray[vOffset] = colorAlpha * 255.0;

dashParamsArray[vOffset * 4] = encodedGapColor;
dashParamsArray[vOffset * 4 + 1] = gapAlpha;
dashParamsArray[vOffset * 4 + 2] = dashRepeatAndOffset;
dashParamsArray[vOffset * 4 + 3] = dashPattern;

vOffset++;
}
}
Expand Down Expand Up @@ -462,6 +480,16 @@ function renderBufferPolylineCollection(collection, frameState, renderContext) {
usage: BufferUsage.STATIC_DRAW,
}),
},
{
index: attributeLocations.dashParams,
componentDatatype: ComponentDatatype.FLOAT,
componentsPerAttribute: 4,
vertexBuffer: Buffer.createVertexBuffer({
typedArray: attributeArrays.dashParams,
context,
usage: BufferUsage.STATIC_DRAW,
}),
},
],
});
} else if (collection._dirtyCount > 0) {
Expand Down
30 changes: 28 additions & 2 deletions packages/engine/Source/Shaders/BufferPolylineMaterialFS.glsl
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
in vec4 v_pickColor;
in vec4 v_color;
in vec4 v_gapColor;
in float v_along; // 0..1 along the line, anchored to the geometry
in float v_dashRepeat; // integer part = cycles along the line, fractional part = phase offset
in float v_dashPattern;

// Number of on/off slots in one dash cycle; matches the 16-bit v_dashPattern.
const float maskLength = 16.0;

void main()
{
if (v_color.a < 0.005) // matches 0/255 and 1/255
vec4 fragColor = v_color;

// Geometry-anchored dash: walk `dashRepeat` pattern cycles along the line
// (v_along, 0..1) and read the matching bit out of the 16-bit v_dashPattern.
// Sampling along the geometry (rather than gl_FragCoord) keeps the bands
// fixed to the line as the camera pans and scales them with the line as it
// zooms. `dashOffset` phase-shifts the pattern (e.g. 0.25 lands a solid slot
// on the origin). A solid line uses an all-ones pattern (0xFFFF), so this
// never selects the gap.
float dashRepeat = floor(v_dashRepeat);
float dashOffset = fract(v_dashRepeat);
float dashPosition = fract(v_along * dashRepeat + dashOffset);
float maskIndex = floor(dashPosition * maskLength);
float maskTest = floor(v_dashPattern / pow(2.0, maskIndex));
if (mod(maskTest, 2.0) < 1.0)
{
fragColor = v_gapColor;
}

if (fragColor.a < 0.005) // matches 0/255 and 1/255
{
discard;
}

out_FragColor = czm_gammaCorrect(v_color);
out_FragColor = czm_gammaCorrect(fragColor);
czm_writeLogDepth();
}
11 changes: 11 additions & 0 deletions packages/engine/Source/Shaders/BufferPolylineMaterialVS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ in vec3 nextPosition;
in vec4 pickColor;
in vec4 showColorWidthAndTexCoord;
in float alpha;
in vec4 dashParams; // (encodedGapColorRGB8, gapAlpha, dashRepeat + dashOffset, dashPattern)

out vec4 v_pickColor;
out vec4 v_color;
out vec2 v_st;
out float v_width;
out float v_polylineAngle;
out vec4 v_gapColor;
out float v_along;
out float v_dashRepeat;
out float v_dashPattern;

void main()
{
Expand Down Expand Up @@ -60,4 +65,10 @@ void main()

v_width = width;
v_polylineAngle = polylineAngle;

v_gapColor = czm_decodeRGB8(dashParams.x);
v_gapColor.a = dashParams.y;
v_along = texCoord;
v_dashRepeat = dashParams.z; // integer part = cycles, fractional part = phase offset
v_dashPattern = dashParams.w;
}