Skip to content

Commit c28b11b

Browse files
stephanheiglgithub-actions[bot]
authored andcommitted
QRF support for model source.
GitOrigin-RevId: 393e3ebd5db57ad21f429d2e413102c98cf83a76
1 parent fa1edb0 commit c28b11b

File tree

15 files changed

+471
-14
lines changed

15 files changed

+471
-14
lines changed

3d-style/data/model.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,11 @@ export default class Model {
297297
nodeOverrideNames: string[] = [];
298298
featureProperties: Record<string, unknown> = {};
299299

300-
constructor(id: string, position: [number, number] | null | undefined, orientation: [number, number, number] | null | undefined, nodes: Array<ModelNode>) {
300+
uri: string;
301+
302+
constructor(id: string, uri: string, position: [number, number] | null | undefined, orientation: [number, number, number] | null | undefined, nodes: Array<ModelNode>) {
301303
this.id = id;
304+
this.uri = uri;
302305
this.position = position != null ? new LngLat(position[0], position[1]) : new LngLat(0, 0);
303306

304307
this.orientation = orientation != null ? orientation : [0, 0, 0];

3d-style/render/model_manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class ModelManager extends Evented {
4949
if (!gltf) return;
5050

5151
const nodes = convertModel(gltf);
52-
const model = new Model(id, undefined, undefined, nodes);
52+
const model = new Model(id, url, undefined, undefined, nodes);
5353
model.computeBoundsAndApplyParent();
5454
return model;
5555
})

3d-style/source/model_source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class ModelSource extends Evented<SourceEvents> implements ISource {
113113
const modelPromise = loadGLTF(this.map._requestManager.transformRequest(modelSpec.uri, ResourceType.Model).url).then(gltf => {
114114
if (!gltf) return;
115115
const nodes = convertModel(gltf);
116-
const model = new Model(modelId, modelSpec.position, modelSpec.orientation, nodes);
116+
const model = new Model(modelId, modelSpec.uri, modelSpec.position, modelSpec.orientation, nodes);
117117
ModelSource.applyModelSpecification(model, modelSpec);
118118
model.computeBoundsAndApplyParent();
119119
this.models.push(model);

3d-style/style/style_layer/model_style_layer.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import {latFromMercatorY, lngFromMercatorX} from '../../../src/geo/mercator_coor
99
import EXTENT from '../../../src/style-spec/data/extent';
1010
import {convertModelMatrixForGlobe, queryGeometryIntersectsProjectedAabb} from '../../util/model_util';
1111
import Tiled3dModelBucket from '../../data/bucket/tiled_3d_model_bucket';
12+
import Feature from '../../../src/util/vectortile_to_geojson';
13+
import {type Feature as ExpressionEvalFeature, type FeatureState} from '../../../src/style-spec/expression/index';
14+
import ModelSource from '../../source/model_source';
1215

1316
import type {Layout, Transitionable, Transitioning, PossiblyEvaluated, PropertyValue, ConfigOptions} from '../../../src/style/properties';
1417
import type Point from '@mapbox/point-geometry';
15-
import type {LayerSpecification} from '../../../src/style-spec/types';
18+
import type {LayerSpecification, ModelLayerSpecification} from '../../../src/style-spec/types';
1619
import type {PaintProps, LayoutProps} from './model_style_layer_properties';
1720
import type {BucketParameters, Bucket} from '../../../src/data/bucket';
18-
import type {TilespaceQueryGeometry} from '../../../src/style/query_geometry';
19-
import type {FeatureState} from '../../../src/style-spec/expression/index';
21+
import type {QueryGeometry, TilespaceQueryGeometry} from '../../../src/style/query_geometry';
2022
import type Transform from '../../../src/geo/transform';
2123
import type ModelManager from '../../render/model_manager';
2224
import type {ModelNode} from '../../data/model';
@@ -25,6 +27,8 @@ import type {CanonicalTileID} from '../../../src/source/tile_id';
2527
import type {LUT} from "../../../src/util/lut";
2628
import type {EvaluationFeature} from '../../../src/data/evaluation_feature';
2729
import type {ProgramName} from '../../../src/render/program';
30+
import type {QueryResult} from '../../../src/source/query_features';
31+
import type SourceCache from '../../../src/source/source_cache';
2832

2933
class ModelStyleLayer extends StyleLayer {
3034
override type: 'model';
@@ -37,13 +41,15 @@ class ModelStyleLayer extends StyleLayer {
3741
override paint: PossiblyEvaluated<PaintProps>;
3842

3943
modelManager: ModelManager;
44+
layer: ModelLayerSpecification;
4045

4146
constructor(layer: LayerSpecification, scope: string, lut: LUT | null, options?: ConfigOptions | null) {
4247
const properties = {
4348
layout: getLayoutProperties(),
4449
paint: getPaintProperties()
4550
};
4651
super(layer, properties, scope, lut, options);
52+
this.layer = layer as ModelLayerSpecification;
4753
this._stats = {numRenderedVerticesInShadowPass: 0, numRenderedVerticesInTransparentPass: 0};
4854
}
4955

@@ -79,6 +85,81 @@ class ModelStyleLayer extends StyleLayer {
7985
return (bucket instanceof Tiled3dModelBucket) ? EXTENT - 1 : 0;
8086
}
8187

88+
override queryRenderedFeatures(
89+
queryGeometry: QueryGeometry,
90+
sourceCache: SourceCache,
91+
transform: Transform
92+
): QueryResult {
93+
const source = sourceCache.getSource<ModelSource>();
94+
if (!source || !(source instanceof ModelSource)) return {};
95+
const modelSource: ModelSource = source;
96+
97+
const result: QueryResult = {};
98+
result[this.id] = [];
99+
const layerResult = result[this.id];
100+
101+
let modelFeatureIndex = 0;
102+
for (const model of modelSource.models) {
103+
const modelFeatureState = sourceCache.getFeatureState(this.sourceLayer, model.id);
104+
105+
const modelFeatureForEval: ExpressionEvalFeature = {
106+
type: 'Unknown',
107+
id: model.id,
108+
properties: model.featureProperties
109+
};
110+
const rotation = this.paint.get('model-rotation').evaluate(modelFeatureForEval, modelFeatureState);
111+
const scale = this.paint.get('model-scale').evaluate(modelFeatureForEval, modelFeatureState);
112+
const translation = this.paint.get('model-translation').evaluate(modelFeatureForEval, modelFeatureState);
113+
const elevationReference = this.paint.get('model-elevation-reference');
114+
const shouldFollowTerrainSlope = elevationReference === 'ground';
115+
const shouldApplyElevation = elevationReference === 'ground';
116+
117+
let matrix: mat4 = [];
118+
calculateModelMatrix(matrix,
119+
model,
120+
transform,
121+
model.position,
122+
rotation,
123+
scale,
124+
translation,
125+
shouldApplyElevation,
126+
shouldFollowTerrainSlope,
127+
false);
128+
129+
if (transform.projection.name === 'globe') {
130+
matrix = convertModelMatrixForGlobe(matrix, transform);
131+
}
132+
const worldViewProjection = mat4.multiply([], transform.projMatrix, matrix);
133+
134+
const projectedQueryGeometry = queryGeometry.isPointQuery() ? queryGeometry.screenBounds : queryGeometry.screenGeometry;
135+
136+
const depth = queryGeometryIntersectsProjectedAabb(projectedQueryGeometry, transform, worldViewProjection, model.aabb);
137+
if (depth != null) {
138+
const modelFeature: Feature = new Feature(undefined, 0, 0, 0, model.id);
139+
modelFeature.layer = this.layer;
140+
// Use unsafe assignment for now, due to restriction of GeoJSON/Feature properties to number, string and boolean.
141+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
142+
modelFeature.properties = structuredClone(model.featureProperties) as any;
143+
modelFeature.properties['layer'] = this.id;
144+
modelFeature.properties['uri'] = model.uri;
145+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
146+
modelFeature.properties['orientation'] = model.orientation as any;
147+
modelFeature.sourceLayer = this.sourceLayer;
148+
modelFeature.geometry = {
149+
type: 'Point',
150+
coordinates: [model.position.lng, model.position.lat]
151+
};
152+
modelFeature.state = modelFeatureState;
153+
modelFeature.source = this.source;
154+
layerResult.push({featureIndex: modelFeatureIndex, feature: modelFeature, intersectionZ: depth});
155+
}
156+
157+
++modelFeatureIndex;
158+
}
159+
160+
return result;
161+
}
162+
82163
override queryIntersectsFeature(
83164
queryGeometry: TilespaceQueryGeometry,
84165
feature: VectorTileFeature,

src/source/query_features.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ export function queryRenderedFeatures(
117117
}
118118
}
119119

120+
for (const layerId in query.layers) {
121+
const layer = query.layers[layerId];
122+
if (layer.styleLayer) {
123+
const queryResults = layer.styleLayer.queryRenderedFeatures(queryGeometry, query.sourceCache, transform);
124+
if (Object.keys(queryResults).length) {
125+
renderedFeatureLayers.push({wrappedTileID: 0, queryResults});
126+
}
127+
}
128+
}
129+
120130
if (renderedFeatureLayers.length === 0) {
121131
return {};
122132
}

src/style/style_layer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import type {
2626
} from '../style-spec/types';
2727
import type {CustomLayerInterface} from './style_layer/custom_style_layer';
2828
import type {Map as MapboxMap} from '../ui/map';
29-
import type {TilespaceQueryGeometry} from './query_geometry';
29+
import type {QueryGeometry, TilespaceQueryGeometry} from './query_geometry';
3030
import type {DEMSampler} from '../terrain/elevation';
3131
import type {VectorTileFeature} from '@mapbox/vector-tile';
3232
import type {CreateProgramParams} from '../render/painter';
@@ -36,6 +36,7 @@ import type {LUT} from '../util/lut';
3636
import type {ImageId} from '../style-spec/expression/types/image_id';
3737
import type {ProgramName} from '../render/program';
3838
import type {AppearanceProps} from './appearance_properties';
39+
import type {QueryResult} from '../source/query_features';
3940

4041
const TRANSITION_SUFFIX = '-transition';
4142

@@ -462,6 +463,12 @@ class StyleLayer extends Evented {
462463
return this.appearances;
463464
}
464465

466+
queryRenderedFeatures(
467+
queryGeometry: QueryGeometry,
468+
sourceCache: SourceCache,
469+
transform: Transform
470+
) : QueryResult { return {}; }
471+
465472
// @ts-expect-error - TS2355 - A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
466473
queryRadius(_bucket: Bucket): number {}
467474

src/util/vectortile_to_geojson.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Feature implements GeoJSONFeature {
2020
properties: Record<string, string | number | boolean>;
2121
tile?: {z: number; x: number; y: number};
2222
_geometry?: GeoJSON.Geometry;
23-
_vectorTileFeature: VectorTileFeature;
23+
_vectorTileFeature: VectorTileFeature | null | undefined;
2424
_x: number;
2525
_y: number;
2626
_z: number;
@@ -31,15 +31,15 @@ class Feature implements GeoJSONFeature {
3131
state?: FeatureState;
3232
variants?: Record<string, FeatureVariant[]>;
3333

34-
constructor(vectorTileFeature: VectorTileFeature, z: number, x: number, y: number, id?: string | number) {
34+
constructor(vectorTileFeature: VectorTileFeature | null | undefined, z: number, x: number, y: number, id?: string | number) {
3535
this.type = 'Feature';
3636

3737
this._vectorTileFeature = vectorTileFeature;
3838
this._z = z;
3939
this._x = x;
4040
this._y = y;
4141

42-
this.properties = vectorTileFeature.properties;
42+
this.properties = vectorTileFeature ? vectorTileFeature.properties : {};
4343
this.id = id;
4444
}
4545

@@ -53,7 +53,7 @@ class Feature implements GeoJSONFeature {
5353
}
5454

5555
get geometry(): GeoJSON.Geometry {
56-
if (this._geometry === undefined) {
56+
if (this._geometry === undefined && this._vectorTileFeature) {
5757
this._geometry = this._vectorTileFeature.toGeoJSON(this._x, this._y, this._z).geometry;
5858
}
5959
return this._geometry;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[
2+
{
3+
"type": "Feature",
4+
"state": {},
5+
"geometry": {
6+
"type": "Point",
7+
"coordinates": [
8+
20,
9+
20
10+
]
11+
},
12+
"properties": {
13+
"layer": "model",
14+
"uri": "local://models/low-poly-car.gltf",
15+
"orientation": [
16+
-45,
17+
0,
18+
45
19+
]
20+
},
21+
"id": "model-on-side2",
22+
"source": "model"
23+
}
24+
]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{
2+
"version": 8,
3+
"metadata": {
4+
"test": {
5+
"width": 512,
6+
"height": 512,
7+
"operations": [
8+
["setProjection", "globe"],
9+
["wait"]
10+
],
11+
"queryGeometry": [
12+
[
13+
426,
14+
203
15+
],
16+
[
17+
430,
18+
230
19+
]
20+
]
21+
}
22+
},
23+
"center": [ 0, 0 ],
24+
"zoom": 3.00,
25+
"pitch": 60,
26+
"fog": {
27+
"star-intensity": 0.0
28+
},
29+
"sources": {
30+
"model": {
31+
"type": "model",
32+
"models": {
33+
"model-1" : {
34+
"uri": "local://models/low-poly-car.gltf",
35+
"position": [0, 0],
36+
"orientation": [0, 0, 45]
37+
},
38+
"model-on-bumper-1" : {
39+
"uri": "local://models/low-poly-car.gltf",
40+
"position": [-20, 20],
41+
"orientation": [0, 110, 180]
42+
},
43+
"model-on-roof-resting" : {
44+
"uri": "local://models/low-poly-car.gltf",
45+
"position": [-20, -20],
46+
"orientation": [0, 180, 180]
47+
},
48+
"model-on-bumper-resting" : {
49+
"uri": "local://models/low-poly-car.gltf",
50+
"position": [0, -20],
51+
"orientation": [0, 90, 180]
52+
},
53+
"model-on-side1" : {
54+
"uri": "local://models/low-poly-car.gltf",
55+
"position": [20, 0],
56+
"orientation": [-90, 0, 45]
57+
},
58+
"model-on-side2" : {
59+
"uri": "local://models/low-poly-car.gltf",
60+
"position": [20, 20],
61+
"orientation": [-45, 0, 45]
62+
},
63+
"model-on-side3" : {
64+
"uri": "local://models/low-poly-car.gltf",
65+
"position": [0.00000, 20],
66+
"orientation": [0, 45, 45]
67+
}
68+
}
69+
}
70+
},
71+
"layers": [
72+
{
73+
"id": "background",
74+
"type": "background",
75+
"paint": {
76+
"background-color": "white"
77+
}
78+
},
79+
{
80+
"id": "model",
81+
"type": "model",
82+
"source": "model",
83+
"paint": {
84+
"model-scale" : [200000, 200000, 200000]
85+
}
86+
}
87+
]
88+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[
2+
{
3+
"type": "Feature",
4+
"state": {},
5+
"geometry": {
6+
"type": "Point",
7+
"coordinates": [
8+
-122.394457,
9+
37.794784
10+
]
11+
},
12+
"properties": {
13+
"layer": "model",
14+
"uri": "local://models/low-poly-car.gltf",
15+
"orientation": [
16+
0,
17+
0,
18+
0
19+
]
20+
},
21+
"id": "model-1",
22+
"source": "model"
23+
}
24+
]

0 commit comments

Comments
 (0)