Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.4.0 #13186

Merged
merged 42 commits into from
May 24, 2024
Merged

v3.4.0 #13186

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3cf1f61
[MAPS3D-1348] Fix landmarks highlights v2 (internal-1383)
jtorresfabra May 10, 2024
c776150
Propagate padding set by `fitBounds` to the global map padding (inter…
stepankuzmin May 13, 2024
9b7d647
Invalidate playwright cache on lock file change (internal-1395)
stepankuzmin May 13, 2024
5bd8017
[MAPS3D-1335] Add model-front-cutoff property (internal-1371)
lasselammi May 14, 2024
b7c8994
Added test for cross-tile circle-sort-key (works incorrectly in GL Na…
woodroof May 14, 2024
24816a1
Bump puppeteer-core from 22.8.0 to 22.8.1 (internal-1396)
dependabot[bot] May 14, 2024
1ff39cc
Bump eslint-plugin-jsdoc from 48.2.3 to 48.2.4 (internal-1399)
dependabot[bot] May 14, 2024
33c1220
Bump playwright from 1.43.1 to 1.44.0 (internal-1398)
dependabot[bot] May 14, 2024
e9094fd
Bump glob from 10.3.12 to 10.3.15 (internal-1397)
dependabot[bot] May 14, 2024
6ad015f
Update mockServiceWorker (internal-1401)
stepankuzmin May 14, 2024
e93e1f3
Fix line-pattern precision (internal-1403)
aleksigron May 14, 2024
f15f62d
Add define to set highp default for fragment shader (internal-1405) (…
endanke May 14, 2024
1cb1441
[MAPS3D-1351] Fix zOffset for v2 tiles (internal-1388)
jtorresfabra May 14, 2024
e6857e0
Fix circular dependency path check (internal-1407)
stepankuzmin May 15, 2024
b1c1588
Drop vite-plugin-node-polyfills (internal-1404)
stepankuzmin May 15, 2024
8364469
[MAPS3D-1335] Update front cutoff render tests (internal-1410)
lasselammi May 15, 2024
049ae08
Devtools modifications and more parameters exposure: (internal-1392)
alexey-romanov May 16, 2024
0660140
Added note to lnglatbounds to explain the value range limits and how …
emilygamedeveloper May 14, 2024
7633196
Updated latitude values to match the mercator max/min
emilygamedeveloper May 15, 2024
6ede823
v3.4.0-beta.2 (internal-1416)
stepankuzmin May 17, 2024
d8f8e43
fix incorrect tile coordinate transforms in replacement_source (inter…
akoylasar May 17, 2024
6063693
[MAPS3D-1353] Fix shadow normal offset for v2 tiles (internal-1417)
jtorresfabra May 20, 2024
ca46c66
[GLJS-820] Add getConfig/setConfig and getSchema/setSchema to Map (in…
stepankuzmin May 20, 2024
fed58b8
Fix local `start-release` workflow (internal-1420)
stepankuzmin May 20, 2024
d957cdf
Update Rollup to v4.x (internal-1423)
stepankuzmin May 20, 2024
85ff5c0
Fix raster-particle rendering precision issues on Android devices (in…
Nelarius May 21, 2024
90d5341
Add source bounds to raster-particle-layer example (internal-1414)
Nelarius May 21, 2024
5aabf98
Fix packing functions of raster-particle shaders (internal-1426)
Nelarius May 21, 2024
26819be
--- (internal-1425)
dependabot[bot] May 21, 2024
acc985f
--- (internal-1424)
dependabot[bot] May 21, 2024
99f7721
Add `config` option to the Map constructor and `setStyle` (internal-1…
stepankuzmin May 21, 2024
1229b73
[MAPSNAT-1863] Added render test for the fill-layer with MultiPolygon…
dserebriakov May 21, 2024
0f5ba1c
Improve Flow typings (internal-1427)
stepankuzmin May 21, 2024
1dbc1cc
[GLJS-679] Introduce partial update API for GeoJSON sources (internal…
mourner May 21, 2024
9ccd178
Update `map.getStyle` JSDoc to highlight Mapbox Standard differences …
stepankuzmin May 21, 2024
72fa05d
Improve Flow typings (internal-1428)
stepankuzmin May 22, 2024
d6c8bb8
--- (internal-1429)
dependabot[bot] May 22, 2024
a430303
Fix for z-offset in v2 tiles and z-offset flickering in v1 and v2 til…
jtorresfabra May 22, 2024
dd68665
Improve Flow types for the `LngLatBounds` constructor (internal-1431)
stepankuzmin May 22, 2024
cd62ed0
Expose `*-transition` properties in Style Spec types (internal-1433)
stepankuzmin May 22, 2024
71c6b78
Cherry-pick `wrapIndent` enforcement (internal-1434)
mourner May 22, 2024
2b6915c
v3.4.0
mourner May 22, 2024
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
20 changes: 12 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ jobs:
- run: npm run build-style-spec
- run: npm run build-flow-types
- run: npm run test-build
- run: while read l; do cp debug/$l test/release/$l; done < test/release/local_release_page_list.txt
- run: npm run prepare-release-pages
- run:
name: Create mapbox-gl.tar.gz
command: |
Expand All @@ -279,11 +279,21 @@ jobs:
steps:
- attach_workspace:
at: ~/
- restore_cache:
keys:
- v0-playwright-{{ checksum "package-lock.json" }}
- run:
name: Playwright version
command: npx playwright --version
- run: npx playwright install chromium
- save_cache:
key: v0-playwright
key: v0-playwright-{{ checksum "package-lock.json" }}
paths:
- ~/.cache
- persist_to_workspace:
root: ~/
paths:
- .cache

check-bundle-size:
<<: *linux-defaults
Expand All @@ -306,9 +316,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- restore_cache:
keys:
- v0-playwright
- run:
command: npm run test-unit
no_output_timeout: 5m
Expand Down Expand Up @@ -345,9 +352,6 @@ jobs:
steps:
- attach_workspace:
at: ~/
- restore_cache:
keys:
- v0-playwright
- run:
command: npm run test-csp
no_output_timeout: 5m
Expand Down
10 changes: 8 additions & 2 deletions 3d-style/data/bucket/tiled_3d_model_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {clamp} from '../../../src/util/util.js';
import {DEMSampler} from '../../../src/terrain/elevation.js';
import {ZoomConstantExpression} from '../../../src/style-spec/expression/index.js';
import {Aabb} from '../../../src/util/primitives.js';
import {vec3} from 'gl-matrix';

import type ModelStyleLayer from '../../style/style_layer/model_style_layer.js';
import type {ReplacementSource} from '../../source/replacement_source.js';
Expand Down Expand Up @@ -506,17 +507,22 @@ class Tiled3dModelBucket implements Bucket {
const nodesInfo = this.getNodesInfo();
const candidates = [];

const tmpVertex = [0, 0, 0];

for (let i = 0; i < this.nodesInfo.length; i++) {
const nodeInfo = nodesInfo[i];
assert(nodeInfo.node.meshes.length > 0);
const mesh = nodeInfo.node.meshes[0];
if (x < mesh.aabb.min[0] || y < mesh.aabb.min[1] || x > mesh.aabb.max[0] || y > mesh.aabb.max[1]) continue;
const meshAabb = Aabb.applyTransform(mesh.aabb, nodeInfo.node.matrix);
if (x < meshAabb.min[0] || y < meshAabb.min[1] || x > meshAabb.max[0] || y > meshAabb.max[1]) continue;

assert(mesh.heightmap);
const xCell = ((x - mesh.aabb.min[0]) / (mesh.aabb.max[0] - mesh.aabb.min[0]) * HEIGHTMAP_DIM) | 0;
const yCell = ((y - mesh.aabb.min[1]) / (mesh.aabb.max[1] - mesh.aabb.min[1]) * HEIGHTMAP_DIM) | 0;
const heightmapIndex = Math.min(HEIGHTMAP_DIM - 1, yCell) * HEIGHTMAP_DIM + Math.min(HEIGHTMAP_DIM - 1, xCell);

tmpVertex[2] = mesh.heightmap[heightmapIndex];
vec3.transformMat4(tmpVertex, tmpVertex, nodeInfo.node.matrix);
if (mesh.heightmap[heightmapIndex] < 0 && nodeInfo.node.footprint) {
// unpopulated cell. If it is in the building footprint, return undefined height
nodeInfo.node.footprint.grid.query(new Point(x, y), new Point(x, y), candidates);
Expand All @@ -526,7 +532,7 @@ class Tiled3dModelBucket implements Bucket {
continue;
}
if (nodeInfo.hiddenByReplacement) return; // better luck with the next source
return {height: mesh.heightmap[heightmapIndex], maxHeight: nodeInfo.feature.properties["height"], hidden: false, verticalScale: nodeInfo.evaluatedScale[2]};
return {height: tmpVertex[2], maxHeight: nodeInfo.feature.properties["height"], hidden: false, verticalScale: nodeInfo.evaluatedScale[2]};
}
}
}
Expand Down
16 changes: 12 additions & 4 deletions 3d-style/render/draw_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,12 @@ function drawMesh(sortedMesh: SortedMesh, painter: Painter, layer: ModelStyleLay
mat4.transpose(normalMatrix, normalMatrix);

const emissiveStrength = layer.paint.get('model-emissive-strength').constantOr(0.0);

const uniformValues = modelUniformValues(
new Float32Array(sortedMesh.worldViewProjection),
new Float32Array(lightingMatrix),
new Float32Array(normalMatrix),
/* $FlowIgnore[incompatible-call] we can safely pass null for node.matrix as it is handled in the modelUniformValues constructor */
null,
painter,
opacity,
pbr.baseColorFactor,
Expand Down Expand Up @@ -621,11 +622,12 @@ function drawInstancedNode(painter: Painter, layer: ModelStyleLayer, node: Node,
const layerOpacity = layer.paint.get('model-opacity');

const emissiveStrength = layer.paint.get('model-emissive-strength').constantOr(0.0);

uniformValues = modelUniformValues(
coord.expandedProjMatrix,
Float32Array.from(node.matrix),
new Float32Array(16),
/* $FlowIgnore[incompatible-call] we can safely pass null for node.matrix as it is handled in the modelUniformValues constructor */
null,
painter,
layerOpacity,
pbr.baseColorFactor,
Expand Down Expand Up @@ -788,12 +790,15 @@ function drawBatchedModels(painter: Painter, source: SourceCache, layer: ModelSt
// keep model and nodemodel matrices separate for rendering door lights
const nodeModelMatrix = mat4.multiply([], tileModelMatrix, node.matrix);

const lightingMatrix = mat4.multiply([], zScaleMatrix, tileModelMatrix);
let lightingMatrix = mat4.multiply([], zScaleMatrix, tileModelMatrix);
mat4.multiply(lightingMatrix, negCameraPosMatrix, lightingMatrix);
const normalMatrix = mat4.invert([], lightingMatrix);
mat4.transpose(normalMatrix, normalMatrix);
mat4.scale(normalMatrix, normalMatrix, normalScale);

// lighting matrix should take node.matrix into account
lightingMatrix = mat4.multiply(lightingMatrix, lightingMatrix, node.matrix);

const isLightBeamPass = painter.renderPass === 'light-beam';
const wpvForNode = mat4.multiply([], tr.expandedFarZProjMatrix, nodeModelMatrix);
// Lights come in tilespace so wvp should not include node.matrix when rendering door ligths
Expand Down Expand Up @@ -873,7 +878,9 @@ function drawBatchedModels(painter: Painter, source: SourceCache, layer: ModelSt
const program = painter.getOrCreateProgram('model', programOptions);

if (!isShadowPass && shadowRenderer) {
shadowRenderer.setupShadowsFromMatrix(nodeModelMatrix, program, shadowRenderer.useNormalOffset);
// The shadow matrix does not need to include node transforms,
// as shadow_pos will be performing that transform in the shader
shadowRenderer.setupShadowsFromMatrix(tileModelMatrix, program, shadowRenderer.useNormalOffset);
}

painter.uploadCommonUniforms(context, program, null, fogMatrixArray);
Expand All @@ -888,6 +895,7 @@ function drawBatchedModels(painter: Painter, source: SourceCache, layer: ModelSt
new Float32Array(worldViewProjection),
new Float32Array(lightingMatrix),
new Float32Array(normalMatrix),
new Float32Array(node.matrix),
painter,
layerOpacity,
pbr.baseColorFactor,
Expand Down
9 changes: 7 additions & 2 deletions 3d-style/render/program/model_program.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

import {mat3, vec3} from 'gl-matrix';
import {mat3, mat4, vec3} from 'gl-matrix';
import {
Uniform1i,
Uniform1f,
Expand All @@ -22,6 +22,7 @@ export type ModelUniformsType = {
'u_matrix': UniformMatrix4f,
'u_lighting_matrix': UniformMatrix4f,
'u_normal_matrix': UniformMatrix4f,
'u_node_matrix': UniformMatrix4f,
'u_lightpos': Uniform3f,
'u_lightintensity': Uniform1f,
'u_lightcolor': Uniform3f,
Expand Down Expand Up @@ -49,6 +50,7 @@ const modelUniforms = (context: Context): ModelUniformsType => ({
'u_matrix': new UniformMatrix4f(context),
'u_lighting_matrix': new UniformMatrix4f(context),
'u_normal_matrix': new UniformMatrix4f(context),
'u_node_matrix': new UniformMatrix4f(context),
'u_lightpos': new Uniform3f(context),
'u_lightintensity': new Uniform1f(context),
'u_lightcolor': new Uniform3f(context),
Expand All @@ -73,10 +75,13 @@ const modelUniforms = (context: Context): ModelUniformsType => ({

});

const emptyMat4 = new Float32Array(mat4.identity([]));

const modelUniformValues = (
matrix: Float32Array,
lightingMatrix: Float32Array,
normalMatrix: Float32Array,
nodeMatrix: Float32Array,
painter: Painter,
opacity: number,
baseColorFactor: Color,
Expand Down Expand Up @@ -112,6 +117,7 @@ const modelUniformValues = (
'u_matrix': matrix,
'u_lighting_matrix': lightingMatrix,
'u_normal_matrix': normalMatrix,
'u_node_matrix': nodeMatrix ? nodeMatrix : emptyMat4,
'u_lightpos': lightPos,
'u_lightintensity': light.properties.get('intensity'),
'u_lightcolor': [lightColor.r, lightColor.g, lightColor.b],
Expand Down Expand Up @@ -150,7 +156,6 @@ const modelDepthUniforms = (context: Context): ModelDepthUniformsType => ({
'u_node_matrix': new UniformMatrix4f(context)
});

const emptyMat4 = new Float32Array(16);
const modelDepthUniformValues = (
matrix: Float32Array,
instance: Float32Array = emptyMat4,
Expand Down
16 changes: 10 additions & 6 deletions 3d-style/render/shadow_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export class ShadowRenderer {
shadowDirection: Vec3;
useNormalOffset: boolean;

_forceDisable: boolean;

constructor(painter: Painter) {
this.painter = painter;
this._enabled = false;
Expand All @@ -159,9 +161,11 @@ export class ShadowRenderer {
this._receivers = new ShadowReceivers();
this._depthMode = new DepthMode(painter.context.gl.LEQUAL, DepthMode.ReadWrite, [0, 1]);
this._uniformValues = defaultShadowUniformValues();
this._forceDisable = false;

this.useNormalOffset = false;

painter.tp.registerParameter(this, ["Shadows"], "_forceDisable", {label: "forceDisable"}, () => { this.painter.style.map.triggerRepaint(); });
painter.tp.registerParameter(shadowParameters, ["Shadows"], "cascadeCount", {min: 1, max: 2, step: 1});
painter.tp.registerParameter(shadowParameters, ["Shadows"], "shadowMapResolution", {min: 32, max: 2048, step: 32});
painter.tp.registerBinding(this, ["Shadows"], "_numCascadesToRender", {readonly: true, label: 'numCascadesToRender'});
Expand Down Expand Up @@ -201,7 +205,7 @@ export class ShadowRenderer {

this._enabled = this._shadowLayerCount > 0;

if (!this._enabled) {
if (!this.enabled) {
return;
}

Expand Down Expand Up @@ -309,7 +313,7 @@ export class ShadowRenderer {
}

get enabled(): boolean {
return this._enabled;
return this._enabled && !this._forceDisable;
}

set enabled(enabled: boolean) {
Expand All @@ -318,7 +322,7 @@ export class ShadowRenderer {
}

drawShadowPass(style: Style, sourceCoords: {[_: string]: Array<OverscaledTileID>}) {
if (!this._enabled) {
if (!this.enabled) {
return;
}

Expand Down Expand Up @@ -356,7 +360,7 @@ export class ShadowRenderer {
}

drawGroundShadows() {
if (!this._enabled) {
if (!this.enabled) {
return;
}

Expand Down Expand Up @@ -425,7 +429,7 @@ export class ShadowRenderer {
}

setupShadows(unwrappedTileID: UnwrappedTileID, program: Program<*>, normalOffsetMode: ?ShadowNormalOffsetMode, tileOverscaledZ: number = 0) {
if (!this._enabled) {
if (!this.enabled) {
return;
}

Expand Down Expand Up @@ -467,7 +471,7 @@ export class ShadowRenderer {
}

setupShadowsFromMatrix(worldMatrix: Mat4, program: Program<*>, normalOffset: boolean = false) {
if (!this._enabled) {
if (!this.enabled) {
return;
}

Expand Down
17 changes: 9 additions & 8 deletions 3d-style/shaders/model.vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ in vec3 a_pos_3f;
// interpolatedHeight = pow(pos_z * .x + .y, .z)

uniform mat4 u_matrix;
uniform mat4 u_node_matrix;
uniform mat4 u_lighting_matrix;
uniform vec3 u_camera_pos;
uniform vec4 u_color_mix;
Expand Down Expand Up @@ -170,22 +171,22 @@ void main() {
#endif

#ifdef RENDER_SHADOWS
vec3 shadow_pos = local_pos;
vec4 shadow_pos = u_node_matrix * vec4(local_pos, 1.0);
#ifdef NORMAL_OFFSET
#ifdef HAS_ATTRIBUTE_a_normal_3f
#ifdef MODEL_POSITION_ON_GPU
// flip the xy to bring it to the same, wrong, fill extrusion normal orientation toward inside.
// See the explanation in shadow_normal_offset.
vec3 offset = shadow_normal_offset(vec3(-normal_3f.xy, normal_3f.z));
shadow_pos += offset * shadow_normal_offset_multiplier0();
// flip the xy to bring it to the same, wrong, fill extrusion normal orientation toward inside.
// See the explanation in shadow_normal_offset.
vec3 offset = shadow_normal_offset(vec3(-normal_3f.xy, normal_3f.z));
shadow_pos.xyz += offset * shadow_normal_offset_multiplier0();
#else
vec3 offset = shadow_normal_offset_model(normalize(normal_3f));
shadow_pos += offset * shadow_normal_offset_multiplier0();
shadow_pos.xyz += offset * shadow_normal_offset_multiplier0();
#endif
#endif // HAS_ATTRIBUTE_a_normal_3f
#endif // NORMAL_OFFSET
v_pos_light_view_0 = u_light_matrix_0 * vec4(shadow_pos, 1);
v_pos_light_view_1 = u_light_matrix_1 * vec4(shadow_pos, 1);
v_pos_light_view_0 = u_light_matrix_0 * shadow_pos;
v_pos_light_view_1 = u_light_matrix_1 * shadow_pos;
v_depth_shadows = gl_Position.w;
#endif // RENDER_SHADOWS
}
7 changes: 3 additions & 4 deletions 3d-style/source/replacement_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class ReplacementSource {
const active = this._activeRegions[idx];

// Go through each footprint in the current priority level
// and check whether they're been occluded by any other regions
// and check whether they've been occluded by any other regions
// with higher priority
active.hiddenByOverlap = false;

Expand Down Expand Up @@ -359,9 +359,8 @@ function footprintsIntersect(a: Footprint, aTile: UnwrappedTileID, b: Footprint,
const zDiff = Math.pow(2.0, dstId.z - srcId.z);

queryVertices = a.vertices.map(v => {
const x = (v.x * srcId.x * EXTENT) * zDiff - dstId.x * EXTENT;
const y = (v.y * srcId.y * EXTENT) * zDiff - dstId.y * EXTENT;

const x = (v.x + srcId.x * EXTENT) * zDiff - dstId.x * EXTENT;
const y = (v.y + srcId.y * EXTENT) * zDiff - dstId.y * EXTENT;
return new Point(x, y);
});
}
Expand Down
2 changes: 2 additions & 0 deletions 3d-style/style/style_layer/model_style_layer_properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type PaintProps = {|
"model-roughness": DataDrivenProperty<number>,
"model-height-based-emissive-strength-multiplier": DataDrivenProperty<[number, number, number, number, number]>,
"model-cutoff-fade-range": DataConstantProperty<number>,
"model-front-cutoff": DataConstantProperty<[number, number, number]>,
|};

const paint: Properties<PaintProps> = new Properties({
Expand All @@ -59,6 +60,7 @@ const paint: Properties<PaintProps> = new Properties({
"model-roughness": new DataDrivenProperty(styleSpec["paint_model"]["model-roughness"]),
"model-height-based-emissive-strength-multiplier": new DataDrivenProperty(styleSpec["paint_model"]["model-height-based-emissive-strength-multiplier"]),
"model-cutoff-fade-range": new DataConstantProperty(styleSpec["paint_model"]["model-cutoff-fade-range"]),
"model-front-cutoff": new DataConstantProperty(styleSpec["paint_model"]["model-front-cutoff"]),
});

// Note: without adding the explicit type annotation, Flow infers weaker types
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
## 3.4.0-beta.1
## 3.4.0

### Features and improvements ✨
- Add `dynamic: true` option for GeoJSON sources that enables partial update API with `source.updateData` method. Further optimizations for this mode are expected in future releases.
- Add `Map` `setConfig`, `getConfig`, `setSchema` and `getSchema` methods for batch setting of style configuration options.
- Add `config` option for the `Map` constructor and `setStyle` methods for conveniently setting style configuration options on map initialization.
- Add `icon-color-saturation`, `icon-color-contrast`, `icon-color-brightness-min` and `icon-color-brightness-max` to control symbol layer appearance.
- Introduce a new `line-join` mode: `none` to improve line pattern distortions around joins.
- Extend `model-id` property to support URIs (in addition to style-defined model references).
- Expose more parameters in map `devtools` UI.

### Bug fixes 🐞
- Fix an issue with `flyTo` ignoring `padding` in its options.
- Respect padding in `cameraForBounds` on globe view. (h/t [@jonasnoki](https://github.com/jonasnoki)) [#13126](https://github.com/mapbox/mapbox-gl-js/pull/13126)
- Fix `preloadOnly` not preloading tiles from style imports.
- Fix `queryRenderedFeatures` for non-integer ID in non-tiled model sources
- Fix `model-scale` property for large number of 3D models.
- Fix flickering of `raster-particle` layer on globe view.
- Improve rendering of low-resolution `raster-array` data.
- Fix an issue with GL JS bundle not building locally on Windows.
- Fix multiple edge cases when using `symbol-z-elevate`.
- Fix rendering issues with `raster-particle` layer on certain Android devices.
- Fix shadow and lighting rendering issues in certain areas when using Mapbox Standard.

## 3.3.0

Expand Down
Loading
Loading