mirror of https://github.com/CesiumGS/cesium.git
Compare commits
17 Commits
6cd87da64f
...
c3a93dd3bf
| Author | SHA1 | Date |
|---|---|---|
|
|
c3a93dd3bf | |
|
|
ee2b3813b2 | |
|
|
4e3980cc53 | |
|
|
e5678b111d | |
|
|
a2de2ca719 | |
|
|
a72903f0cf | |
|
|
6d381de00d | |
|
|
11d6b15eb7 | |
|
|
d4db1ea2d8 | |
|
|
bb9f62f52c | |
|
|
5d4770e142 | |
|
|
d30ad47d6f | |
|
|
072e65f882 | |
|
|
ca8380fa04 | |
|
|
b935277dd7 | |
|
|
6bb62280fe | |
|
|
859a058c60 |
|
|
@ -7,7 +7,8 @@
|
|||
#### Fixes :wrench:
|
||||
|
||||
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
|
||||
- Improved scaling of SVGs in billboards [#TODO](https://github.com/CesiumGS/cesium/issues/TODO)
|
||||
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/issues/13020)
|
||||
- Fixed unexpected outline artifacts around billboards [#13038](https://github.com/CesiumGS/cesium/issues/13038)
|
||||
|
||||
#### Additions :tada:
|
||||
|
||||
|
|
@ -27,6 +28,7 @@
|
|||
|
||||
- Added experimental support for loading 3D Tiles as terrain, via `Cesium3DTilesTerrainProvider`. See [the PR](https://github.com/CesiumGS/cesium/pull/12963) for limitations on the types of 3D Tiles that can be used. [#12296](https://github.com/CesiumGS/cesium/issues/12296)
|
||||
- Added support for [EXT_mesh_primitive_edge_visibility](https://github.com/KhronosGroup/glTF/pull/2479) glTF extension. [#12765](https://github.com/CesiumGS/cesium/issues/12765)
|
||||
- Extended edge visibility loading to honor material colors and line-string overrides from EXT_mesh_primitive_edge_visibility.
|
||||
|
||||
#### Fixes :wrench:
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -122,6 +122,8 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
|
|||
return node;
|
||||
}
|
||||
|
||||
const borderPadding = this._borderPadding;
|
||||
|
||||
// Vertical split (childNode1 = left half, childNode2 = right half).
|
||||
if (widthDifference > heightDifference) {
|
||||
node.childNode1 = new TextureNode({
|
||||
|
|
@ -130,12 +132,18 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
|
|||
width,
|
||||
height: nodeHeight,
|
||||
});
|
||||
node.childNode2 = new TextureNode({
|
||||
x: rectangle.x + width,
|
||||
y: rectangle.y,
|
||||
width: widthDifference,
|
||||
height: nodeHeight,
|
||||
});
|
||||
|
||||
// Apply padding only along the vertical "cut".
|
||||
const widthDifferencePadded = widthDifference - borderPadding;
|
||||
|
||||
if (widthDifferencePadded > 0) {
|
||||
node.childNode2 = new TextureNode({
|
||||
x: rectangle.x + width + borderPadding,
|
||||
y: rectangle.y,
|
||||
width: widthDifferencePadded,
|
||||
height: nodeHeight,
|
||||
});
|
||||
}
|
||||
|
||||
return this._findNode(node.childNode1, { width, height });
|
||||
}
|
||||
|
|
@ -147,12 +155,19 @@ TexturePacker.prototype._findNode = function (node, { width, height }) {
|
|||
width: nodeWidth,
|
||||
height,
|
||||
});
|
||||
node.childNode2 = new TextureNode({
|
||||
x: rectangle.x,
|
||||
y: rectangle.y + height,
|
||||
width: nodeWidth,
|
||||
height: heightDifference,
|
||||
});
|
||||
|
||||
// Apply padding only along the horizontal "cut".
|
||||
const heightDifferencePadded = heightDifference - borderPadding;
|
||||
|
||||
if (heightDifferencePadded > 0) {
|
||||
node.childNode2 = new TextureNode({
|
||||
x: rectangle.x,
|
||||
y: rectangle.y + height + borderPadding,
|
||||
width: nodeWidth,
|
||||
height: heightDifferencePadded,
|
||||
});
|
||||
}
|
||||
|
||||
return this._findNode(node.childNode1, { width, height });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2002,6 +2002,89 @@ function fetchSpzExtensionFrom(extensions) {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
function getEdgeVisibilityMaterialColor(loader, materialIndex) {
|
||||
if (!defined(materialIndex)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const materials = loader.gltfJson.materials;
|
||||
if (
|
||||
!defined(materials) ||
|
||||
materialIndex < 0 ||
|
||||
materialIndex >= materials.length
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const material = materials[materialIndex];
|
||||
if (!defined(material)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const metallicRoughness =
|
||||
material.pbrMetallicRoughness ?? Frozen.EMPTY_OBJECT;
|
||||
const color = fromArray(Cartesian4, metallicRoughness.baseColorFactor);
|
||||
|
||||
if (defined(color)) {
|
||||
return color;
|
||||
}
|
||||
|
||||
return new Cartesian4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
function getLineStringPrimitiveRestartValue(componentType) {
|
||||
switch (componentType) {
|
||||
case ComponentDatatype.UNSIGNED_BYTE:
|
||||
return 255;
|
||||
case ComponentDatatype.UNSIGNED_SHORT:
|
||||
return 65535;
|
||||
case ComponentDatatype.UNSIGNED_INT:
|
||||
return 4294967295;
|
||||
default:
|
||||
throw new RuntimeError(
|
||||
"EXT_mesh_primitive_edge_visibility line strings indices must use unsigned scalar component types.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function loadEdgeVisibilityLineStrings(
|
||||
loader,
|
||||
lineStringDefinitions,
|
||||
defaultMaterialIndex,
|
||||
) {
|
||||
if (!defined(lineStringDefinitions) || lineStringDefinitions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = new Array(lineStringDefinitions.length);
|
||||
for (let i = 0; i < lineStringDefinitions.length; i++) {
|
||||
const definition = lineStringDefinitions[i] ?? Frozen.EMPTY_OBJECT;
|
||||
const accessorId = definition.indices;
|
||||
const accessor = loader.gltfJson.accessors[accessorId];
|
||||
|
||||
if (!defined(accessor)) {
|
||||
throw new RuntimeError("Edge visibility line string accessor not found!");
|
||||
}
|
||||
|
||||
const indices = loadAccessor(loader, accessor);
|
||||
const restartIndex = getLineStringPrimitiveRestartValue(
|
||||
accessor.componentType,
|
||||
);
|
||||
const materialIndex = defined(definition.material)
|
||||
? definition.material
|
||||
: defaultMaterialIndex;
|
||||
|
||||
result[i] = {
|
||||
indices: indices,
|
||||
restartIndex: restartIndex,
|
||||
componentType: accessor.componentType,
|
||||
materialColor: getEdgeVisibilityMaterialColor(loader, materialIndex),
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load resources associated with a mesh primitive for a glTF node
|
||||
* @param {GltfLoader} loader
|
||||
|
|
@ -2041,37 +2124,44 @@ function loadPrimitive(loader, gltfPrimitive, hasInstances, frameState) {
|
|||
|
||||
// Edge Visibility
|
||||
const edgeVisibilityExtension = extensions.EXT_mesh_primitive_edge_visibility;
|
||||
const hasEdgeVisibility = defined(edgeVisibilityExtension);
|
||||
if (hasEdgeVisibility) {
|
||||
const visibilityAccessor =
|
||||
loader.gltfJson.accessors[edgeVisibilityExtension.visibility];
|
||||
if (!defined(visibilityAccessor)) {
|
||||
throw new RuntimeError("Edge visibility accessor not found!");
|
||||
}
|
||||
const visibilityValues = loadAccessor(loader, visibilityAccessor);
|
||||
primitive.edgeVisibility = {
|
||||
visibility: visibilityValues,
|
||||
material: edgeVisibilityExtension.material,
|
||||
};
|
||||
if (defined(edgeVisibilityExtension)) {
|
||||
const edgeVisibility = {};
|
||||
|
||||
const visibilityAccessorId = edgeVisibilityExtension.visibility;
|
||||
if (defined(visibilityAccessorId)) {
|
||||
const visibilityAccessor =
|
||||
loader.gltfJson.accessors[visibilityAccessorId];
|
||||
if (!defined(visibilityAccessor)) {
|
||||
throw new RuntimeError("Edge visibility accessor not found!");
|
||||
}
|
||||
edgeVisibility.visibility = loadAccessor(loader, visibilityAccessor);
|
||||
}
|
||||
|
||||
edgeVisibility.materialColor = getEdgeVisibilityMaterialColor(
|
||||
loader,
|
||||
edgeVisibilityExtension.material,
|
||||
);
|
||||
|
||||
// Load silhouette normals
|
||||
if (defined(edgeVisibilityExtension.silhouetteNormals)) {
|
||||
const silhouetteNormalsAccessor =
|
||||
loader.gltfJson.accessors[edgeVisibilityExtension.silhouetteNormals];
|
||||
if (defined(silhouetteNormalsAccessor)) {
|
||||
const silhouetteNormalsValues = loadAccessor(
|
||||
edgeVisibility.silhouetteNormals = loadAccessor(
|
||||
loader,
|
||||
silhouetteNormalsAccessor,
|
||||
);
|
||||
primitive.edgeVisibility.silhouetteNormals = silhouetteNormalsValues;
|
||||
}
|
||||
}
|
||||
|
||||
// Load line strings
|
||||
if (defined(edgeVisibilityExtension.lineStrings)) {
|
||||
primitivePlan.edgeVisibility.lineStrings =
|
||||
edgeVisibilityExtension.lineStrings;
|
||||
edgeVisibility.lineStrings = loadEdgeVisibilityLineStrings(
|
||||
loader,
|
||||
edgeVisibilityExtension.lineStrings,
|
||||
edgeVisibilityExtension.material,
|
||||
);
|
||||
}
|
||||
|
||||
primitive.edgeVisibility = edgeVisibility;
|
||||
}
|
||||
|
||||
//support the latest glTF spec and the legacy extension
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import EdgeVisibilityStageFS from "../../Shaders/Model/EdgeVisibilityStageFS.js"
|
|||
import ModelUtility from "./ModelUtility.js";
|
||||
import ModelReader from "./ModelReader.js";
|
||||
import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
|
||||
import AttributeType from "../AttributeType.js";
|
||||
|
||||
/**
|
||||
* Builds derived line geometry for model edges using EXT_mesh_primitive_edge_visibility data.
|
||||
|
|
@ -112,6 +113,9 @@ EdgeVisibilityPipelineStage.process = function (
|
|||
"#ifdef HAS_EDGE_FEATURE_ID",
|
||||
" v_featureId_0 = a_edgeFeatureId;",
|
||||
"#endif",
|
||||
"#ifdef HAS_EDGE_COLOR_ATTRIBUTE",
|
||||
" v_edgeColor = a_edgeColor;",
|
||||
"#endif",
|
||||
" // Transform normals from model space to view space",
|
||||
" v_silhouetteNormalView = czm_normal * a_silhouetteNormal;",
|
||||
" v_faceNormalAView = czm_normal * a_faceNormalA;",
|
||||
|
|
@ -133,6 +137,26 @@ EdgeVisibilityPipelineStage.process = function (
|
|||
return;
|
||||
}
|
||||
|
||||
const runtimePrimitive = renderResources.runtimePrimitive.primitive;
|
||||
const vertexColorInfo = collectVertexColors(runtimePrimitive);
|
||||
const hasEdgeColorOverride = edgeResult.edgeData.some(function (edge) {
|
||||
return defined(edge.color);
|
||||
});
|
||||
|
||||
const needsEdgeColorAttribute =
|
||||
hasEdgeColorOverride || defined(vertexColorInfo);
|
||||
|
||||
let edgeColorLocation;
|
||||
if (needsEdgeColorAttribute) {
|
||||
edgeColorLocation = shaderBuilder.addAttribute("vec4", "a_edgeColor");
|
||||
shaderBuilder.addVarying("vec4", "v_edgeColor", "flat");
|
||||
shaderBuilder.addDefine(
|
||||
"HAS_EDGE_COLOR_ATTRIBUTE",
|
||||
undefined,
|
||||
ShaderDestination.BOTH,
|
||||
);
|
||||
}
|
||||
|
||||
// Generate paired face normals for each unique edge (used to classify silhouette edges in the shader).
|
||||
const edgeFaceNormals = generateEdgeFaceNormals(
|
||||
adjacencyData,
|
||||
|
|
@ -150,6 +174,8 @@ EdgeVisibilityPipelineStage.process = function (
|
|||
faceNormalALocation,
|
||||
faceNormalBLocation,
|
||||
edgeFeatureIdLocation,
|
||||
edgeColorLocation,
|
||||
vertexColorInfo,
|
||||
primitive.edgeVisibility,
|
||||
edgeFaceNormals,
|
||||
);
|
||||
|
|
@ -372,91 +398,165 @@ function generateEdgeFaceNormals(adjacencyData, edgeIndices) {
|
|||
*/
|
||||
function extractVisibleEdges(primitive) {
|
||||
const edgeVisibility = primitive.edgeVisibility;
|
||||
const visibility = edgeVisibility.visibility;
|
||||
const indices = primitive.indices;
|
||||
|
||||
if (!defined(visibility) || !defined(indices)) {
|
||||
if (!defined(edgeVisibility)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const triangleIndexArray = indices.typedArray;
|
||||
const vertexCount = primitive.attributes[0].count;
|
||||
const visibility = edgeVisibility.visibility;
|
||||
const indices = primitive.indices;
|
||||
const lineStrings = edgeVisibility.lineStrings;
|
||||
|
||||
const attributes = primitive.attributes;
|
||||
const vertexCount =
|
||||
defined(attributes) && attributes.length > 0 ? attributes[0].count : 0;
|
||||
|
||||
const hasVisibilityData =
|
||||
defined(visibility) &&
|
||||
defined(indices) &&
|
||||
defined(indices.typedArray) &&
|
||||
indices.typedArray.length > 0;
|
||||
const hasLineStrings = defined(lineStrings) && lineStrings.length > 0;
|
||||
|
||||
if (!hasVisibilityData && !hasLineStrings) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const triangleIndexArray = hasVisibilityData ? indices.typedArray : undefined;
|
||||
const edgeIndices = [];
|
||||
const edgeData = [];
|
||||
const seenEdgeHashes = new Set();
|
||||
let silhouetteEdgeCount = 0;
|
||||
const globalColor = edgeVisibility.materialColor;
|
||||
|
||||
// Process triangles and extract edges (2 bits per edge)
|
||||
let edgeIndex = 0;
|
||||
const totalIndices = triangleIndexArray.length;
|
||||
if (hasVisibilityData) {
|
||||
let edgeIndex = 0;
|
||||
const totalIndices = triangleIndexArray.length;
|
||||
const visibilityArray = visibility;
|
||||
|
||||
for (let i = 0; i + 2 < totalIndices; i += 3) {
|
||||
const v0 = triangleIndexArray[i];
|
||||
const v1 = triangleIndexArray[i + 1];
|
||||
const v2 = triangleIndexArray[i + 2];
|
||||
for (let e = 0; e < 3; e++) {
|
||||
let a, b;
|
||||
if (e === 0) {
|
||||
a = v0;
|
||||
b = v1;
|
||||
} else if (e === 1) {
|
||||
a = v1;
|
||||
b = v2;
|
||||
} else if (e === 2) {
|
||||
a = v2;
|
||||
b = v0;
|
||||
}
|
||||
const byteIndex = Math.floor(edgeIndex / 4);
|
||||
const bitPairOffset = (edgeIndex % 4) * 2;
|
||||
edgeIndex++;
|
||||
for (let i = 0; i + 2 < totalIndices; i += 3) {
|
||||
const v0 = triangleIndexArray[i];
|
||||
const v1 = triangleIndexArray[i + 1];
|
||||
const v2 = triangleIndexArray[i + 2];
|
||||
for (let e = 0; e < 3; e++) {
|
||||
let a;
|
||||
let b;
|
||||
if (e === 0) {
|
||||
a = v0;
|
||||
b = v1;
|
||||
} else if (e === 1) {
|
||||
a = v1;
|
||||
b = v2;
|
||||
} else {
|
||||
a = v2;
|
||||
b = v0;
|
||||
}
|
||||
|
||||
if (byteIndex >= visibility.length) {
|
||||
break;
|
||||
}
|
||||
const byteIndex = Math.floor(edgeIndex / 4);
|
||||
const bitPairOffset = (edgeIndex % 4) * 2;
|
||||
edgeIndex++;
|
||||
|
||||
const byte = visibility[byteIndex];
|
||||
const visibility2Bit = (byte >> bitPairOffset) & 0x3;
|
||||
|
||||
// Only include visible edge types according to EXT_mesh_primitive_edge_visibility spec
|
||||
let shouldIncludeEdge = false;
|
||||
switch (visibility2Bit) {
|
||||
case 0: // HIDDEN - never draw
|
||||
shouldIncludeEdge = false;
|
||||
if (byteIndex >= visibilityArray.length) {
|
||||
break;
|
||||
case 1: // SILHOUETTE - conditionally visible (front-facing vs back-facing)
|
||||
shouldIncludeEdge = true;
|
||||
break;
|
||||
case 2: // HARD - always draw (primary encoding)
|
||||
shouldIncludeEdge = true;
|
||||
break;
|
||||
case 3: // REPEATED - always draw (secondary encoding of a hard edge already encoded as 2)
|
||||
shouldIncludeEdge = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const byte = visibilityArray[byteIndex];
|
||||
const visibility2Bit = (byte >> bitPairOffset) & 0x3;
|
||||
|
||||
if (visibility2Bit === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldIncludeEdge) {
|
||||
const small = Math.min(a, b);
|
||||
const big = Math.max(a, b);
|
||||
const hash = small * vertexCount + big;
|
||||
const edgeKey = `${small},${big}`;
|
||||
|
||||
if (!seenEdgeHashes.has(hash)) {
|
||||
seenEdgeHashes.add(hash);
|
||||
edgeIndices.push(a, b);
|
||||
|
||||
let mateVertexIndex = -1;
|
||||
if (visibility2Bit === 1) {
|
||||
mateVertexIndex = silhouetteEdgeCount;
|
||||
silhouetteEdgeCount++;
|
||||
}
|
||||
|
||||
edgeData.push({
|
||||
edgeType: visibility2Bit,
|
||||
triangleIndex: Math.floor(i / 3),
|
||||
edgeIndex: e,
|
||||
mateVertexIndex: mateVertexIndex,
|
||||
currentTriangleVertices: [v0, v1, v2],
|
||||
});
|
||||
if (seenEdgeHashes.has(edgeKey)) {
|
||||
continue;
|
||||
}
|
||||
seenEdgeHashes.add(edgeKey);
|
||||
edgeIndices.push(a, b);
|
||||
|
||||
let mateVertexIndex = -1;
|
||||
if (visibility2Bit === 1) {
|
||||
mateVertexIndex = silhouetteEdgeCount;
|
||||
silhouetteEdgeCount++;
|
||||
}
|
||||
|
||||
edgeData.push({
|
||||
edgeType: visibility2Bit,
|
||||
triangleIndex: Math.floor(i / 3),
|
||||
edgeIndex: e,
|
||||
mateVertexIndex: mateVertexIndex,
|
||||
currentTriangleVertices: [v0, v1, v2],
|
||||
color: globalColor,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLineStrings) {
|
||||
for (let i = 0; i < lineStrings.length; i++) {
|
||||
const lineString = lineStrings[i];
|
||||
if (!defined(lineString) || !defined(lineString.indices)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const indicesArray = lineString.indices;
|
||||
if (!defined(indicesArray) || indicesArray.length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const restartValue = lineString.restartIndex;
|
||||
const lineColor = defined(lineString.materialColor)
|
||||
? lineString.materialColor
|
||||
: globalColor;
|
||||
|
||||
let previous;
|
||||
for (let j = 0; j < indicesArray.length; j++) {
|
||||
const currentIndex = indicesArray[j];
|
||||
if (defined(restartValue) && currentIndex === restartValue) {
|
||||
previous = undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!defined(previous)) {
|
||||
previous = currentIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
const a = previous;
|
||||
const b = currentIndex;
|
||||
previous = currentIndex;
|
||||
|
||||
if (a === b) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
vertexCount > 0 &&
|
||||
(a < 0 || a >= vertexCount || b < 0 || b >= vertexCount)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const small = Math.min(a, b);
|
||||
const big = Math.max(a, b);
|
||||
const edgeKey = `${small},${big}`;
|
||||
|
||||
if (seenEdgeHashes.has(edgeKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seenEdgeHashes.add(edgeKey);
|
||||
edgeIndices.push(a, b);
|
||||
edgeData.push({
|
||||
edgeType: 2,
|
||||
triangleIndex: -1,
|
||||
edgeIndex: -1,
|
||||
mateVertexIndex: -1,
|
||||
currentTriangleVertices: undefined,
|
||||
color: defined(lineColor) ? lineColor : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -464,6 +564,94 @@ function extractVisibleEdges(primitive) {
|
|||
return { edgeIndices, edgeData, silhouetteEdgeCount };
|
||||
}
|
||||
|
||||
function collectVertexColors(runtimePrimitive) {
|
||||
if (!defined(runtimePrimitive)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const colorAttribute = ModelUtility.getAttributeBySemantic(
|
||||
runtimePrimitive,
|
||||
VertexAttributeSemantic.COLOR,
|
||||
);
|
||||
if (!defined(colorAttribute)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const components = AttributeType.getNumberOfComponents(colorAttribute.type);
|
||||
if (components !== 3 && components !== 4) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let colorData = colorAttribute.typedArray;
|
||||
if (!defined(colorData)) {
|
||||
colorData = ModelReader.readAttributeAsTypedArray(colorAttribute);
|
||||
}
|
||||
if (!defined(colorData)) {
|
||||
return undefined;
|
||||
}
|
||||
const count = colorAttribute.count;
|
||||
if (!defined(count) || count === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (colorData.length < count * components) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isFloatArray =
|
||||
colorData instanceof Float32Array || colorData instanceof Float64Array;
|
||||
const isUint8Array = colorData instanceof Uint8Array;
|
||||
const isUint16Array = colorData instanceof Uint16Array;
|
||||
const isInt8Array = colorData instanceof Int8Array;
|
||||
const isInt16Array = colorData instanceof Int16Array;
|
||||
|
||||
if (
|
||||
!isFloatArray &&
|
||||
!isUint8Array &&
|
||||
!isUint16Array &&
|
||||
!isInt8Array &&
|
||||
!isInt16Array
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const colors = new Float32Array(count * 4);
|
||||
|
||||
const convertComponent = function (value) {
|
||||
let converted;
|
||||
if (isFloatArray) {
|
||||
converted = value;
|
||||
} else if (isUint8Array) {
|
||||
converted = value / 255.0;
|
||||
} else if (isUint16Array) {
|
||||
converted = value / 65535.0;
|
||||
} else if (isInt8Array) {
|
||||
converted = (value + 128.0) / 255.0;
|
||||
} else {
|
||||
converted = (value + 32768.0) / 65535.0;
|
||||
}
|
||||
return Math.min(Math.max(converted, 0.0), 1.0);
|
||||
};
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const srcBase = i * components;
|
||||
const destBase = i * 4;
|
||||
colors[destBase] = convertComponent(colorData[srcBase]);
|
||||
colors[destBase + 1] = convertComponent(colorData[srcBase + 1]);
|
||||
colors[destBase + 2] = convertComponent(colorData[srcBase + 2]);
|
||||
if (components === 4) {
|
||||
colors[destBase + 3] = convertComponent(colorData[srcBase + 3]);
|
||||
} else {
|
||||
colors[destBase + 3] = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
colors: colors,
|
||||
count: count,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a derived line list geometry representing edges. A new vertex domain is used so we can pack
|
||||
* per-edge attributes (silhouette normal, face normal pair, edge type, optional feature ID) without
|
||||
|
|
@ -478,6 +666,8 @@ function extractVisibleEdges(primitive) {
|
|||
* @param {number} faceNormalALocation Shader attribute location for face normal A
|
||||
* @param {number} faceNormalBLocation Shader attribute location for face normal B
|
||||
* @param {number} edgeFeatureIdLocation Shader attribute location for optional edge feature ID
|
||||
* @param {number} edgeColorLocation Shader attribute location for optional edge color data
|
||||
* @param {{colors:Float32Array,count:number}} vertexColorInfo Packed per-vertex colors (optional)
|
||||
* @param {Object} edgeVisibility Edge visibility extension object (may contain silhouetteNormals[])
|
||||
* @param {Float32Array} edgeFaceNormals Packed face normals (6 floats per edge)
|
||||
* @returns {Object|undefined} Object with {vertexArray, indexBuffer, indexCount} or undefined on failure
|
||||
|
|
@ -493,6 +683,8 @@ function createCPULineEdgeGeometry(
|
|||
faceNormalALocation,
|
||||
faceNormalBLocation,
|
||||
edgeFeatureIdLocation,
|
||||
edgeColorLocation,
|
||||
vertexColorInfo,
|
||||
edgeVisibility,
|
||||
edgeFaceNormals,
|
||||
) {
|
||||
|
|
@ -522,8 +714,62 @@ function createCPULineEdgeGeometry(
|
|||
const silhouetteNormalArray = new Float32Array(totalVerts * 3);
|
||||
const faceNormalAArray = new Float32Array(totalVerts * 3);
|
||||
const faceNormalBArray = new Float32Array(totalVerts * 3);
|
||||
const needsEdgeColorAttribute = defined(edgeColorLocation);
|
||||
const edgeColorArray = needsEdgeColorAttribute
|
||||
? new Float32Array(totalVerts * 4)
|
||||
: undefined;
|
||||
const vertexColors = defined(vertexColorInfo)
|
||||
? vertexColorInfo.colors
|
||||
: undefined;
|
||||
const vertexColorCount = defined(vertexColorInfo) ? vertexColorInfo.count : 0;
|
||||
let p = 0;
|
||||
|
||||
function setNoColor(destVertexIndex) {
|
||||
if (!needsEdgeColorAttribute) {
|
||||
return;
|
||||
}
|
||||
const destOffset = destVertexIndex * 4;
|
||||
edgeColorArray[destOffset] = 0.0;
|
||||
edgeColorArray[destOffset + 1] = 0.0;
|
||||
edgeColorArray[destOffset + 2] = 0.0;
|
||||
edgeColorArray[destOffset + 3] = -1.0;
|
||||
}
|
||||
|
||||
function setColorFromOverride(destVertexIndex, color) {
|
||||
if (!needsEdgeColorAttribute) {
|
||||
return;
|
||||
}
|
||||
const destOffset = destVertexIndex * 4;
|
||||
const r = defined(color.x) ? color.x : color[0];
|
||||
const g = defined(color.y) ? color.y : color[1];
|
||||
const b = defined(color.z) ? color.z : color[2];
|
||||
const a = defined(color.w) ? color.w : defined(color[3]) ? color[3] : 1.0;
|
||||
edgeColorArray[destOffset] = r;
|
||||
edgeColorArray[destOffset + 1] = g;
|
||||
edgeColorArray[destOffset + 2] = b;
|
||||
edgeColorArray[destOffset + 3] = a;
|
||||
}
|
||||
|
||||
function assignVertexColor(destVertexIndex, sourceVertexIndex) {
|
||||
if (!needsEdgeColorAttribute) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!defined(vertexColors) ||
|
||||
sourceVertexIndex < 0 ||
|
||||
sourceVertexIndex >= vertexColorCount
|
||||
) {
|
||||
setNoColor(destVertexIndex);
|
||||
return;
|
||||
}
|
||||
const srcOffset = sourceVertexIndex * 4;
|
||||
const destOffset = destVertexIndex * 4;
|
||||
edgeColorArray[destOffset] = vertexColors[srcOffset];
|
||||
edgeColorArray[destOffset + 1] = vertexColors[srcOffset + 1];
|
||||
edgeColorArray[destOffset + 2] = vertexColors[srcOffset + 2];
|
||||
edgeColorArray[destOffset + 3] = vertexColors[srcOffset + 3];
|
||||
}
|
||||
|
||||
const maxSrcVertex = srcPos.length / 3 - 1;
|
||||
|
||||
for (let i = 0; i < numEdges; i++) {
|
||||
|
|
@ -564,6 +810,11 @@ function createCPULineEdgeGeometry(
|
|||
faceNormalBArray[(normalIdx + 1) * 3] = 0;
|
||||
faceNormalBArray[(normalIdx + 1) * 3 + 1] = 0;
|
||||
faceNormalBArray[(normalIdx + 1) * 3 + 2] = 1;
|
||||
if (needsEdgeColorAttribute) {
|
||||
const baseVertexIndex = i * 2;
|
||||
setNoColor(baseVertexIndex);
|
||||
setNoColor(baseVertexIndex + 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -588,6 +839,21 @@ function createCPULineEdgeGeometry(
|
|||
edgeTypeArray[i * 2] = t;
|
||||
edgeTypeArray[i * 2 + 1] = t;
|
||||
|
||||
if (needsEdgeColorAttribute) {
|
||||
const color = edgeData[i].color;
|
||||
const baseVertexIndex = i * 2;
|
||||
if (defined(color)) {
|
||||
setColorFromOverride(baseVertexIndex, color);
|
||||
setColorFromOverride(baseVertexIndex + 1, color);
|
||||
} else if (defined(vertexColors)) {
|
||||
assignVertexColor(baseVertexIndex, a);
|
||||
assignVertexColor(baseVertexIndex + 1, b);
|
||||
} else {
|
||||
setNoColor(baseVertexIndex);
|
||||
setNoColor(baseVertexIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Add silhouette normal for silhouette edges (type 1)
|
||||
let normalX = 0,
|
||||
normalY = 0,
|
||||
|
|
@ -671,6 +937,14 @@ function createCPULineEdgeGeometry(
|
|||
typedArray: faceNormalBArray,
|
||||
usage: BufferUsage.STATIC_DRAW,
|
||||
});
|
||||
let edgeColorBuffer;
|
||||
if (needsEdgeColorAttribute) {
|
||||
edgeColorBuffer = Buffer.createVertexBuffer({
|
||||
context,
|
||||
typedArray: edgeColorArray,
|
||||
usage: BufferUsage.STATIC_DRAW,
|
||||
});
|
||||
}
|
||||
|
||||
// Create sequential indices for line pairs
|
||||
const useU32 = totalVerts > 65534;
|
||||
|
|
@ -727,6 +1001,16 @@ function createCPULineEdgeGeometry(
|
|||
},
|
||||
];
|
||||
|
||||
if (needsEdgeColorAttribute) {
|
||||
attributes.push({
|
||||
index: edgeColorLocation,
|
||||
vertexBuffer: edgeColorBuffer,
|
||||
componentsPerAttribute: 4,
|
||||
componentDatatype: ComponentDatatype.FLOAT,
|
||||
normalize: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Get feature ID from original geometry
|
||||
const primitive = renderResources.runtimePrimitive.primitive;
|
||||
const getFeatureIdForEdge = function () {
|
||||
|
|
@ -794,6 +1078,7 @@ function createCPULineEdgeGeometry(
|
|||
indexBuffer,
|
||||
indexCount: totalVerts,
|
||||
hasEdgeFeatureIds,
|
||||
hasEdgeColors: needsEdgeColorAttribute,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -630,6 +630,14 @@ function Primitive() {
|
|||
* @private
|
||||
*/
|
||||
this.modelPrimitiveImagery = undefined;
|
||||
|
||||
/**
|
||||
* Data loaded from the EXT_mesh_primitive_edge_visibility extension.
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.edgeVisibility = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,59 +13,38 @@ void edgeVisibilityStage(inout vec4 color, inout FeatureIds featureIds)
|
|||
if (!u_isEdgePass) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
float edgeTypeInt = v_edgeType * 255.0;
|
||||
|
||||
// Color code different edge types
|
||||
vec4 edgeColor = vec4(0.0);
|
||||
|
||||
if (edgeTypeInt < 0.5) { // HIDDEN (0)
|
||||
edgeColor = vec4(0.0, 0.0, 0.0, 0.0); // Transparent for hidden edges
|
||||
}
|
||||
else if (edgeTypeInt > 0.5 && edgeTypeInt < 1.5) { // SILHOUETTE (1) - Conditional visibility
|
||||
// Proper silhouette detection using face normals
|
||||
vec3 normalA = normalize(v_faceNormalAView);
|
||||
vec3 normalB = normalize(v_faceNormalBView);
|
||||
|
||||
// Calculate view direction using existing eye-space position varying (v_positionEC)
|
||||
vec3 viewDir = -normalize(v_positionEC);
|
||||
|
||||
// Calculate dot products to determine triangle facing
|
||||
float dotA = dot(normalA, viewDir);
|
||||
float dotB = dot(normalB, viewDir);
|
||||
|
||||
const float eps = 1e-3;
|
||||
bool frontA = dotA > eps;
|
||||
bool backA = dotA < -eps;
|
||||
bool frontB = dotB > eps;
|
||||
bool backB = dotB < -eps;
|
||||
|
||||
// True silhouette: one triangle front-facing, other back-facing
|
||||
bool oppositeFacing = (frontA && backB) || (backA && frontB);
|
||||
|
||||
// Exclude edges where both triangles are nearly grazing (perpendicular to view)
|
||||
// This handles the top-view cylinder case where both normals are ~horizontal
|
||||
bool bothNearGrazing = (abs(dotA) <= eps && abs(dotB) <= eps);
|
||||
|
||||
if (!(oppositeFacing && !bothNearGrazing)) {
|
||||
discard; // Not a true silhouette edge
|
||||
} else {
|
||||
// True silhouette
|
||||
edgeColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
else if (edgeTypeInt > 1.5 && edgeTypeInt < 2.5) { // HARD (2) - BRIGHT GREEN
|
||||
edgeColor = vec4(0.0, 1.0, 0.0, 1.0); // Extra bright green
|
||||
}
|
||||
else if (edgeTypeInt > 2.5 && edgeTypeInt < 3.5) { // REPEATED (3)
|
||||
edgeColor = vec4(0.0, 0.0, 1.0, 1.0);
|
||||
} else {
|
||||
edgeColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
if (edgeTypeInt < 0.5) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// Temporary color: white
|
||||
edgeColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
color = edgeColor;
|
||||
if (edgeTypeInt > 0.5 && edgeTypeInt < 1.5) { // silhouette candidate
|
||||
vec3 normalA = normalize(v_faceNormalAView);
|
||||
vec3 normalB = normalize(v_faceNormalBView);
|
||||
vec3 viewDir = -normalize(v_positionEC);
|
||||
float dotA = dot(normalA, viewDir);
|
||||
float dotB = dot(normalB, viewDir);
|
||||
const float eps = 1e-3;
|
||||
bool frontA = dotA > eps;
|
||||
bool backA = dotA < -eps;
|
||||
bool frontB = dotB > eps;
|
||||
bool backB = dotB < -eps;
|
||||
bool oppositeFacing = (frontA && backB) || (backA && frontB);
|
||||
bool bothNearGrazing = (abs(dotA) <= eps && abs(dotB) <= eps);
|
||||
if (!(oppositeFacing && !bothNearGrazing)) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
vec4 finalColor = color;
|
||||
#ifdef HAS_EDGE_COLOR_ATTRIBUTE
|
||||
if (v_edgeColor.a >= 0.0) {
|
||||
finalColor = v_edgeColor;
|
||||
}
|
||||
#endif
|
||||
color = finalColor;
|
||||
|
||||
#if defined(HAS_EDGE_VISIBILITY_MRT) && !defined(CESIUM_REDIRECTED_COLOR_OUTPUT)
|
||||
// Write edge metadata
|
||||
|
|
|
|||
|
|
@ -852,16 +852,16 @@ describe("Scene/TextureAtlas", function () {
|
|||
.2222222222222222...............
|
||||
.2222222222222222...............
|
||||
.2222222222222222...............
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.22222222222222223333333333.....
|
||||
.2222222222222222333333333301...
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333.1..
|
||||
.2222222222222222.3333333333....
|
||||
.2222222222222222.3333333333.0..
|
||||
................................
|
||||
`.trim(),
|
||||
);
|
||||
|
|
@ -926,9 +926,9 @@ describe("Scene/TextureAtlas", function () {
|
|||
.2222222222...
|
||||
.2222222222...
|
||||
.2222222222...
|
||||
.2222222222.1.
|
||||
.2222222222...
|
||||
.2222222222...
|
||||
.222222222201.
|
||||
.2222222222.0.
|
||||
..............
|
||||
`.trim(),
|
||||
);
|
||||
|
|
@ -976,16 +976,16 @@ describe("Scene/TextureAtlas", function () {
|
|||
.3333333333333333...............
|
||||
.3333333333333333...............
|
||||
.3333333333333333...............
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.33333333333333332222222222.....
|
||||
.3333333333333333222222222201...
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222.1..
|
||||
.3333333333333333.2222222222....
|
||||
.3333333333333333.2222222222.0..
|
||||
................................
|
||||
`.trim(),
|
||||
);
|
||||
|
|
@ -1337,6 +1337,108 @@ describe("Scene/TextureAtlas", function () {
|
|||
).contextToRender([0, 255, 0, 255]);
|
||||
});
|
||||
|
||||
it("adds custom padding with borderWidthInPixels", async function () {
|
||||
atlas = new TextureAtlas({ borderWidthInPixels: 0 });
|
||||
let indices = await addImages();
|
||||
|
||||
expect(drawAtlas(atlas, indices)).toBe(
|
||||
`
|
||||
................
|
||||
................
|
||||
................
|
||||
................
|
||||
................
|
||||
................
|
||||
2222222222......
|
||||
2222222222......
|
||||
2222222222......
|
||||
2222222222......
|
||||
2222222222......
|
||||
2222222222......
|
||||
22222222220.....
|
||||
22222222220.....
|
||||
22222222220.....
|
||||
222222222201....
|
||||
`.trim(),
|
||||
);
|
||||
|
||||
atlas = new TextureAtlas({ borderWidthInPixels: 2 });
|
||||
indices = await addImages();
|
||||
|
||||
expect(drawAtlas(atlas, indices)).toBe(
|
||||
`
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
..2222222222....................
|
||||
..2222222222....................
|
||||
..2222222222....................
|
||||
..2222222222..1.................
|
||||
..2222222222....................
|
||||
..2222222222....................
|
||||
..2222222222..0.................
|
||||
..2222222222..0.................
|
||||
..2222222222..0.................
|
||||
..2222222222..0.................
|
||||
................................
|
||||
................................
|
||||
`.trim(),
|
||||
);
|
||||
|
||||
atlas = new TextureAtlas({ borderWidthInPixels: 5 });
|
||||
indices = await addImages();
|
||||
|
||||
expect(drawAtlas(atlas, indices)).toBe(
|
||||
`
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
.....2222222222.................
|
||||
.....2222222222.................
|
||||
.....2222222222.................
|
||||
.....2222222222.................
|
||||
.....2222222222.................
|
||||
.....2222222222.................
|
||||
.....2222222222.....0...........
|
||||
.....2222222222.....0...........
|
||||
.....2222222222.....0...........
|
||||
.....2222222222.....0.....1.....
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
................................
|
||||
`.trim(),
|
||||
);
|
||||
|
||||
async function addImages() {
|
||||
const promise = Promise.all([
|
||||
atlas.addImage(tallGreenGuid, tallGreenImage),
|
||||
atlas.addImage(blueGuid, blueImage),
|
||||
atlas.addImage(bigBlueGuid, bigBlueImage),
|
||||
]);
|
||||
|
||||
return pollWhilePromise(promise, () => {
|
||||
atlas.update(scene.frameState.context);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("GUID changes when atlas texure is modified", async function () {
|
||||
atlas = new TextureAtlas();
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ describe(
|
|||
"./Data/Models/glTF-2.0/MeshPrimitiveRestart/glTF/MeshPrimitiveRestart.gltf";
|
||||
const edgeVisibilityTestData =
|
||||
"./Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibility.glb";
|
||||
const edgeVisibilityMaterialTestData =
|
||||
"./Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityMaterial.glb";
|
||||
const edgeVisibilityLineStringTestData =
|
||||
"./Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityLineString.glb";
|
||||
|
||||
let scene;
|
||||
const gltfLoaders = [];
|
||||
|
|
@ -227,14 +231,16 @@ describe(
|
|||
}
|
||||
|
||||
async function loadModifiedGltfAndTest(gltfPath, options, modifyFunction) {
|
||||
let gltf = await Resource.fetchJson({
|
||||
const arrayBuffer = await Resource.fetchArrayBuffer({
|
||||
url: gltfPath,
|
||||
});
|
||||
|
||||
gltf = modifyFunction(gltf);
|
||||
const gltfData = parseGlb(arrayBuffer);
|
||||
const modifiedGltf = modifyFunction(gltfData.gltf) ?? gltfData.gltf;
|
||||
const rebuiltGlb = createGlbBuffer(modifiedGltf, gltfData.binaryChunk);
|
||||
|
||||
spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue(
|
||||
Promise.resolve(generateJsonBuffer(gltf).buffer),
|
||||
Promise.resolve(rebuiltGlb),
|
||||
);
|
||||
|
||||
const gltfLoader = new GltfLoader(getOptions(gltfPath, options));
|
||||
|
|
@ -246,6 +252,111 @@ describe(
|
|||
return gltfLoader;
|
||||
}
|
||||
|
||||
function parseGlb(arrayBuffer) {
|
||||
const dataView = new DataView(arrayBuffer);
|
||||
if (dataView.byteLength < 12) {
|
||||
const jsonText = new TextDecoder().decode(new Uint8Array(arrayBuffer));
|
||||
return { gltf: JSON.parse(jsonText), binaryChunk: undefined };
|
||||
}
|
||||
|
||||
const magic = dataView.getUint32(0, true);
|
||||
if (magic !== 0x46546c67) {
|
||||
const jsonText = new TextDecoder().decode(new Uint8Array(arrayBuffer));
|
||||
return { gltf: JSON.parse(jsonText), binaryChunk: undefined };
|
||||
}
|
||||
|
||||
let offset = 12;
|
||||
let jsonObject;
|
||||
let binaryChunk;
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
while (offset < arrayBuffer.byteLength) {
|
||||
const chunkLength = dataView.getUint32(offset, true);
|
||||
offset += 4;
|
||||
const chunkType = dataView.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
const chunkData = new Uint8Array(arrayBuffer, offset, chunkLength);
|
||||
if (chunkType === 0x4e4f534a) {
|
||||
jsonObject = JSON.parse(textDecoder.decode(chunkData));
|
||||
} else if (chunkType === 0x004e4942) {
|
||||
binaryChunk = chunkData.slice();
|
||||
}
|
||||
|
||||
offset += chunkLength;
|
||||
}
|
||||
|
||||
if (!jsonObject) {
|
||||
throw new RuntimeError("GLB JSON chunk not found.");
|
||||
}
|
||||
|
||||
if (binaryChunk && jsonObject.buffers && jsonObject.buffers.length > 0) {
|
||||
jsonObject.buffers[0].byteLength = binaryChunk.length;
|
||||
delete jsonObject.buffers[0].uri;
|
||||
}
|
||||
|
||||
return { gltf: jsonObject, binaryChunk: binaryChunk };
|
||||
}
|
||||
|
||||
function createGlbBuffer(gltf, binaryChunk) {
|
||||
const textEncoder = new TextEncoder();
|
||||
const jsonBuffer = textEncoder.encode(JSON.stringify(gltf));
|
||||
const jsonPadding = (4 - (jsonBuffer.byteLength % 4)) % 4;
|
||||
const paddedJson = new Uint8Array(jsonBuffer.byteLength + jsonPadding);
|
||||
paddedJson.set(jsonBuffer);
|
||||
if (jsonPadding > 0) {
|
||||
paddedJson.fill(0x20, jsonBuffer.byteLength);
|
||||
}
|
||||
|
||||
let paddedBinary;
|
||||
if (binaryChunk && binaryChunk.length > 0) {
|
||||
const binPadding = (4 - (binaryChunk.length % 4)) % 4;
|
||||
paddedBinary = new Uint8Array(binaryChunk.length + binPadding);
|
||||
paddedBinary.set(binaryChunk);
|
||||
if (binPadding > 0) {
|
||||
paddedBinary.fill(0, binaryChunk.length);
|
||||
}
|
||||
}
|
||||
|
||||
const hasBinaryChunk = !!paddedBinary;
|
||||
const totalLength =
|
||||
12 +
|
||||
8 +
|
||||
paddedJson.byteLength +
|
||||
(hasBinaryChunk ? 8 + paddedBinary.byteLength : 0);
|
||||
|
||||
const glbBuffer = new ArrayBuffer(totalLength);
|
||||
const dataView = new DataView(glbBuffer);
|
||||
let offset = 0;
|
||||
|
||||
dataView.setUint32(offset, 0x46546c67, true);
|
||||
offset += 4;
|
||||
dataView.setUint32(offset, 2, true);
|
||||
offset += 4;
|
||||
dataView.setUint32(offset, totalLength, true);
|
||||
offset += 4;
|
||||
|
||||
dataView.setUint32(offset, paddedJson.byteLength, true);
|
||||
offset += 4;
|
||||
dataView.setUint32(offset, 0x4e4f534a, true);
|
||||
offset += 4;
|
||||
new Uint8Array(glbBuffer, offset, paddedJson.byteLength).set(paddedJson);
|
||||
offset += paddedJson.byteLength;
|
||||
|
||||
if (hasBinaryChunk) {
|
||||
dataView.setUint32(offset, paddedBinary.byteLength, true);
|
||||
offset += 4;
|
||||
dataView.setUint32(offset, 0x004e4942, true);
|
||||
offset += 4;
|
||||
new Uint8Array(glbBuffer, offset, paddedBinary.byteLength).set(
|
||||
paddedBinary,
|
||||
);
|
||||
offset += paddedBinary.byteLength;
|
||||
}
|
||||
|
||||
return glbBuffer;
|
||||
}
|
||||
|
||||
function getAttribute(attributes, semantic, setIndex) {
|
||||
const attributesLength = attributes.length;
|
||||
for (let i = 0; i < attributesLength; ++i) {
|
||||
|
|
@ -4374,6 +4485,76 @@ describe(
|
|||
}
|
||||
});
|
||||
|
||||
it("loads edge visibility material color override", async function () {
|
||||
const gltfLoader = await loadModifiedGltfAndTest(
|
||||
edgeVisibilityMaterialTestData,
|
||||
undefined,
|
||||
function (gltf) {
|
||||
const primitive = gltf.meshes[0].primitives[0];
|
||||
const extension =
|
||||
primitive.extensions.EXT_mesh_primitive_edge_visibility;
|
||||
extension.material = 0;
|
||||
|
||||
const material = gltf.materials[0];
|
||||
const pbr =
|
||||
material.pbrMetallicRoughness ??
|
||||
(material.pbrMetallicRoughness = {});
|
||||
pbr.baseColorFactor = [0.2, 0.4, 0.6, 0.8];
|
||||
|
||||
return gltf;
|
||||
},
|
||||
);
|
||||
|
||||
const primitive = gltfLoader.components.scene.nodes[0].primitives[0];
|
||||
const edgeVisibility = primitive.edgeVisibility;
|
||||
expect(edgeVisibility).toBeDefined();
|
||||
expect(edgeVisibility.materialColor).toEqualEpsilon(
|
||||
new Cartesian4(0.2, 0.4, 0.6, 0.8),
|
||||
CesiumMath.EPSILON7,
|
||||
);
|
||||
});
|
||||
|
||||
it("loads edge visibility line strings", async function () {
|
||||
const gltfLoader = await loadModifiedGltfAndTest(
|
||||
edgeVisibilityLineStringTestData,
|
||||
undefined,
|
||||
function (gltf) {
|
||||
const primitive = gltf.meshes[0].primitives[0];
|
||||
primitive.extensions = primitive.extensions ?? Object.create(null);
|
||||
primitive.extensions.EXT_mesh_primitive_edge_visibility = {
|
||||
lineStrings: [
|
||||
{
|
||||
indices: gltf.meshes[0].primitives[1].indices,
|
||||
material: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const material = gltf.materials[0];
|
||||
const pbr =
|
||||
material.pbrMetallicRoughness ??
|
||||
(material.pbrMetallicRoughness = {});
|
||||
pbr.baseColorFactor = [1.0, 0.5, 0.0, 1.0];
|
||||
|
||||
return gltf;
|
||||
},
|
||||
);
|
||||
|
||||
const primitive = gltfLoader.components.scene.nodes[0].primitives[0];
|
||||
const edgeVisibility = primitive.edgeVisibility;
|
||||
expect(edgeVisibility).toBeDefined();
|
||||
expect(edgeVisibility.lineStrings).toBeDefined();
|
||||
|
||||
const lineStrings = edgeVisibility.lineStrings;
|
||||
expect(lineStrings.length).toBe(1);
|
||||
expect(lineStrings[0].indices.length).toBeGreaterThan(0);
|
||||
expect(lineStrings[0].restartIndex).toBeDefined();
|
||||
expect(lineStrings[0].materialColor).toEqualEpsilon(
|
||||
new Cartesian4(1.0, 0.5, 0.0, 1.0),
|
||||
CesiumMath.EPSILON7,
|
||||
);
|
||||
});
|
||||
|
||||
it("validates edge visibility data loading", async function () {
|
||||
const gltfLoader = await loadGltf(edgeVisibilityTestData);
|
||||
const primitive = gltfLoader.components.scene.nodes[0].primitives[0];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
Cartesian4,
|
||||
Buffer,
|
||||
BufferUsage,
|
||||
ComponentDatatype,
|
||||
|
|
@ -314,6 +315,36 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () {
|
|||
expect(expectedEdges.size).toBe(3);
|
||||
});
|
||||
|
||||
it("generates edge color attribute for material overrides and line strings", function () {
|
||||
const primitive = createTestPrimitive();
|
||||
primitive.edgeVisibility.materialColor = new Cartesian4(0.2, 0.3, 0.4, 1.0);
|
||||
primitive.edgeVisibility.lineStrings = [
|
||||
{
|
||||
indices: new Uint16Array([0, 1, 65535, 1, 3]),
|
||||
restartIndex: 65535,
|
||||
materialColor: new Cartesian4(0.9, 0.1, 0.2, 1.0),
|
||||
},
|
||||
];
|
||||
|
||||
const renderResources = createMockRenderResources(primitive);
|
||||
const frameState = createMockFrameState();
|
||||
|
||||
EdgeVisibilityPipelineStage.process(renderResources, primitive, frameState);
|
||||
|
||||
expect(renderResources.edgeGeometry).toBeDefined();
|
||||
|
||||
const attributeLocations = renderResources.shaderBuilder.attributeLocations;
|
||||
expect(attributeLocations.a_edgeColor).toBeDefined();
|
||||
|
||||
const vertexDefines =
|
||||
renderResources.shaderBuilder._vertexShaderParts.defineLines;
|
||||
expect(vertexDefines).toContain("HAS_EDGE_COLOR_ATTRIBUTE");
|
||||
|
||||
const attributes =
|
||||
renderResources.edgeGeometry.vertexArray._attributes ?? [];
|
||||
expect(attributes.length).toBeGreaterThan(5);
|
||||
});
|
||||
|
||||
it("sets up uniforms correctly", function () {
|
||||
const primitive = createTestPrimitive();
|
||||
const renderResources = createMockRenderResources(primitive);
|
||||
|
|
|
|||
|
|
@ -2471,6 +2471,10 @@ describe(
|
|||
},
|
||||
scene,
|
||||
);
|
||||
await pollToPromise(function () {
|
||||
scene.renderForSpecs();
|
||||
return model._heightDirty === false;
|
||||
});
|
||||
expect(model._heightDirty).toBe(false);
|
||||
const terrainProvider = await CesiumTerrainProvider.fromUrl(
|
||||
"Data/CesiumTerrainTileJson/QuantizedMeshWithVertexNormals",
|
||||
|
|
|
|||
Loading…
Reference in New Issue