Extract canvas/webgl code to separate component
This commit is contained in:
215
frontend/src/app/components/block-overview-graph/tx-sprite.ts
Normal file
215
frontend/src/app/components/block-overview-graph/tx-sprite.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { FastVertexArray } from './fast-vertex-array';
|
||||
import { InterpolatedAttribute, Attributes, OptionalAttributes, SpriteUpdateParams, Update } from './sprite-types';
|
||||
|
||||
const attribKeys = ['a', 'b', 't', 'v'];
|
||||
const updateKeys = ['x', 'y', 's', 'r', 'g', 'b', 'a'];
|
||||
|
||||
export default class TxSprite {
|
||||
static vertexSize = 30;
|
||||
static vertexCount = 6;
|
||||
static dataSize: number = (30 * 6);
|
||||
|
||||
vertexArray: FastVertexArray;
|
||||
vertexPointer: number;
|
||||
vertexData: number[];
|
||||
updateMap: Update;
|
||||
attributes: Attributes;
|
||||
tempAttributes: OptionalAttributes;
|
||||
|
||||
|
||||
constructor(params: SpriteUpdateParams, vertexArray: FastVertexArray) {
|
||||
const offsetTime = params.start;
|
||||
this.vertexArray = vertexArray;
|
||||
this.vertexData = Array(VI.length).fill(0);
|
||||
this.updateMap = {
|
||||
x: 0, y: 0, s: 0, r: 0, g: 0, b: 0, a: 0
|
||||
};
|
||||
|
||||
this.attributes = {
|
||||
x: { a: params.x, b: params.x, t: offsetTime, v: 0, d: 0 },
|
||||
y: { a: params.y, b: params.y, t: offsetTime, v: 0, d: 0 },
|
||||
s: { a: params.s, b: params.s, t: offsetTime, v: 0, d: 0 },
|
||||
r: { a: params.r, b: params.r, t: offsetTime, v: 0, d: 0 },
|
||||
g: { a: params.g, b: params.g, t: offsetTime, v: 0, d: 0 },
|
||||
b: { a: params.b, b: params.b, t: offsetTime, v: 0, d: 0 },
|
||||
a: { a: params.a, b: params.a, t: offsetTime, v: 0, d: 0 },
|
||||
};
|
||||
|
||||
// Used to temporarily modify the sprite, so that the base view can be resumed later
|
||||
this.tempAttributes = null;
|
||||
|
||||
this.vertexPointer = this.vertexArray.insert(this);
|
||||
|
||||
this.compile();
|
||||
}
|
||||
|
||||
private interpolateAttributes(updateMap: Update, attributes: OptionalAttributes, offsetTime: DOMHighResTimeStamp, v: number,
|
||||
duration: number, minDuration: number, adjust: boolean): void {
|
||||
for (const key of Object.keys(updateMap)) {
|
||||
// for each non-null attribute:
|
||||
if (updateMap[key] != null) {
|
||||
// calculate current interpolated value, and set as 'from'
|
||||
interpolateAttributeStart(attributes[key], offsetTime);
|
||||
// update start time
|
||||
attributes[key].t = offsetTime;
|
||||
|
||||
if (!adjust || (duration && attributes[key].d === 0)) {
|
||||
attributes[key].v = v;
|
||||
attributes[key].d = duration;
|
||||
} else if (minDuration > attributes[key].d) {
|
||||
// enforce minimum transition duration
|
||||
attributes[key].v = 1 / minDuration;
|
||||
attributes[key].d = minDuration;
|
||||
}
|
||||
// set 'to' to target value
|
||||
attributes[key].b = updateMap[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
params:
|
||||
x, y, s: position & size of the sprite
|
||||
r, g, b, a: color & opacity
|
||||
start: performance.now() timestamp, when to start the transition
|
||||
duration: of the tweening animation
|
||||
adjust: if true, alter the target value of any conflicting transitions without changing the duration
|
||||
minDuration: minimum remaining transition duration when adjust = true
|
||||
temp: if true, this update is only temporary (can be reversed with 'resume')
|
||||
*/
|
||||
update(params: SpriteUpdateParams): void {
|
||||
const offsetTime = params.start || performance.now();
|
||||
const v = params.duration > 0 ? (1 / params.duration) : 0;
|
||||
|
||||
updateKeys.forEach(key => {
|
||||
this.updateMap[key] = params[key];
|
||||
});
|
||||
|
||||
const isModified = !!this.tempAttributes;
|
||||
if (!params.temp) {
|
||||
this.interpolateAttributes(this.updateMap, this.attributes, offsetTime, v, params.duration, params.minDuration, params.adjust);
|
||||
} else {
|
||||
if (!isModified) { // set up tempAttributes
|
||||
this.tempAttributes = {};
|
||||
for (const key of Object.keys(this.updateMap)) {
|
||||
if (this.updateMap[key] != null) {
|
||||
this.tempAttributes[key] = { ...this.attributes[key] };
|
||||
}
|
||||
}
|
||||
}
|
||||
this.interpolateAttributes(this.updateMap, this.tempAttributes, offsetTime, v, params.duration, params.minDuration, params.adjust);
|
||||
}
|
||||
|
||||
this.compile();
|
||||
}
|
||||
|
||||
// Transition back from modified state back to base attributes
|
||||
resume(duration: number, start: DOMHighResTimeStamp = performance.now()): void {
|
||||
// If not in modified state, there's nothing to do
|
||||
if (!this.tempAttributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetTime = start;
|
||||
const v = duration > 0 ? (1 / duration) : 0;
|
||||
|
||||
for (const key of Object.keys(this.tempAttributes)) {
|
||||
// If this base attribute is static (fixed or post-transition), transition smoothly back
|
||||
if (this.attributes[key].v === 0 || (this.attributes[key].t + this.attributes[key].d) <= start) {
|
||||
// calculate current interpolated value, and set as 'from'
|
||||
interpolateAttributeStart(this.tempAttributes[key], offsetTime);
|
||||
this.attributes[key].a = this.tempAttributes[key].a;
|
||||
this.attributes[key].t = offsetTime;
|
||||
this.attributes[key].v = v;
|
||||
this.attributes[key].d = duration;
|
||||
}
|
||||
}
|
||||
|
||||
this.tempAttributes = null;
|
||||
|
||||
this.compile();
|
||||
}
|
||||
|
||||
// Write current state into the graphics vertex array for rendering
|
||||
compile(): void {
|
||||
let attributes = this.attributes;
|
||||
if (this.tempAttributes) {
|
||||
attributes = {
|
||||
...this.attributes,
|
||||
...this.tempAttributes
|
||||
};
|
||||
}
|
||||
const size = attributes.s;
|
||||
|
||||
// update vertex data in place
|
||||
// ugly, but avoids overhead of allocating large temporary arrays
|
||||
const vertexStride = VI.length + 2;
|
||||
for (let vertex = 0; vertex < 6; vertex++) {
|
||||
this.vertexData[vertex * vertexStride] = vertexOffsetFactors[vertex][0];
|
||||
this.vertexData[(vertex * vertexStride) + 1] = vertexOffsetFactors[vertex][1];
|
||||
for (let step = 0; step < VI.length; step++) {
|
||||
// components of each field in the vertex array are defined by an entry in VI:
|
||||
// VI[i].a is the attribute, VI[i].f is the inner field, VI[i].offA and VI[i].offB are offset factors
|
||||
this.vertexData[(vertex * vertexStride) + step + 2] = attributes[VI[step].a][VI[step].f];
|
||||
}
|
||||
}
|
||||
|
||||
this.vertexArray.setData(this.vertexPointer, this.vertexData);
|
||||
}
|
||||
|
||||
moveVertexPointer(index: number): void {
|
||||
this.vertexPointer = index;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.vertexArray.remove(this.vertexPointer);
|
||||
this.vertexPointer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// expects 0 <= x <= 1
|
||||
function smootherstep(x: number): number {
|
||||
const ix = 1 - x;
|
||||
x = x * x;
|
||||
return x / (x + ix * ix);
|
||||
}
|
||||
|
||||
function interpolateAttributeStart(attribute: InterpolatedAttribute, start: DOMHighResTimeStamp): void {
|
||||
if (attribute.v === 0 || (attribute.t + attribute.d) <= start) {
|
||||
// transition finished, next transition starts from current end state
|
||||
// (clamp to 1)
|
||||
attribute.a = attribute.b;
|
||||
attribute.v = 0;
|
||||
attribute.d = 0;
|
||||
} else if (attribute.t > start) {
|
||||
// transition not started
|
||||
// (clamp to 0)
|
||||
} else {
|
||||
// transition in progress
|
||||
// (interpolate)
|
||||
const progress = (start - attribute.t);
|
||||
const delta = smootherstep(progress / attribute.d);
|
||||
attribute.a = attribute.a + (delta * (attribute.b - attribute.a));
|
||||
attribute.d = attribute.d - progress;
|
||||
attribute.v = 1 / attribute.d;
|
||||
}
|
||||
}
|
||||
|
||||
const vertexOffsetFactors = [
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[1, 0],
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[0, 1]
|
||||
];
|
||||
|
||||
const VI = [];
|
||||
updateKeys.forEach((attribute, aIndex) => {
|
||||
attribKeys.forEach(field => {
|
||||
VI.push({
|
||||
a: attribute,
|
||||
f: field
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user