mirror of
https://github.com/nasa/openmct.git
synced 2025-06-15 05:38:12 +00:00
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov> Co-authored-by: Andrew Henry <akhenry@gmail.com>
301 lines
10 KiB
JavaScript
301 lines
10 KiB
JavaScript
/*****************************************************************************
|
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
* as represented by the Administrator of the National Aeronautics and Space
|
|
* Administration. All rights reserved.
|
|
*
|
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
* Open MCT includes source code licensed under additional open source
|
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
* this source code distribution or the Licensing information page available
|
|
* at runtime from the About dialog for additional information.
|
|
*****************************************************************************/
|
|
|
|
import EventEmitter from 'EventEmitter';
|
|
import eventHelpers from '../lib/eventHelpers';
|
|
import { MARKER_SHAPES } from './MarkerShapes';
|
|
|
|
// WebGL shader sources (for drawing plain colors)
|
|
const FRAGMENT_SHADER = `
|
|
precision mediump float;
|
|
uniform vec4 uColor;
|
|
uniform int uMarkerShape;
|
|
|
|
void main(void) {
|
|
gl_FragColor = uColor;
|
|
|
|
if (uMarkerShape > 1) {
|
|
vec2 clipSpacePointCoord = 2.0 * gl_PointCoord - 1.0;
|
|
|
|
if (uMarkerShape == 2) { // circle
|
|
float distance = length(clipSpacePointCoord);
|
|
|
|
if (distance > 1.0) {
|
|
discard;
|
|
}
|
|
} else if (uMarkerShape == 3) { // diamond
|
|
float distance = abs(clipSpacePointCoord.x) + abs(clipSpacePointCoord.y);
|
|
|
|
if (distance > 1.0) {
|
|
discard;
|
|
}
|
|
} else if (uMarkerShape == 4) { // triangle
|
|
float x = clipSpacePointCoord.x;
|
|
float y = clipSpacePointCoord.y;
|
|
float distance = 2.0 * x - 1.0;
|
|
float distance2 = -2.0 * x - 1.0;
|
|
|
|
if (distance > y || distance2 > y) {
|
|
discard;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
`;
|
|
|
|
const VERTEX_SHADER = `
|
|
attribute vec2 aVertexPosition;
|
|
uniform vec2 uDimensions;
|
|
uniform vec2 uOrigin;
|
|
uniform float uPointSize;
|
|
|
|
void main(void) {
|
|
gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);
|
|
gl_PointSize = uPointSize;
|
|
}
|
|
`;
|
|
|
|
/**
|
|
* Create a draw api utilizing WebGL.
|
|
*
|
|
* @constructor
|
|
* @param {CanvasElement} canvas the canvas object to render upon
|
|
* @throws {Error} an error is thrown if WebGL is unavailable.
|
|
*/
|
|
function DrawWebGL(canvas, overlay) {
|
|
this.canvas = canvas;
|
|
this.gl = this.canvas.getContext("webgl", { preserveDrawingBuffer: true })
|
|
|| this.canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true });
|
|
|
|
this.overlay = overlay;
|
|
this.c2d = overlay.getContext('2d');
|
|
if (!this.c2d) {
|
|
throw new Error("No canvas 2d!");
|
|
}
|
|
|
|
// Ensure a context was actually available before proceeding
|
|
if (!this.gl) {
|
|
throw new Error("WebGL unavailable.");
|
|
}
|
|
|
|
this.initContext();
|
|
|
|
this.listenTo(this.canvas, "webglcontextlost", this.onContextLost, this);
|
|
}
|
|
|
|
Object.assign(DrawWebGL.prototype, EventEmitter.prototype);
|
|
eventHelpers.extend(DrawWebGL.prototype);
|
|
|
|
DrawWebGL.prototype.onContextLost = function (event) {
|
|
this.emit('error');
|
|
this.isContextLost = true;
|
|
this.destroy();
|
|
};
|
|
|
|
DrawWebGL.prototype.initContext = function () {
|
|
// Initialize shaders
|
|
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
|
|
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
|
|
this.gl.compileShader(this.vertexShader);
|
|
|
|
this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
|
|
this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER);
|
|
this.gl.compileShader(this.fragmentShader);
|
|
|
|
// Assemble vertex/fragment shaders into programs
|
|
this.program = this.gl.createProgram();
|
|
this.gl.attachShader(this.program, this.vertexShader);
|
|
this.gl.attachShader(this.program, this.fragmentShader);
|
|
this.gl.linkProgram(this.program);
|
|
this.gl.useProgram(this.program);
|
|
|
|
// Get locations for attribs/uniforms from the
|
|
// shader programs (to pass values into shaders at draw-time)
|
|
this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition");
|
|
this.uColor = this.gl.getUniformLocation(this.program, "uColor");
|
|
this.uMarkerShape = this.gl.getUniformLocation(this.program, "uMarkerShape");
|
|
this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions");
|
|
this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin");
|
|
this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize");
|
|
|
|
this.gl.enableVertexAttribArray(this.aVertexPosition);
|
|
|
|
// Create a buffer to holds points which will be drawn
|
|
this.buffer = this.gl.createBuffer();
|
|
|
|
// Enable blending, for smoothness
|
|
this.gl.enable(this.gl.BLEND);
|
|
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
|
|
|
|
};
|
|
|
|
DrawWebGL.prototype.destroy = function () {
|
|
this.stopListening();
|
|
};
|
|
|
|
// Convert from logical to physical x coordinates
|
|
DrawWebGL.prototype.x = function (v) {
|
|
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
|
|
};
|
|
|
|
// Convert from logical to physical y coordinates
|
|
DrawWebGL.prototype.y = function (v) {
|
|
return this.height
|
|
- ((v - this.origin[1]) / this.dimensions[1]) * this.height;
|
|
};
|
|
|
|
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) {
|
|
if (this.isContextLost) {
|
|
return;
|
|
}
|
|
|
|
const shapeCode = MARKER_SHAPES[shape] ? MARKER_SHAPES[shape].drawWebGL : 0;
|
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
|
|
this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW);
|
|
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
|
|
this.gl.uniform4fv(this.uColor, color);
|
|
this.gl.uniform1i(this.uMarkerShape, shapeCode);
|
|
this.gl.drawArrays(drawType, 0, points);
|
|
};
|
|
|
|
DrawWebGL.prototype.clear = function () {
|
|
if (this.isContextLost) {
|
|
return;
|
|
}
|
|
|
|
this.height = this.canvas.height = this.canvas.offsetHeight;
|
|
this.width = this.canvas.width = this.canvas.offsetWidth;
|
|
this.overlay.height = this.overlay.offsetHeight;
|
|
this.overlay.width = this.overlay.offsetWidth;
|
|
// Set the viewport size; note that we use the width/height
|
|
// that our WebGL context reports, which may be lower
|
|
// resolution than the canvas we requested.
|
|
this.gl.viewport(
|
|
0,
|
|
0,
|
|
this.gl.drawingBufferWidth,
|
|
this.gl.drawingBufferHeight
|
|
);
|
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT + this.gl.DEPTH_BUFFER_BIT);
|
|
};
|
|
|
|
/**
|
|
* Set the logical boundaries of the chart.
|
|
* @param {number[]} dimensions the horizontal and
|
|
* vertical dimensions of the chart
|
|
* @param {number[]} origin the horizontal/vertical
|
|
* origin of the chart
|
|
*/
|
|
DrawWebGL.prototype.setDimensions = function (dimensions, origin) {
|
|
this.dimensions = dimensions;
|
|
this.origin = origin;
|
|
if (this.isContextLost) {
|
|
return;
|
|
}
|
|
|
|
if (dimensions && dimensions.length > 0
|
|
&& origin && origin.length > 0) {
|
|
this.gl.uniform2fv(this.uDimensions, dimensions);
|
|
this.gl.uniform2fv(this.uOrigin, origin);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Draw the supplied buffer as a line strip (a sequence
|
|
* of line segments), in the chosen color.
|
|
* @param {Float32Array} buf the line strip to draw,
|
|
* in alternating x/y positions
|
|
* @param {number[]} color the color to use when drawing
|
|
* the line, as an RGBA color where each element
|
|
* is in the range of 0.0-1.0
|
|
* @param {number} points the number of points to draw
|
|
*/
|
|
DrawWebGL.prototype.drawLine = function (buf, color, points) {
|
|
if (this.isContextLost) {
|
|
return;
|
|
}
|
|
|
|
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
|
|
};
|
|
|
|
/**
|
|
* Draw the buffer as points.
|
|
*
|
|
*/
|
|
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize, shape) {
|
|
if (this.isContextLost) {
|
|
return;
|
|
}
|
|
|
|
this.gl.uniform1f(this.uPointSize, pointSize);
|
|
this.doDraw(this.gl.POINTS, buf, color, points, shape);
|
|
};
|
|
|
|
/**
|
|
* Draw a rectangle extending from one corner to another,
|
|
* in the chosen color.
|
|
* @param {number[]} min the first corner of the rectangle
|
|
* @param {number[]} max the opposite corner
|
|
* @param {number[]} color the color to use when drawing
|
|
* the rectangle, as an RGBA color where each element
|
|
* is in the range of 0.0-1.0
|
|
*/
|
|
DrawWebGL.prototype.drawSquare = function (min, max, color) {
|
|
if (this.isContextLost) {
|
|
return;
|
|
}
|
|
|
|
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
|
|
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
|
|
), color, 4);
|
|
};
|
|
|
|
DrawWebGL.prototype.drawLimitPoint = function (x, y, size) {
|
|
this.c2d.fillRect(x + size, y, size, size);
|
|
this.c2d.fillRect(x, y + size, size, size);
|
|
this.c2d.fillRect(x - size, y, size, size);
|
|
this.c2d.fillRect(x, y - size, size, size);
|
|
};
|
|
|
|
DrawWebGL.prototype.drawLimitPoints = function (points, color, pointSize) {
|
|
const limitSize = pointSize * 2;
|
|
const offset = limitSize / 2;
|
|
|
|
const mappedColor = color.map(function (c, i) {
|
|
return i < 3 ? Math.floor(c * 255) : (c);
|
|
}).join(',');
|
|
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
|
|
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
this.drawLimitPoint(
|
|
this.x(points[i].x) - offset,
|
|
this.y(points[i].y) - offset,
|
|
limitSize
|
|
);
|
|
}
|
|
};
|
|
|
|
export default DrawWebGL;
|