Comparing version 0.18.0 to 0.18.1
{ | ||
"name": "tangram", | ||
"version": "0.18.0", | ||
"version": "0.18.1", | ||
"description": "WebGL Maps for Vector Tiles", | ||
@@ -5,0 +5,0 @@ "repository": { |
// Point builders | ||
import { default_uvs } from './common'; | ||
// Build a billboard sprite quad centered on a point. Sprites are intended to be drawn in screenspace, and have | ||
// properties for width, height, angle, and a scale factor that can be used to interpolate the screenspace size | ||
// of a sprite between two zoom levels. | ||
export function buildQuadsForPoints (points, vertex_data, vertex_template, | ||
{ texcoord_index, position_index, shape_index, offset_index, offsets_index, pre_angles_index, angles_index }, | ||
{ quad, quad_normalize, offset, offsets, pre_angles, angle, angles, curve, texcoord_scale, texcoord_normalize, pre_angles_normalize, angles_normalize, offsets_normalize }) { | ||
quad_normalize = quad_normalize || 1; | ||
let w2 = quad[0] / 2 * quad_normalize; | ||
let h2 = quad[1] / 2 * quad_normalize; | ||
let scaling = [ | ||
[-w2, -h2], | ||
[w2, -h2], | ||
[w2, h2], | ||
[-w2, h2] | ||
]; | ||
// Scaling values to encode fractional values with fixed-point integer attributes | ||
const pre_angles_normalize = 128 / Math.PI; | ||
const angles_normalize = 16384 / Math.PI; | ||
const offsets_normalize = 64; | ||
const texcoord_normalize = 65535; | ||
const size_normalize = 128; // width/height are 8.8 fixed-point, but are halved (so multiply by 128 instead of 256) | ||
let vertex_elements = vertex_data.vertex_elements; | ||
let element_offset = vertex_data.vertex_count; | ||
// These index values map a 4-element vertex position counter from this pattern (used for size and UVs): | ||
// [min_x, min_y, max_x, max_y] | ||
// to this pattern: | ||
// [min_x, min_y], | ||
// [max_x, min_y], | ||
// [max_x, max_y], | ||
// [min_x, max_y] | ||
const ix = [0, 2, 2, 0]; | ||
const iy = [1, 1, 3, 3]; | ||
const shape = new Array(4); // single, reusable allocation | ||
let texcoords; | ||
if (texcoord_index) { | ||
texcoord_normalize = texcoord_normalize || 1; | ||
// Build a billboard sprite quad centered on a point. Sprites are intended to be drawn in screenspace, and have | ||
// properties for width, height, angle, and texture UVs. Curved label segment sprites have additional properties | ||
// for interpolating their position and angle across zooms. | ||
export function buildQuadForPoint ( | ||
point, | ||
vertex_data, | ||
vertex_template, | ||
vindex, | ||
size, | ||
offset, | ||
offsets, | ||
pre_angles, | ||
angle, | ||
angles, | ||
texcoords, | ||
curve) { | ||
var [min_u, min_v, max_u, max_v] = texcoord_scale || default_uvs; | ||
// Half-sized point dimensions in fixed point | ||
const w2 = size[0] * size_normalize; | ||
const h2 = size[1] * size_normalize; | ||
shape[0] = -w2; | ||
shape[1] = -h2; | ||
shape[2] = w2; | ||
shape[3] = h2; | ||
texcoords = [ | ||
[min_u, min_v], | ||
[max_u, min_v], | ||
[max_u, max_v], | ||
[min_u, max_v] | ||
]; | ||
} | ||
const uvs = texcoords || default_uvs; | ||
var geom_count = 0; | ||
let num_points = points.length; | ||
for (let p=0; p < num_points; p++) { | ||
let point = points[p]; | ||
const vertex_elements = vertex_data.vertex_elements; | ||
let element_offset = vertex_data.vertex_count; | ||
for (let pos=0; pos < 4; pos++) { | ||
// Add texcoords | ||
if (texcoord_index) { | ||
vertex_template[texcoord_index + 0] = texcoords[pos][0] * texcoord_normalize; | ||
vertex_template[texcoord_index + 1] = texcoords[pos][1] * texcoord_normalize; | ||
} | ||
for (let p=0; p < 4; p++) { | ||
vertex_template[vindex.a_position + 0] = point[0]; | ||
vertex_template[vindex.a_position + 1] = point[1]; | ||
vertex_template[position_index + 0] = point[0]; | ||
vertex_template[position_index + 1] = point[1]; | ||
vertex_template[vindex.a_shape + 0] = shape[ix[p]]; | ||
vertex_template[vindex.a_shape + 1] = shape[iy[p]]; | ||
vertex_template[vindex.a_shape + 2] = angle; | ||
vertex_template[shape_index + 0] = scaling[pos][0]; | ||
vertex_template[shape_index + 1] = scaling[pos][1]; | ||
vertex_template[shape_index + 2] = angle; | ||
vertex_template[vindex.a_offset + 0] = offset[0]; | ||
vertex_template[vindex.a_offset + 1] = offset[1]; | ||
vertex_template[offset_index + 0] = offset[0]; | ||
vertex_template[offset_index + 1] = offset[1]; | ||
// Add texcoords | ||
if (vindex.a_texcoord) { | ||
vertex_template[vindex.a_texcoord + 0] = uvs[ix[p]] * texcoord_normalize; | ||
vertex_template[vindex.a_texcoord + 1] = uvs[iy[p]] * texcoord_normalize; | ||
} | ||
if (curve){ | ||
// 1 byte (signed) range: [-127, 128] | ||
// actual range: [-2pi, 2pi] | ||
// total: multiply by 128 / (2 PI) | ||
vertex_template[pre_angles_index + 0] = pre_angles_normalize * pre_angles[0]; | ||
vertex_template[pre_angles_index + 1] = pre_angles_normalize * pre_angles[1]; | ||
vertex_template[pre_angles_index + 2] = pre_angles_normalize * pre_angles[2]; | ||
vertex_template[pre_angles_index + 3] = pre_angles_normalize * pre_angles[3]; | ||
// Add curved label segment props | ||
if (curve) { | ||
// 1 byte (signed) range: [-127, 128] | ||
// actual range: [-2pi, 2pi] | ||
// total: multiply by 128 / (2 PI) | ||
vertex_template[vindex.a_pre_angles + 0] = pre_angles_normalize * pre_angles[0]; | ||
vertex_template[vindex.a_pre_angles + 1] = pre_angles_normalize * pre_angles[1]; | ||
vertex_template[vindex.a_pre_angles + 2] = pre_angles_normalize * pre_angles[2]; | ||
vertex_template[vindex.a_pre_angles + 3] = pre_angles_normalize * pre_angles[3]; | ||
// 2 byte (signed) of resolution [-32767, 32768] | ||
// actual range: [-2pi, 2pi] | ||
// total: multiply by 32768 / (2 PI) = 16384 / PI | ||
vertex_template[angles_index + 0] = angles_normalize * angles[0]; | ||
vertex_template[angles_index + 1] = angles_normalize * angles[1]; | ||
vertex_template[angles_index + 2] = angles_normalize * angles[2]; | ||
vertex_template[angles_index + 3] = angles_normalize * angles[3]; | ||
// 2 byte (signed) of resolution [-32767, 32768] | ||
// actual range: [-2pi, 2pi] | ||
// total: multiply by 32768 / (2 PI) = 16384 / PI | ||
vertex_template[vindex.a_angles + 0] = angles_normalize * angles[0]; | ||
vertex_template[vindex.a_angles + 1] = angles_normalize * angles[1]; | ||
vertex_template[vindex.a_angles + 2] = angles_normalize * angles[2]; | ||
vertex_template[vindex.a_angles + 3] = angles_normalize * angles[3]; | ||
// offset range can be [0, 65535] | ||
// actual range: [0, 1024] | ||
vertex_template[offsets_index + 0] = offsets_normalize * offsets[0]; | ||
vertex_template[offsets_index + 1] = offsets_normalize * offsets[1]; | ||
vertex_template[offsets_index + 2] = offsets_normalize * offsets[2]; | ||
vertex_template[offsets_index + 3] = offsets_normalize * offsets[3]; | ||
} | ||
vertex_data.addVertex(vertex_template); | ||
// offset range can be [0, 65535] | ||
// actual range: [0, 1024] | ||
vertex_template[vindex.a_offsets + 0] = offsets_normalize * offsets[0]; | ||
vertex_template[vindex.a_offsets + 1] = offsets_normalize * offsets[1]; | ||
vertex_template[vindex.a_offsets + 2] = offsets_normalize * offsets[2]; | ||
vertex_template[vindex.a_offsets + 3] = offsets_normalize * offsets[3]; | ||
} | ||
vertex_elements.push(element_offset + 0); | ||
vertex_elements.push(element_offset + 1); | ||
vertex_elements.push(element_offset + 2); | ||
vertex_elements.push(element_offset + 2); | ||
vertex_elements.push(element_offset + 3); | ||
vertex_elements.push(element_offset + 0); | ||
element_offset += 4; | ||
geom_count += 2; | ||
vertex_data.addVertex(vertex_template); | ||
} | ||
return geom_count; | ||
vertex_elements.push(element_offset + 0); | ||
vertex_elements.push(element_offset + 1); | ||
vertex_elements.push(element_offset + 2); | ||
vertex_elements.push(element_offset + 2); | ||
vertex_elements.push(element_offset + 3); | ||
vertex_elements.push(element_offset + 0); | ||
return 2; // geom count is always two triangles, for one quad | ||
} |
@@ -22,34 +22,26 @@ // Geometry building functions | ||
const DEFAULT = { | ||
MITER_LIMIT: 3, | ||
TEXCOORD_NORMALIZE: 1, | ||
TEXCOORD_RATIO: 1, | ||
MIN_FAN_WIDTH: 5 // Width of line in tile units to place 1 triangle per fan | ||
}; | ||
const DEFAULT_MITER_LIMIT = 3; | ||
const MIN_FAN_WIDTH = 5; // Width of line in tile units to place 1 triangle per fan | ||
const TEXCOORD_NORMALIZE = 65536; // Scaling factor for UV attribute values | ||
// Scaling factor to add precision to line texture V coordinate packed as normalized short | ||
const v_scale_adjust = Geo.tile_scale; | ||
const V_SCALE_ADJUST = Geo.tile_scale; | ||
const zero_v = [0, 0], one_v = [1, 0], mid_v = [0.5, 0]; // reusable instances, updated with V coordinate | ||
export function buildPolylines (lines, width, vertex_data, vertex_template, | ||
{ | ||
closed_polygon, | ||
remove_tile_edges, | ||
tile_edge_tolerance, | ||
texcoord_index, | ||
texcoord_width, | ||
texcoord_ratio, | ||
texcoord_normalize, | ||
extrude_index, | ||
offset_index, | ||
join, cap, | ||
miter_limit, | ||
offset | ||
}) { | ||
var cap_type = cap ? CAP_TYPE[cap] : CAP_TYPE.butt; | ||
var join_type = join ? JOIN_TYPE[join] : JOIN_TYPE.miter; | ||
export function buildPolylines ( | ||
lines, | ||
style, | ||
vertex_data, | ||
vertex_template, | ||
vindex, | ||
closed_polygon, | ||
remove_tile_edges, | ||
tile_edge_tolerance) { | ||
var cap_type = style.cap ? CAP_TYPE[style.cap] : CAP_TYPE.butt; | ||
var join_type = style.join ? JOIN_TYPE[style.join] : JOIN_TYPE.miter; | ||
// Configure miter limit | ||
if (join_type === JOIN_TYPE.miter) { | ||
miter_limit = miter_limit || DEFAULT.MITER_LIMIT; // default miter limit | ||
const miter_limit = style.miter_limit || DEFAULT_MITER_LIMIT; // default miter limit | ||
var miter_len_sq = miter_limit * miter_limit; | ||
@@ -60,6 +52,4 @@ } | ||
var v_scale; | ||
if (texcoord_index) { | ||
texcoord_normalize = texcoord_normalize || DEFAULT.TEXCOORD_NORMALIZE; | ||
texcoord_ratio = texcoord_ratio || DEFAULT.TEXCOORD_RATIO; | ||
v_scale = 1 / (texcoord_width * texcoord_ratio * v_scale_adjust); // scales line texture as a ratio of the line's width | ||
if (vindex.a_texcoord) { | ||
v_scale = 1 / (style.texcoord_width * V_SCALE_ADJUST); // scales line texture as a ratio of the line's width | ||
} | ||
@@ -77,10 +67,9 @@ | ||
vertex_template, | ||
half_width: width / 2, | ||
extrude_index, | ||
offset_index, | ||
half_width: style.width / 2, | ||
extrude_index: vindex.a_extrude, | ||
offset_index: vindex.a_offset, | ||
v_scale, | ||
texcoord_index, | ||
texcoord_width, | ||
texcoord_normalize, | ||
offset, | ||
texcoord_index: vindex.a_texcoord, | ||
texcoord_width: style.texcoord_width, | ||
offset: style.offset, | ||
geom_count: 0 | ||
@@ -90,4 +79,4 @@ }; | ||
// Process lines | ||
for (let index = 0; index < lines.length; index++) { | ||
buildPolyline(lines[index], context); | ||
for (let i = 0; i < lines.length; i++) { | ||
buildPolyline(lines[i], context); | ||
} | ||
@@ -97,4 +86,4 @@ | ||
if (context.extra_lines) { | ||
for (let index = 0; index < context.extra_lines.length; index++) { | ||
buildPolyline(context.extra_lines[index], context); | ||
for (let i = 0; i < context.extra_lines.length; i++) { | ||
buildPolyline(context.extra_lines[i], context); | ||
} | ||
@@ -460,4 +449,4 @@ } | ||
if (context.texcoord_index != null) { | ||
vertex_template[context.texcoord_index + 0] = u * context.texcoord_normalize; | ||
vertex_template[context.texcoord_index + 1] = v * context.texcoord_normalize; | ||
vertex_template[context.texcoord_index + 0] = u * TEXCOORD_NORMALIZE; | ||
vertex_template[context.texcoord_index + 1] = v * TEXCOORD_NORMALIZE; | ||
} | ||
@@ -654,3 +643,3 @@ | ||
var numTriangles = (width > 2 * DEFAULT.MIN_FAN_WIDTH) ? Math.log2(width / DEFAULT.MIN_FAN_WIDTH) : 1; | ||
var numTriangles = (width > 2 * MIN_FAN_WIDTH) ? Math.log2(width / MIN_FAN_WIDTH) : 1; | ||
return Math.ceil(angle / Math.PI * numTriangles); | ||
@@ -657,0 +646,0 @@ } |
@@ -8,6 +8,7 @@ import Label from './label'; | ||
constructor (position, size, layout) { | ||
constructor (position, size, layout, angle = 0) { | ||
super(size, layout); | ||
this.type = 'point'; | ||
this.position = [position[0], position[1]]; | ||
this.angle = angle; | ||
this.parent = this.layout.parent; | ||
@@ -55,12 +56,14 @@ this.update(); | ||
// fudge width value as text may overflow bounding box if it has italic, bold, etc style | ||
if (this.layout.italic){ | ||
if (this.layout.italic) { | ||
width += 5 * this.unit_scale; | ||
} | ||
let p = [ | ||
// make bounding boxes | ||
this.obb = new OBB( | ||
this.position[0] + (this.offset[0] * this.unit_scale), | ||
this.position[1] - (this.offset[1] * this.unit_scale) | ||
]; | ||
this.obb = new OBB(p[0], p[1], 0, width, height); | ||
this.position[1] - (this.offset[1] * this.unit_scale), | ||
-this.angle, // angle is negative because tile system y axis is pointing down | ||
width, | ||
height | ||
); | ||
this.aabb = this.obb.getExtent(); | ||
@@ -67,0 +70,0 @@ |
import PointAnchor from './point_anchor'; | ||
import {boxIntersectsList} from './intersect'; | ||
import Utils from '../utils/utils'; | ||
import OBB from '../utils/obb'; | ||
@@ -16,2 +15,3 @@ import Geo from '../utils/geo'; | ||
this.position = null; | ||
this.angle = 0; | ||
this.anchor = Array.isArray(this.layout.anchor) ? this.layout.anchor[0] : this.layout.anchor; // initial anchor | ||
@@ -34,2 +34,3 @@ this.placed = null; | ||
position: this.position, | ||
angle: this.angle, | ||
size: this.size, | ||
@@ -84,9 +85,6 @@ offset: this.offset, | ||
inTileBounds () { | ||
let min = [ this.aabb[0], this.aabb[1] ]; | ||
let max = [ this.aabb[2], this.aabb[3] ]; | ||
if (!Utils.pointInTile(min) || !Utils.pointInTile(max)) { | ||
if ((this.aabb[0] >= 0 && this.aabb[1] > -Geo.tile_scale && this.aabb[0] < Geo.tile_scale && this.aabb[1] <= 0) || | ||
(this.aabb[2] >= 0 && this.aabb[3] > -Geo.tile_scale && this.aabb[2] < Geo.tile_scale && this.aabb[3] <= 0)) { | ||
return false; | ||
} | ||
return true; | ||
@@ -140,6 +138,7 @@ } | ||
Label.id = 0; | ||
Label.id_prefix = ''; // id prefix scoped to worker thread | ||
Label.id_prefix = 0; // id prefix scoped to worker thread | ||
Label.id_multiplier = 0; // multiplier to keep label ids distinct across threads | ||
Label.nextLabelId = function () { | ||
return Label.id_prefix + '/' + (Label.id++); | ||
return Label.id_prefix + ((Label.id++) * Label.id_multiplier); | ||
}; | ||
@@ -146,0 +145,0 @@ |
@@ -11,6 +11,70 @@ import Label from './label'; | ||
export default function mainThreadLabelCollisionPass (tiles, view_zoom, hide_breach = false) { | ||
export default async function mainThreadLabelCollisionPass (tiles, view_zoom, hide_breach = false) { | ||
// Swap/reset visible label set | ||
prev_visible = visible; // save last visible label set | ||
visible = {}; // initialize new visible label set | ||
// Build label containers from tiles | ||
let containers = buildLabels(tiles, view_zoom); | ||
// Collide all labels in a single group | ||
// TODO: maybe rename tile and style to group/subgroup? | ||
Collision.startTile('main', { apply_repeat_groups: true, return_hidden: true }); | ||
Collision.addStyle('main', 'main'); | ||
const labels = await Collision.collide(containers, 'main', 'main'); | ||
// Update label visiblity | ||
let meshes = []; | ||
labels.forEach(container => { | ||
// Hide breach labels (those that cross tile boundaries) while tiles are loading, unless they | ||
// were previously visible (otherwise fully loaded/collided breach labels will flicker in and out | ||
// when new tiles load, even if they aren't adjacent) | ||
let show = 0; | ||
if (container.show === true && | ||
(!hide_breach || !container.label.breach || prev_visible[container.label.id])) { | ||
show = 1; | ||
} | ||
if (show) { | ||
visible[container.label.id] = true; // track visible labels | ||
} | ||
let changed = true; // check if label visibility changed on this collision pass | ||
container.ranges.forEach(r => { | ||
if (!changed) { | ||
return; // skip rest of label if state hasn't changed | ||
} | ||
let mesh = container.mesh; | ||
if (!mesh.valid) { | ||
return; | ||
} | ||
let off = mesh.vertex_layout.offset.a_shape; // byte offset (within each vertex) of attribute | ||
let stride = mesh.vertex_layout.stride; // byte stride per vertex | ||
for (let i=0; i < r[1]; i++) { | ||
// NB: +6 is because attribute is a short int (2 bytes each), and we're skipping to 3rd element, 6=3*2 | ||
if (mesh.vertex_data[r[0] + i * stride + off + 6] === show) { | ||
changed = false; | ||
return; // label hasn't changed states, skip further updates | ||
} | ||
mesh.vertex_data[r[0] + i * stride + off + 6] = show; | ||
} | ||
if (meshes.indexOf(mesh) === -1) { | ||
meshes.push(mesh); | ||
} | ||
}); | ||
}); | ||
// Upload updated meshes and make them visible | ||
meshes.forEach(mesh => mesh.upload()); | ||
tiles.forEach(t => t.swapPendingLabels()); | ||
return { labels, containers }; // currently returned for debugging | ||
} | ||
function buildLabels (tiles, view_zoom) { | ||
const labels = {}; | ||
@@ -23,3 +87,3 @@ let containers = {}; | ||
const zoom_scale = Math.pow(2, view_zoom - tile.style_z); // adjust label size by view zoom | ||
const size_scale = units_per_meter * zoom_scale; // scale from tile units to zoom-adjusted meters | ||
const size_scale = units_per_meter * zoom_scale; // scale from tile units to zoom-adjusted meters | ||
const meters_per_pixel = Geo.metersPerPixel(view_zoom); | ||
@@ -46,3 +110,3 @@ | ||
const ranges = mesh.labels[label_id].ranges; | ||
const debug = Object.assign({}, mesh.labels[label_id].debug, {tile, params, label_id}); | ||
const debug = Object.assign({}, mesh.labels[label_id].debug, { tile, params, label_id }); | ||
@@ -58,3 +122,2 @@ let label = labels[label_id] = {}; | ||
label.layout.repeat_distance /= size_scale; // TODO: where should this be scaled? | ||
label.position = [ // don't overwrite referenced values | ||
@@ -74,6 +137,6 @@ label.position[0] / units_per_meter + tile.min.x, | ||
// NB: this is a very rough approximation of curved label collision at intermediate zooms, | ||
// becuase the position/scale of each collision box isn't correctly updated; however, | ||
// because the position/scale of each collision box isn't correctly updated; however, | ||
// it's good enough to provide some additional label coverage, with less overhead | ||
const obbs = params.obbs.map(o => { | ||
let {x, y, a, w, h} = o; | ||
let { x, y, a, w, h } = o; | ||
x = x / units_per_meter + tile.min.x; | ||
@@ -113,59 +176,3 @@ y = y / units_per_meter + tile.min.y; | ||
containers = Object.keys(containers).map(k => containers[k]); | ||
// Collide all labels in a single group | ||
// TODO: maybe rename tile and style to group/subgroup? | ||
Collision.startTile('main', { apply_repeat_groups: true, return_hidden: true }); | ||
Collision.addStyle('main', 'main'); | ||
return Collision.collide(containers, 'main', 'main').then(labels => { | ||
let meshes = []; | ||
labels.forEach(container => { | ||
// Hide breach labels (those that cross tile boundaries) while tiles are loading, unless they | ||
// were previously visible (otherwise fully loaded/collided breach labels will flicker in and out | ||
// when new tiles load, even if they aren't adjacent) | ||
let show = 0; | ||
if (container.show === true && | ||
(!hide_breach || !container.label.breach || prev_visible[container.label.id])) { | ||
show = 1; | ||
} | ||
if (show) { | ||
visible[container.label.id] = true; // track visible labels | ||
} | ||
let changed = true; // check if label visibility changed on this collision pass | ||
container.ranges.forEach(r => { | ||
if (!changed) { | ||
return; // skip rest of label if state hasn't changed | ||
} | ||
let mesh = container.mesh; | ||
if (!mesh.valid) { | ||
return; | ||
} | ||
let off = mesh.vertex_layout.offset.a_shape; // byte offset (within each vertex) of attribute | ||
let stride = mesh.vertex_layout.stride; // byte stride per vertex | ||
for (let i=0; i < r[1]; i++) { | ||
// NB: +6 is because attribute is a short int (2 bytes each), and we're skipping to 3rd element, 6=3*2 | ||
if (mesh.vertex_data[r[0] + i * stride + off + 6] === show) { | ||
changed = false; | ||
return; // label hasn't changed states, skip further updates | ||
} | ||
mesh.vertex_data[r[0] + i * stride + off + 6] = show; | ||
} | ||
if (meshes.indexOf(mesh) === -1) { | ||
meshes.push(mesh); | ||
} | ||
}); | ||
}); | ||
meshes.forEach(mesh => mesh.upload()); | ||
tiles.forEach(t => t.swapPendingLabels()); | ||
return { labels, containers }; // currently returned for debugging | ||
}); | ||
return containers; | ||
} | ||
@@ -172,0 +179,0 @@ |
@@ -9,9 +9,9 @@ // Logic for placing point labels along a line geometry | ||
export default function placePointsOnLine (line, size, options) { | ||
let labels = []; | ||
let strategy = options.placement; | ||
let min_length = Math.max(size[0], size[1]) * options.placement_min_length_ratio * options.units_per_pixel; | ||
export default function placePointsOnLine (line, size, layout) { | ||
const labels = []; | ||
const strategy = layout.placement; | ||
const min_length = Math.max(size[0], size[1]) * layout.placement_min_length_ratio * layout.units_per_pixel; | ||
if (strategy === PLACEMENT.SPACED) { | ||
let result = getPositionsAndAngles(line, min_length, options); | ||
let result = getPositionsAndAngles(line, min_length, layout); | ||
// false will be returned if line have no length | ||
@@ -24,9 +24,7 @@ if (!result) { | ||
let angles = result.angles; | ||
for (let i = 0; i < positions.length; i++){ | ||
for (let i = 0; i < positions.length; i++) { | ||
let position = positions[i]; | ||
let angle = angles[i]; | ||
if (options.tile_edges === true || !isCoordOutsideTile(position)) { | ||
let label = new LabelPoint(position, size, options); | ||
label.angle = angle; | ||
labels.push(label); | ||
if (layout.tile_edges === true || !isCoordOutsideTile(position)) { | ||
labels.push(new LabelPoint(position, size, layout, angle)); | ||
} | ||
@@ -36,10 +34,9 @@ } | ||
else if (strategy === PLACEMENT.VERTEX) { | ||
let p, q, label; | ||
for (let i = 0; i < line.length - 1; i++){ | ||
let p, q; | ||
for (let i = 0; i < line.length - 1; i++) { | ||
p = line[i]; | ||
q = line[i + 1]; | ||
if (options.tile_edges === true || !isCoordOutsideTile(p)) { | ||
label = new LabelPoint(p, size, options); | ||
label.angle = getAngle(p, q, options.angle); | ||
labels.push(label); | ||
if (layout.tile_edges === true || !isCoordOutsideTile(p)) { | ||
const angle = getAngle(p, q, layout.angle); | ||
labels.push(new LabelPoint(p, size, layout, angle)); | ||
} | ||
@@ -49,8 +46,7 @@ } | ||
// add last endpoint | ||
label = new LabelPoint(q, size, options); | ||
label.angle = getAngle(p, q, options.angle); | ||
labels.push(label); | ||
const angle = getAngle(p, q, layout.angle); | ||
labels.push(new LabelPoint(q, size, layout, angle)); | ||
} | ||
else if (strategy === PLACEMENT.MIDPOINT) { | ||
for (let i = 0; i < line.length - 1; i++){ | ||
for (let i = 0; i < line.length - 1; i++) { | ||
let p = line[i]; | ||
@@ -62,7 +58,6 @@ let q = line[i + 1]; | ||
]; | ||
if (options.tile_edges === true || !isCoordOutsideTile(position)) { | ||
if (layout.tile_edges === true || !isCoordOutsideTile(position)) { | ||
if (!min_length || norm(p, q) > min_length) { | ||
let label = new LabelPoint(position, size, options); | ||
label.angle = getAngle(p, q, options.angle); | ||
labels.push(label); | ||
const angle = getAngle(p, q, layout.angle); | ||
labels.push(new LabelPoint(position, size, layout, angle)); | ||
} | ||
@@ -75,5 +70,5 @@ } | ||
function getPositionsAndAngles(line, min_length, options){ | ||
let upp = options.units_per_pixel; | ||
let spacing = (options.placement_spacing || default_spacing) * upp; | ||
function getPositionsAndAngles(line, min_length, layout) { | ||
let upp = layout.units_per_pixel; | ||
let spacing = (layout.placement_spacing || default_spacing) * upp; | ||
@@ -91,4 +86,4 @@ let length = getLineLength(line); | ||
let distance = 0.5 * remainder; | ||
for (let i = 0; i < num_labels; i++){ | ||
let {position, angle} = interpolateLine(line, distance, min_length, options); | ||
for (let i = 0; i < num_labels; i++) { | ||
let {position, angle} = interpolateLine(line, distance, min_length, layout); | ||
if (position != null && angle != null) { | ||
@@ -104,9 +99,9 @@ positions.push(position); | ||
function getAngle(p, q, angle = 0){ | ||
function getAngle(p, q, angle = 0) { | ||
return (angle === 'auto') ? Math.atan2(q[0] - p[0], q[1] - p[1]) : angle; | ||
} | ||
function getLineLength(line){ | ||
function getLineLength(line) { | ||
let distance = 0; | ||
for (let i = 0; i < line.length - 1; i++){ | ||
for (let i = 0; i < line.length - 1; i++) { | ||
distance += norm(line[i], line[i+1]); | ||
@@ -117,3 +112,3 @@ } | ||
function norm(p, q){ | ||
function norm(p, q) { | ||
return Math.sqrt(Math.pow(p[0] - q[0], 2) + Math.pow(p[1] - q[1], 2)); | ||
@@ -124,6 +119,6 @@ } | ||
// you don't have to start from the first index every time for placement | ||
function interpolateLine(line, distance, min_length, options){ | ||
function interpolateLine(line, distance, min_length, layout) { | ||
let sum = 0; | ||
let position, angle; | ||
for (let i = 0; i < line.length-1; i++){ | ||
for (let i = 0; i < line.length-1; i++) { | ||
let p = line[i]; | ||
@@ -139,5 +134,5 @@ let q = line[i+1]; | ||
if (sum > distance){ | ||
if (sum > distance) { | ||
position = interpolateSegment(p, q, sum - distance); | ||
angle = getAngle(p, q, options.angle); | ||
angle = getAngle(p, q, layout.angle); | ||
break; | ||
@@ -149,3 +144,3 @@ } | ||
function interpolateSegment(p, q, distance){ | ||
function interpolateSegment(p, q, distance) { | ||
let length = norm(p, q); | ||
@@ -152,0 +147,0 @@ let ratio = distance / length; |
@@ -291,5 +291,5 @@ import ShaderProgram from '../gl/shader_program'; | ||
// Move light's world position into camera space | ||
let [x, y] = Geo.latLngToMeters(this.position); | ||
this.position_eye[0] = x - this.view.camera.position_meters[0]; | ||
this.position_eye[1] = y - this.view.camera.position_meters[1]; | ||
const m = Geo.latLngToMeters([...this.position]); | ||
this.position_eye[0] = m[0] - this.view.camera.position_meters[0]; | ||
this.position_eye[1] = m[1] - this.view.camera.position_meters[1]; | ||
@@ -296,0 +296,0 @@ this.position_eye[2] = StyleParser.convertUnits(this.position[2], |
@@ -42,2 +42,3 @@ /*jshint worker: true*/ | ||
Label.id_prefix = worker_id; | ||
Label.id_multiplier = num_workers; | ||
return worker_id; | ||
@@ -266,3 +267,5 @@ }, | ||
let features = []; | ||
let tiles = tile_keys.map(t => this.tiles[t]).filter(t => t); | ||
let tiles = tile_keys | ||
.map(t => this.tiles[t]) | ||
.filter(t => t && t.loaded); | ||
@@ -269,0 +272,0 @@ // Compile feature filter |
@@ -1336,2 +1336,3 @@ import log from '../utils/log'; | ||
if ((this.render_count_changed || this.generation !== this.last_complete_generation) && | ||
!this.building && | ||
!this.tile_manager.isLoadingVisibleTiles() && | ||
@@ -1338,0 +1339,0 @@ this.tile_manager.allVisibleTilesLabeled()) { |
@@ -201,4 +201,4 @@ import Geo from '../utils/geo'; | ||
// Center of viewport in meters, and tile | ||
let [x, y] = Geo.latLngToMeters([this.center.lng, this.center.lat]); | ||
this.center.meters = { x, y }; | ||
const m = Geo.latLngToMeters([this.center.lng, this.center.lat]); | ||
this.center.meters = { x: m[0], y: m[1] }; | ||
@@ -205,0 +205,0 @@ this.center.tile = Geo.tileForMeters([this.center.meters.x, this.center.meters.y], this.tile_zoom); |
@@ -117,7 +117,3 @@ /*jshint worker: true */ | ||
var feature = source.layers[t].features[f]; | ||
Geo.transformGeometry(feature.geometry, coord => { | ||
var [x, y] = Geo.latLngToMeters(coord); | ||
coord[0] = x; | ||
coord[1] = y; | ||
}); | ||
Geo.transformGeometry(feature.geometry, this.projectCoord); | ||
} | ||
@@ -131,2 +127,6 @@ } | ||
static projectCoord (coord) { | ||
Geo.latLngToMeters(coord); | ||
} | ||
/** | ||
@@ -422,3 +422,3 @@ Re-scale geometries within each source to internal tile units | ||
if (!min) { | ||
min = bounds.tiles.min[coords.z] = Geo.wrapTile(Geo.tileForMeters(bounds.meters.min, coords.z)); | ||
min = bounds.tiles.min[coords.z] = Geo.tileForMeters(bounds.meters.min, coords.z); | ||
} | ||
@@ -428,3 +428,3 @@ | ||
if (!max) { | ||
max = bounds.tiles.max[coords.z] = Geo.wrapTile(Geo.tileForMeters(bounds.meters.max, coords.z)); | ||
max = bounds.tiles.max[coords.z] = Geo.tileForMeters(bounds.meters.max, coords.z); | ||
} | ||
@@ -431,0 +431,0 @@ |
@@ -570,19 +570,9 @@ // Line rendering style | ||
lines, | ||
style.width, | ||
style, | ||
vertex_data, | ||
vertex_template, | ||
{ | ||
cap: style.cap, | ||
join: style.join, | ||
miter_limit: style.miter_limit, | ||
extrude_index: vertex_layout.index.a_extrude, | ||
offset_index: vertex_layout.index.a_offset, | ||
texcoord_index: vertex_layout.index.a_texcoord, | ||
texcoord_width: style.texcoord_width, | ||
texcoord_normalize: 65535, // scale UVs to unsigned shorts | ||
closed_polygon: options && options.closed_polygon, | ||
remove_tile_edges: !style.tile_edges && options && options.remove_tile_edges, | ||
tile_edge_tolerance: Geo.tile_scale * context.tile.pad_scale * 2, | ||
offset: style.offset | ||
} | ||
vertex_layout.index, | ||
(options && options.closed_polygon), // closed_polygon | ||
(!style.tile_edges && options && options.remove_tile_edges), // remove_tile_edges | ||
(Geo.tile_scale * context.tile.pad_scale * 2) // tile_edge_tolerance | ||
); | ||
@@ -589,0 +579,0 @@ }, |
@@ -8,6 +8,5 @@ // Point + text label rendering style | ||
import VertexLayout from '../../gl/vertex_layout'; | ||
import {buildQuadsForPoints} from '../../builders/points'; | ||
import { buildQuadForPoint } from '../../builders/points'; | ||
import Texture from '../../gl/texture'; | ||
import Geo from '../../utils/geo'; | ||
import Vector from '../../utils/vector'; | ||
import Collision from '../../labels/collision'; | ||
@@ -25,7 +24,2 @@ import LabelPoint from '../../labels/label_point'; | ||
const pre_angles_normalize = 128 / Math.PI; | ||
const angles_normalize = 16384 / Math.PI; | ||
const offsets_normalize = 64; | ||
const texcoord_normalize = 65535; | ||
export const Points = Object.create(Style); | ||
@@ -380,3 +374,2 @@ | ||
style.size = text_info.size.logical_size; | ||
style.angle = 0; // text attached to point is always upright | ||
style.texcoords = text_info.align[q.label.align].texcoords; | ||
@@ -543,7 +536,7 @@ style.label_texture = textures[text_info.align[q.label.align].texture_id]; | ||
// Builds one or more point labels for a geometry | ||
buildLabels (size, geometry, options) { | ||
buildLabels (size, geometry, layout) { | ||
let labels = []; | ||
if (geometry.type === 'Point') { | ||
labels.push(new LabelPoint(geometry.coordinates, size, options)); | ||
labels.push(new LabelPoint(geometry.coordinates, size, layout, layout.angle)); | ||
} | ||
@@ -554,3 +547,3 @@ else if (geometry.type === 'MultiPoint') { | ||
let point = points[i]; | ||
labels.push(new LabelPoint(point, size, options)); | ||
labels.push(new LabelPoint(point, size, layout, layout.angle)); | ||
} | ||
@@ -560,3 +553,3 @@ } | ||
let line = geometry.coordinates; | ||
let point_labels = placePointsOnLine(line, size, options); | ||
let point_labels = placePointsOnLine(line, size, layout); | ||
for (let i = 0; i < point_labels.length; ++i) { | ||
@@ -570,3 +563,3 @@ labels.push(point_labels[i]); | ||
let line = lines[ln]; | ||
let point_labels = placePointsOnLine(line, size, options); | ||
let point_labels = placePointsOnLine(line, size, layout); | ||
for (let i = 0; i < point_labels.length; ++i) { | ||
@@ -579,6 +572,6 @@ labels.push(point_labels[i]); | ||
// Point at polygon centroid (of outer ring) | ||
if (options.placement === PLACEMENT.CENTROID) { | ||
if (layout.placement === PLACEMENT.CENTROID) { | ||
let centroid = Geo.centroid(geometry.coordinates); | ||
if (centroid) { // skip degenerate polygons | ||
labels.push(new LabelPoint(centroid, size, options)); | ||
labels.push(new LabelPoint(centroid, size, layout, layout.angle)); | ||
} | ||
@@ -590,3 +583,3 @@ } | ||
for (let ln = 0; ln < rings.length; ln++) { | ||
let point_labels = placePointsOnLine(rings[ln], size, options); | ||
let point_labels = placePointsOnLine(rings[ln], size, layout); | ||
for (let i = 0; i < point_labels.length; ++i) { | ||
@@ -599,6 +592,6 @@ labels.push(point_labels[i]); | ||
else if (geometry.type === 'MultiPolygon') { | ||
if (options.placement === PLACEMENT.CENTROID) { | ||
if (layout.placement === PLACEMENT.CENTROID) { | ||
let centroid = Geo.multiCentroid(geometry.coordinates); | ||
if (centroid) { // skip degenerate polygons | ||
labels.push(new LabelPoint(centroid, size, options)); | ||
labels.push(new LabelPoint(centroid, size, layout, layout.angle)); | ||
} | ||
@@ -611,3 +604,3 @@ } | ||
for (let ln = 0; ln < rings.length; ln++) { | ||
let point_labels = placePointsOnLine(rings[ln], size, options); | ||
let point_labels = placePointsOnLine(rings[ln], size, layout); | ||
for (let i = 0; i < point_labels.length; ++i) { | ||
@@ -629,38 +622,61 @@ labels.push(point_labels[i]); | ||
makeVertexTemplate(style, mesh) { | ||
let color = style.color || StyleParser.defaults.color; | ||
let vertex_layout = mesh.vertex_data.vertex_layout; | ||
let i = 0; | ||
// position - x & y coords will be filled in per-vertex below | ||
this.fillVertexTemplate(vertex_layout, 'a_position', 0, { size: 2 }); | ||
this.fillVertexTemplate(vertex_layout, 'a_position', style.z || 0, { size: 1, offset: 2 }); | ||
// layer order - w coord of 'position' attribute (for packing efficiency) | ||
this.fillVertexTemplate(vertex_layout, 'a_position', this.scaleOrder(style.order), { size: 1, offset: 3 }); | ||
// a_position.xyz - vertex position | ||
// a_position.w - layer order | ||
this.vertex_template[i++] = 0; | ||
this.vertex_template[i++] = 0; | ||
this.vertex_template[i++] = style.z || 0; | ||
this.vertex_template[i++] = this.scaleOrder(style.order); | ||
// scaling vector - (x, y) components per pixel, z = angle, w = show/hide | ||
this.fillVertexTemplate(vertex_layout, 'a_shape', 0, { size: 4 }); | ||
this.fillVertexTemplate(vertex_layout, 'a_shape', style.label.layout.collide ? 0 : 1, { size: 1, offset: 3 }); // set initial label hide/show state | ||
// a_shape.xy - size of point in pixels (scaling vector) | ||
// a_shape.z - angle of point | ||
// a_shape.w - show/hide flag | ||
this.vertex_template[i++] = 0; | ||
this.vertex_template[i++] = 0; | ||
this.vertex_template[i++] = 0; | ||
this.vertex_template[i++] = style.label.layout.collide ? 0 : 1; // set initial label hide/show state | ||
// texture coords | ||
this.fillVertexTemplate(vertex_layout, 'a_texcoord', 0, { size: 2 }); | ||
// a_texcoord.xy - texture coords | ||
if (!mesh.variant.shader_point) { | ||
this.vertex_template[i++] = 0; | ||
this.vertex_template[i++] = 0; | ||
} | ||
// offsets | ||
this.fillVertexTemplate(vertex_layout, 'a_offset', 0, { size: 2 }); | ||
// a_offset.xy - offset of point from center, in pixels | ||
this.vertex_template[i++] = 0; | ||
this.vertex_template[i++] = 0; | ||
// color | ||
this.fillVertexTemplate(vertex_layout, 'a_color', Vector.mult(color, 255), { size: 4 }); | ||
// a_color.rgba - feature color | ||
const color = style.color || StyleParser.defaults.color; | ||
this.vertex_template[i++] = color[0] * 255; | ||
this.vertex_template[i++] = color[1] * 255; | ||
this.vertex_template[i++] = color[2] * 255; | ||
this.vertex_template[i++] = color[3] * 255; | ||
// outline (can be static or dynamic depending on style) | ||
if (this.defines.TANGRAM_HAS_SHADER_POINTS && mesh.variant.shader_point) { | ||
let outline_color = style.outline_color || StyleParser.defaults.outline.color; | ||
this.fillVertexTemplate(vertex_layout, 'a_outline_color', Vector.mult(outline_color, 255), { size: 4 }); | ||
this.fillVertexTemplate(vertex_layout, 'a_outline_edge', style.outline_edge_pct || StyleParser.defaults.outline.width, { size: 1 }); | ||
// a_selection_color.rgba - selection color | ||
if (mesh.variant.selection) { | ||
this.vertex_template[i++] = style.selection_color[0] * 255; | ||
this.vertex_template[i++] = style.selection_color[1] * 255; | ||
this.vertex_template[i++] = style.selection_color[2] * 255; | ||
this.vertex_template[i++] = style.selection_color[3] * 255; | ||
} | ||
// selection color | ||
this.fillVertexTemplate(vertex_layout, 'a_selection_color', Vector.mult(style.selection_color, 255), { size: 4 }); | ||
// point outline | ||
if (mesh.variant.shader_point) { | ||
// a_outline_color.rgba - outline color | ||
const outline_color = style.outline_color || StyleParser.defaults.outline.color; | ||
this.vertex_template[i++] = outline_color[0] * 255; | ||
this.vertex_template[i++] = outline_color[1] * 255; | ||
this.vertex_template[i++] = outline_color[2] * 255; | ||
this.vertex_template[i++] = outline_color[3] * 255; | ||
// a_outline_edge - point outline edge (as % of point size where outline begins) | ||
this.vertex_template[i++] = style.outline_edge_pct || StyleParser.defaults.outline.width; | ||
} | ||
return this.vertex_template; | ||
}, | ||
buildQuad(points, size, angle, angles, pre_angles, offset, offsets, texcoord_scale, curve, vertex_data, vertex_template) { | ||
buildQuad(point, size, angle, angles, pre_angles, offset, offsets, texcoords, curve, vertex_data, vertex_template) { | ||
if (size[0] <= 0 || size[1] <= 0) { | ||
@@ -670,30 +686,15 @@ return 0; // size must be positive | ||
return buildQuadsForPoints( | ||
points, | ||
return buildQuadForPoint( | ||
point, | ||
vertex_data, | ||
vertex_template, | ||
{ | ||
texcoord_index: vertex_data.vertex_layout.index.a_texcoord, | ||
position_index: vertex_data.vertex_layout.index.a_position, | ||
shape_index: vertex_data.vertex_layout.index.a_shape, | ||
offset_index: vertex_data.vertex_layout.index.a_offset, | ||
offsets_index: vertex_data.vertex_layout.index.a_offsets, | ||
pre_angles_index: vertex_data.vertex_layout.index.a_pre_angles, | ||
angles_index: vertex_data.vertex_layout.index.a_angles | ||
}, | ||
{ | ||
quad: size, | ||
quad_normalize: 256, // values have an 8-bit fraction | ||
offset, | ||
offsets, | ||
pre_angles: pre_angles, | ||
angle: angle * 4096, // values have a 12-bit fraction | ||
angles: angles, | ||
curve, | ||
texcoord_scale, | ||
texcoord_normalize, | ||
pre_angles_normalize, | ||
angles_normalize, | ||
offsets_normalize | ||
} | ||
vertex_data.vertex_layout.index, | ||
size, | ||
offset, | ||
offsets, | ||
pre_angles, | ||
angle * 4096, // angle values have a 12-bit fraction | ||
angles, | ||
texcoords, | ||
curve | ||
); | ||
@@ -716,3 +717,2 @@ }, | ||
let vertex_template = this.makeVertexTemplate(style, mesh); | ||
let angle = label.angle || style.angle; | ||
@@ -752,5 +752,5 @@ let size, texcoords; | ||
let geom_count = this.buildQuad( | ||
[label.position], // position | ||
label.position, // position | ||
size, // size in pixels | ||
angle, // angle in radians | ||
label.angle, // angle in radians | ||
null, // placeholder for multiple angles | ||
@@ -774,3 +774,2 @@ null, // placeholder for multiple pre_angles | ||
let mesh, vertex_template; | ||
let angle = label.angle; | ||
let geom_count = 0; | ||
@@ -805,5 +804,5 @@ | ||
let seg_count = this.buildQuad( | ||
[position], // position | ||
position, // position | ||
size, // size in pixels | ||
angle, // angle in degrees | ||
label.angle, // angle in degrees | ||
angles, // angles per segment | ||
@@ -848,5 +847,5 @@ pre_angles, // pre_angle array (rotation applied before offseting) | ||
let seg_count = this.buildQuad( | ||
[position], // position | ||
position, // position | ||
size, // size in pixels | ||
angle, // angle in degrees | ||
label.angle, // angle in degrees | ||
angles, // angles per segment | ||
@@ -925,8 +924,8 @@ pre_angles, // pre_angle array (rotation applied before offseting) | ||
{ name: 'a_shape', size: 4, type: gl.SHORT, normalized: false }, | ||
{ name: 'a_texcoord', size: 2, type: gl.UNSIGNED_SHORT, normalized: true }, | ||
{ name: 'a_texcoord', size: 2, type: gl.UNSIGNED_SHORT, normalized: true, static: (variant.shader_point ? [0, 0] : null) }, | ||
{ name: 'a_offset', size: 2, type: gl.SHORT, normalized: false }, | ||
{ name: 'a_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true }, | ||
{ name: 'a_selection_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true, static: (variant.selection ? null : [0, 0, 0, 0]) }, | ||
{ name: 'a_outline_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true, static: (variant.shader_point ? null : [0, 0, 0, 0]) }, | ||
{ name: 'a_outline_edge', size: 1, type: gl.FLOAT, normalized: false, static: (variant.shader_point ? null : 0) }, | ||
{ name: 'a_selection_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true }, | ||
{ name: 'a_outline_edge', size: 1, type: gl.FLOAT, normalized: false, static: (variant.shader_point ? null : 0) } | ||
]; | ||
@@ -947,2 +946,3 @@ | ||
key, | ||
selection: 1, // TODO: make this vary by draw params | ||
shader_point: (texture === SHADER_POINT_VARIANT), // is shader point | ||
@@ -949,0 +949,0 @@ blend_order: draw.blend_order, |
@@ -113,17 +113,2 @@ // Rendering styles | ||
fillVertexTemplate(vertex_layout, attribute, value, { size, offset }) { | ||
offset = (offset === undefined) ? 0 : offset; | ||
let index = vertex_layout.index[attribute]; | ||
if (index === undefined) { | ||
log('warn', `Style: in style '${this.name}', no index found in vertex layout for attribute '${attribute}'`); | ||
return; | ||
} | ||
for (let i = 0; i < size; ++i) { | ||
let v = value.length > i ? value[i] : value; | ||
this.vertex_template[index + i + offset] = v; | ||
} | ||
}, | ||
/*** Style parsing and geometry construction ***/ | ||
@@ -130,0 +115,0 @@ |
@@ -14,2 +14,4 @@ // Text rendering style | ||
TextStyle.vertex_layouts = {}; // vertex layouts by variant key | ||
Object.assign(TextStyle, { | ||
@@ -42,6 +44,10 @@ name: 'text', | ||
let vertex_layout = mesh.vertex_data.vertex_layout; | ||
let i = vertex_layout.index.a_pre_angles; | ||
this.fillVertexTemplate(vertex_layout, 'a_pre_angles', 0, { size: 4 }); | ||
this.fillVertexTemplate(vertex_layout, 'a_offsets', 0, { size: 4 }); | ||
this.fillVertexTemplate(vertex_layout, 'a_angles', 0, { size: 4 }); | ||
// a_pre_angles.xyzw - rotation of entire curved label | ||
// a_angles.xyzw - angle of each curved label segment | ||
// a_offsets.xyzw - offset of each curved label segment | ||
for (let j=0; j < 12; j++) { | ||
this.vertex_template[i++] = 0; | ||
} | ||
@@ -262,4 +268,5 @@ return this.vertex_template; | ||
// Create or return vertex layout | ||
vertexLayoutForMeshVariant () { | ||
if (this.vertex_layout == null) { | ||
vertexLayoutForMeshVariant(variant) { | ||
// Vertex layout only depends on shader point flag, so using it as layout key to avoid duplicate layouts | ||
if (TextStyle.vertex_layouts[variant.shader_point] == null) { | ||
// TODO: could make selection, offset, and curved label attribs optional, but may not be worth it | ||
@@ -273,11 +280,11 @@ // since text points generally don't consume much memory anyway | ||
{ name: 'a_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true }, | ||
{ name: 'a_selection_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true, static: (variant.selection ? null : [0, 0, 0, 0]) }, | ||
{ name: 'a_pre_angles', size: 4, type: gl.BYTE, normalized: false }, | ||
{ name: 'a_angles', size: 4, type: gl.SHORT, normalized: false }, | ||
{ name: 'a_offsets', size: 4, type: gl.UNSIGNED_SHORT, normalized: false }, | ||
{ name: 'a_pre_angles', size: 4, type: gl.BYTE, normalized: false }, | ||
{ name: 'a_selection_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true }, | ||
]; | ||
this.vertex_layout = new VertexLayout(attribs); | ||
TextStyle.vertex_layouts[variant.shader_point] = new VertexLayout(attribs); | ||
} | ||
return this.vertex_layout; | ||
return TextStyle.vertex_layouts[variant.shader_point]; | ||
}, | ||
@@ -284,0 +291,0 @@ }); |
@@ -174,8 +174,13 @@ import Tile from './tile'; | ||
type: 'tileManagerUpdateLabels', | ||
run: (task) => { | ||
return mainThreadLabelCollisionPass(tiles, this.collision.zoom, this.isLoadingVisibleTiles()).then(results => { | ||
this.collision.task = null; | ||
Task.finish(task, results); | ||
this.updateTileStates().then(() => this.scene.immediateRedraw()); | ||
}); | ||
run: async task => { | ||
// Do collision pass, then update view | ||
const results = await mainThreadLabelCollisionPass(tiles, this.collision.zoom, this.isLoadingVisibleTiles()); | ||
this.scene.requestRedraw(); | ||
// Clear state to allow another collision pass to start | ||
this.collision.task = null; | ||
Task.finish(task, results); | ||
// Check if tiles changed during previous collision pass - will start new pass if so | ||
this.updateTileStates(); | ||
} | ||
@@ -182,0 +187,0 @@ }; |
@@ -165,3 +165,3 @@ import log from '../utils/log'; | ||
tile.debug.rendering = +new Date(); | ||
tile.debug.building = +new Date(); | ||
tile.debug.feature_count = 0; | ||
@@ -189,3 +189,3 @@ tile.debug.layers = null; | ||
// Render features in layer | ||
// Build features in layer | ||
for (let s=0; s < source_layers.length; s++) { | ||
@@ -215,3 +215,3 @@ let source_layer = source_layers[s]; | ||
// Render draw groups | ||
// Build draw groups | ||
for (let group_name in draw_groups) { | ||
@@ -243,9 +243,9 @@ let group = draw_groups[group_name]; | ||
} | ||
tile.debug.rendering = +new Date() - tile.debug.rendering; | ||
tile.debug.building = +new Date() - tile.debug.building; | ||
// Send styles back to main thread as they finish building, in two groups: collision vs. non-collision | ||
let tile_styles = this.stylesForTile(tile, styles).map(s => styles[s]); | ||
Tile.sendStyleGroups(tile, tile_styles, { scene_id }, style => style.collision ? 'collision' : 'non-collision'); | ||
// Tile.sendStyleGroups(tile, tile_styles, { scene_id }, style => style.name); // call for each style | ||
// Tile.sendStyleGroups(tile, tile_styles, { scene_id }, style => 'styles'); // all styles in single call (previous behavior) | ||
Tile.buildStyleGroups(tile, tile_styles, scene_id, style => style.collision ? 'collision' : 'non-collision'); | ||
// Tile.buildStyleGroups(tile, tile_styles, scene_id, style => style.name); // call for each style | ||
// Tile.buildStyleGroups(tile, tile_styles, scene_id, style => 'styles'); // all styles in single call (previous behavior) | ||
} | ||
@@ -263,63 +263,61 @@ | ||
// Send groups of styles back to main thread, asynchronously (as they finish building), | ||
// grouped by the provided function | ||
static sendStyleGroups(tile, styles, { scene_id }, group_by) { | ||
// Group styles | ||
let groups = {}; | ||
styles.forEach(s => { | ||
let group_name = group_by(s); | ||
groups[group_name] = groups[group_name] || []; | ||
groups[group_name].push(s); | ||
}); | ||
// Build styles (grouped by the provided function) and send back to main thread as they finish building | ||
static buildStyleGroups(tile, styles, scene_id, group_by) { | ||
// Group the styles; each group will be sent to the main thread when the styles in the group finish building. | ||
const groups = styles.reduce((groups, style) => { | ||
const group = group_by(style); | ||
groups[group] = groups[group] || []; | ||
groups[group].push(style); | ||
return groups; | ||
}, {}); | ||
if (Object.keys(groups).length > 0) { | ||
let progress = { start: true }; | ||
tile.mesh_data = {}; | ||
// If nothing to build, return empty tile to main thread | ||
if (Object.keys(groups).length === 0) { | ||
WorkerBroker.postMessage( | ||
`TileManager_${scene_id}.buildTileStylesCompleted`, | ||
WorkerBroker.withTransferables({ tile: Tile.slice(tile), progress: { start: true, done: true } }) | ||
); | ||
Collision.resetTile(tile.id); // clear collision if we're done with the tile | ||
return; | ||
} | ||
for (let group_name in groups) { | ||
let group = groups[group_name]; | ||
// Build each group of styles | ||
const progress = {}; | ||
for (const group_name in groups) { | ||
Tile.buildStyleGroup({ group_name, groups, tile, progress, scene_id }); | ||
} | ||
} | ||
Promise.all( | ||
group.map(style => { | ||
return style.endData(tile) | ||
.then(style_data => { | ||
if (style_data) { | ||
tile.mesh_data[style.name] = style_data; | ||
} | ||
}); | ||
})) | ||
.then(() => { | ||
log('trace', `Finished style group '${group_name}' for tile ${tile.key}`); | ||
// Build a single group of styles | ||
static async buildStyleGroup({ group_name, groups, tile, progress, scene_id }) { | ||
const group = groups[group_name]; | ||
const mesh_data = {}; | ||
try { | ||
// For each group, build all styles in the group | ||
await Promise.all(group.map(async (style) => { | ||
const style_data = await style.endData(tile); | ||
if (style_data) { | ||
mesh_data[style.name] = style_data; | ||
} | ||
})); | ||
// Clear group and check if all groups finished | ||
groups[group_name] = []; | ||
if (Object.keys(groups).every(g => groups[g].length === 0)) { | ||
progress.done = true; | ||
} | ||
// Mark the group as done, and check if all groups have finished | ||
log('trace', `Finished style group '${group_name}' for tile ${tile.key}`); | ||
groups[group_name] = null; | ||
if (Object.keys(groups).every(g => groups[g] == null)) { | ||
progress.done = true; | ||
} | ||
// Send meshes to main thread | ||
WorkerBroker.postMessage( | ||
`TileManager_${scene_id}.buildTileStylesCompleted`, | ||
WorkerBroker.withTransferables({ tile: Tile.slice(tile, ['mesh_data']), progress }) | ||
); | ||
progress.start = null; | ||
tile.mesh_data = {}; // reset so each group sends separate set of style meshes | ||
if (progress.done) { | ||
Collision.resetTile(tile.id); // clear collision if we're done with the tile | ||
} | ||
}) | ||
.catch((e) => { | ||
log('error', `Error for style group '${group_name}' for tile ${tile.key}`, (e && e.stack) || e); | ||
}); | ||
} | ||
} | ||
else { | ||
// Nothing to build, return empty tile to main thread | ||
// Send meshes to main thread | ||
WorkerBroker.postMessage( | ||
`TileManager_${scene_id}.buildTileStylesCompleted`, | ||
WorkerBroker.withTransferables({ tile: Tile.slice(tile), progress: { start: true, done: true } }) | ||
WorkerBroker.withTransferables({ tile: { ...Tile.slice(tile), mesh_data }, progress }) | ||
); | ||
Collision.resetTile(tile.id); // clear collision if we're done with the tile | ||
if (progress.done) { | ||
Collision.resetTile(tile.id); // clear collision if we're done with the tile | ||
} | ||
} | ||
catch (e) { | ||
log('error', `Error for style group '${group_name}' for tile ${tile.key}`, (e && e.stack) || e); | ||
} | ||
} | ||
@@ -326,0 +324,0 @@ |
@@ -71,30 +71,28 @@ // Miscellaneous geo functions | ||
/** | ||
Convert mercator meters to lat-lng | ||
Convert mercator meters to lat-lng, in-place | ||
*/ | ||
Geo.metersToLatLng = function ([x, y]) { | ||
Geo.metersToLatLng = function (c) { | ||
c[0] /= Geo.half_circumference_meters; | ||
c[1] /= Geo.half_circumference_meters; | ||
x /= Geo.half_circumference_meters; | ||
y /= Geo.half_circumference_meters; | ||
c[1] = (2 * Math.atan(Math.exp(c[1] * Math.PI)) - (Math.PI / 2)) / Math.PI; | ||
y = (2 * Math.atan(Math.exp(y * Math.PI)) - (Math.PI / 2)) / Math.PI; | ||
c[0] *= 180; | ||
c[1] *= 180; | ||
x *= 180; | ||
y *= 180; | ||
return [x, y]; | ||
return c; | ||
}; | ||
/** | ||
Convert lat-lng to mercator meters | ||
Convert lat-lng to mercator meters, in-place | ||
*/ | ||
Geo.latLngToMeters = function([x, y]) { | ||
Geo.latLngToMeters = function (c) { | ||
// Latitude | ||
y = Math.log(Math.tan(y*Math.PI/360 + Math.PI/4)) / Math.PI; | ||
y *= Geo.half_circumference_meters; | ||
c[1] = Math.log(Math.tan(c[1] * Math.PI / 360 + Math.PI / 4)) / Math.PI; | ||
c[1] *= Geo.half_circumference_meters; | ||
// Longitude | ||
x *= Geo.half_circumference_meters / 180; | ||
c[0] *= Geo.half_circumference_meters / 180; | ||
return [x, y]; | ||
return c; | ||
}; | ||
@@ -108,6 +106,3 @@ | ||
coord[1] = (coord[1] / units_per_meter) + min.y; | ||
let [x, y] = Geo.metersToLatLng(coord); | ||
coord[0] = x; | ||
coord[1] = y; | ||
Geo.metersToLatLng(coord); | ||
}); | ||
@@ -114,0 +109,0 @@ return geometry; |
import Vector from './vector'; | ||
// single-allocation, reusable objects | ||
const ZERO_AXES = [[1, 0], [0, 1]]; | ||
const proj_a = [], proj_b = []; | ||
let d0, d1, d2, d3; | ||
export default class OBB { | ||
constructor (x, y, a, w, h) { | ||
this.dimension = [w, h]; | ||
this.dimension = [w / 2, h / 2]; // store half-dimension as that's what's needed in calculations below | ||
this.angle = a; | ||
this.centroid = [x, y]; | ||
this.quad = []; | ||
this.axes = []; | ||
this.quad = null; | ||
this.axis_0 = null; | ||
this.axis_1 = null; | ||
@@ -26,59 +32,92 @@ this.update(); | ||
getExtent () { | ||
let aabb = [Infinity, Infinity, -Infinity, -Infinity]; | ||
for (let i = 0; i < 4; ++i) { | ||
aabb[0] = Math.min(this.quad[i][0], aabb[0]); | ||
aabb[1] = Math.min(this.quad[i][1], aabb[1]); | ||
aabb[2] = Math.max(this.quad[i][0], aabb[2]); | ||
aabb[3] = Math.max(this.quad[i][1], aabb[3]); | ||
// special handling to skip calculations for 0-angle | ||
if (this.angle === 0) { | ||
return [ | ||
this.quad[0], this.quad[1], // lower-left | ||
this.quad[4], this.quad[5] // upper-right | ||
]; | ||
} | ||
let aabb = [ | ||
Math.min(this.quad[0], this.quad[2], this.quad[4], this.quad[6]), // min x | ||
Math.min(this.quad[1], this.quad[3], this.quad[5], this.quad[7]), // min y | ||
Math.max(this.quad[0], this.quad[2], this.quad[4], this.quad[6]), // max x | ||
Math.max(this.quad[1], this.quad[3], this.quad[5], this.quad[7]) // max y | ||
]; | ||
return aabb; | ||
} | ||
perpAxes () { | ||
this.axes[0] = Vector.normalize(Vector.sub(this.quad[2], this.quad[3])); | ||
this.axes[1] = Vector.normalize(Vector.sub(this.quad[2], this.quad[1])); | ||
updateAxes () { | ||
// upper-left to upper-right | ||
this.axis_0 = Vector.normalize([this.quad[4] - this.quad[6], this.quad[5] - this.quad[7]]); | ||
// lower-right to upper-right | ||
this.axis_1 = Vector.normalize([this.quad[4] - this.quad[2], this.quad[5] - this.quad[3]]); | ||
} | ||
update () { | ||
let x = [ Math.cos(this.angle), Math.sin(this.angle)]; | ||
let y = [-Math.sin(this.angle), Math.cos(this.angle)]; | ||
const c = this.centroid; | ||
const w2 = this.dimension[0]; | ||
const h2 = this.dimension[1]; | ||
x = Vector.mult(x, this.dimension[0] / 2.0); | ||
y = Vector.mult(y, this.dimension[1] / 2.0); | ||
// special handling to skip calculations for 0-angle | ||
if (this.angle === 0) { | ||
// quad is a flat array storing 4 [x, y] vectors | ||
this.quad = [ | ||
c[0] - w2, c[1] - h2, // lower-left | ||
c[0] + w2, c[1] - h2, // lower-right | ||
c[0] + w2, c[1] + h2, // upper-right | ||
c[0] - w2, c[1] + h2 // upper-left | ||
]; | ||
this.quad[0] = Vector.sub(Vector.sub(this.centroid, x), y); // lower-left | ||
this.quad[1] = Vector.sub(Vector.add(this.centroid, x), y); // lower-right | ||
this.quad[2] = Vector.add(Vector.add(this.centroid, x), y); // uper-right | ||
this.quad[3] = Vector.add(Vector.sub(this.centroid, x), y); // uper-left | ||
this.axis_0 = ZERO_AXES[0]; | ||
this.axis_1 = ZERO_AXES[1]; | ||
} | ||
// calculate axes and enclosing quad | ||
else { | ||
let x0 = Math.cos(this.angle) * w2; | ||
let x1 = Math.sin(this.angle) * w2; | ||
this.perpAxes(); | ||
} | ||
let y0 = -Math.sin(this.angle) * h2; | ||
let y1 = Math.cos(this.angle) * h2; | ||
static projectToAxis (obb, axis) { | ||
let min = Infinity; | ||
let max = -Infinity; | ||
// quad is a flat array storing 4 [x, y] vectors | ||
this.quad = [ | ||
c[0] - x0 - y0, c[1] - x1 - y1, // lower-left | ||
c[0] + x0 - y0, c[1] + x1 - y1, // lower-right | ||
c[0] + x0 + y0, c[1] + x1 + y1, // upper-right | ||
c[0] - x0 + y0, c[1] - x1 + y1 // upper-left | ||
]; | ||
let quad = obb.quad; | ||
this.updateAxes(); | ||
} | ||
} | ||
static projectToAxis (obb, axis, proj) { | ||
// for each axis, project obb quad to it and find min and max values | ||
for (let i = 0; i < 4; ++i) { | ||
let d = Vector.dot(quad[i], axis); | ||
min = Math.min(min, d); | ||
max = Math.max(max, d); | ||
} | ||
let quad = obb.quad; | ||
d0 = quad[0] * axis[0] + quad[1] * axis[1]; | ||
d1 = quad[2] * axis[0] + quad[3] * axis[1]; | ||
d2 = quad[4] * axis[0] + quad[5] * axis[1]; | ||
d3 = quad[6] * axis[0] + quad[7] * axis[1]; | ||
return [min, max]; | ||
proj[0] = Math.min(d0, d1, d2, d3); | ||
proj[1] = Math.max(d0, d1, d2, d3); | ||
return proj; | ||
} | ||
static axisCollide (obb_a, obb_b, axes) { | ||
for (let i = 0; i < 2; ++i) { | ||
let a_proj = OBB.projectToAxis(obb_a, axes[i]); | ||
let b_proj = OBB.projectToAxis(obb_b, axes[i]); | ||
static axisCollide(obb_a, obb_b, axis_0, axis_1) { | ||
OBB.projectToAxis(obb_a, axis_0, proj_a); | ||
OBB.projectToAxis(obb_b, axis_0, proj_b); | ||
if (proj_b[0] > proj_a[1] || proj_b[1] < proj_a[0]) { | ||
return false; | ||
} | ||
if (b_proj[0] > a_proj[1] || b_proj[1] < a_proj[0]) { | ||
return false; | ||
} | ||
OBB.projectToAxis(obb_a, axis_1, proj_a); | ||
OBB.projectToAxis(obb_b, axis_1, proj_b); | ||
if (proj_b[0] > proj_a[1] || proj_b[1] < proj_a[0]) { | ||
return false; | ||
} | ||
return true; | ||
@@ -88,5 +127,6 @@ } | ||
static intersect(obb_a, obb_b) { | ||
return OBB.axisCollide(obb_a, obb_b, obb_a.axes) && OBB.axisCollide(obb_a, obb_b, obb_b.axes); | ||
return OBB.axisCollide(obb_a, obb_b, obb_a.axis_0, obb_a.axis_1) && | ||
OBB.axisCollide(obb_a, obb_b, obb_b.axis_0, obb_b.axis_1); | ||
} | ||
} |
@@ -7,3 +7,2 @@ // Miscellaneous utilities | ||
import WorkerBroker from './worker_broker'; | ||
import Geo from './geo'; | ||
@@ -241,5 +240,1 @@ export default Utils; | ||
}; | ||
Utils.pointInTile = function (point) { | ||
return point[0] >= 0 && point[1] > -Geo.tile_scale && point[0] < Geo.tile_scale && point[1] <= 0; | ||
}; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
10758504
99892