s2maps-gpu
Version:
S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.
180 lines (179 loc) • 6.62 kB
JavaScript
import { Color } from './color/index.js';
/**
* This encoder is built for webgl/webgpu to parse for conditional drawing.
* The Style object will parse all layers' attributes like "color", "fill", "width", etc.
* The code will be placed into "LayerCode" for the GPU shader to utiilize as necessary.
*
* CONDITION ENCODINGS: 128 positions possible
* 0 -> null
* 1 -> value
* 2 -> data-condition
* 3 -> input-condition
* 4 -> data-range
* 5 -> input-range
* 6 -> feature-state (this updates for each draw assuming the feature has a "feature-state")
* 7 -> animation-state (this updates for each draw assuming the feature has a "animation-state")
* 8 -> input-value (this is a constant value pulled from properties)
*
* FEATURE-STATE ENCODINGS:
* 0 -> default (inactive)
* 1 -> hover
* 2 -> active
* 3 -> selected
* 4 -> disabled
*
* INPUT RANGE/CONDITION ENCODINGS:
* 0 -> zoom
* 1 -> lon
* 2 -> lat
* 3 -> angle
* 4 -> pitch
* 5 -> time
*
* INTERPOLATION ENCODINGS: data-ranges or input-ranges have either linear or exponential interpolations
* if exponential the base must also be encoded, after the type
* 0 -> linear
* 1 -> exponential
* 2 -> quad-bezier
* 3 -> cubic-bezier
* 4 -> step
* @param input - the value to encode
* @param lch - whether to encode in lch
* @returns an array of numbers encoded for the GPU to utilize
*/
export default function encodeLayerAttribute(input, lch) {
const encodings = [];
encodings.push(0); // store a null no matter what
if (typeof input === 'object') {
// conditional
if ('dataCondition' in input && input.dataCondition !== undefined) {
// set the condition bits as data-condition
encodings[0] += 2 << 4;
// encode the condition type
encodings.push(...encodeDataCondition(input.dataCondition, lch));
}
else if ('inputCondition' in input && input.inputCondition !== undefined) {
// set the condition bits as input-condition
encodings[0] += 3 << 4;
// TODO: encode the condition type correctly
}
else if ('dataRange' in input && input.dataRange !== undefined) {
const { dataRange } = input;
const { base, ease } = dataRange;
// set the condition bits as data-range
encodings[0] += 4 << 4;
// encode the interpolation type
if (ease === 'expo') {
encodings[0] += 1;
encodings.push(base ?? 1);
}
// encode range data and store
encodings.push(...encodeRange(dataRange, lch));
}
else if ('inputRange' in input && input.inputRange !== undefined) {
const { inputRange } = input;
const { type, ease, base } = inputRange;
// set the condition bits as input-range
encodings[0] += 5 << 4;
// encode the input-range type
if (type === 'zoom')
encodings[0] += 0 << 1;
else if (type === 'lon')
encodings[0] += 1 << 1;
else if (type === 'lat')
encodings[0] += 2 << 1;
else if (type === 'angle')
encodings[0] += 3 << 1;
else if (type === 'pitch')
encodings[0] += 4 << 1;
// encode the interpolation type (ONLY expo takes a base)
if (ease === 'expo') {
encodings[0] += 1;
encodings.push(base ?? 1);
}
// encode range data and store
encodings.push(...encodeRange(inputRange, lch));
}
else if ('featureState' in input && input.featureState !== undefined) {
// set the condition bits as feature-state
encodings[0] += 6 << 4;
// encode the feature-states and store
encodings.push(...encodeFeatureStates(input.featureState, lch));
}
else if ('inputValue' in input && input.inputValue !== undefined) {
// set the condition bits as input-condition
encodings[0] += 8 << 4;
}
else
throw Error('unknown condition type');
}
else if (input !== undefined && input !== null) {
// assuming data exists, than it's just a value type
// value
if (typeof input === 'string') {
const color = new Color(input); // build the color as RGB or LCH
encodings[0] += 1 << 4; // set the condition bits as 1 (value)
encodings.push(...(lch ? color.getLCH() : color.getRGB())); // store that it is a value and than the values
}
else if (typeof input === 'number') {
encodings[0] += 1 << 4; // set the condition bits as 1 (value)
encodings.push(input); // store true as 1 and false as 0, otherwise it's a number
}
else
throw Error('unknown condition type');
}
// lastly store length of the current encoding
encodings[0] += encodings.length << 10;
return encodings;
}
/**
* Encode a data condition
* @param dataCondition - the data condition to encode
* @param lch - whether or not to use lch
* @returns the encoded data condition
*/
function encodeDataCondition(dataCondition, lch) {
const { conditions, fallback } = dataCondition;
const encoding = [];
let i = 1;
for (const { input } of conditions) {
encoding.push(i, ...encodeLayerAttribute(input, lch));
i++;
}
encoding.push(0, ...encodeLayerAttribute(fallback, lch));
return encoding;
}
/**
* Encode a data range
* @param dataRange - the data range to encode
* @param lch - whether or not to use lch
* @returns the encoded data range
*/
function encodeRange(dataRange, lch) {
const { ranges } = dataRange;
const encoding = [];
for (const { stop, input } of ranges) {
encoding.push(stop, ...encodeLayerAttribute(input, lch));
}
return encoding;
}
/**
* Encode a feature state
* @param featureState - the feature state to encode
* @param lch - whether or not to use lch
* @returns the encoded feature state
*/
function encodeFeatureStates(featureState, lch) {
const { condition, input } = featureState;
const encoding = [];
const conditionCode = condition === 'default'
? 0 // (inactive)
: condition === 'hover'
? 1
: condition === 'active'
? 2
: 0; // default / inactive
// store condition
encoding.push(conditionCode, ...encodeLayerAttribute(input, lch));
return encoding;
}