зеркало из https://github.com/microsoft/scenepic.git
1316 строки
50 KiB
TypeScript
1316 строки
50 KiB
TypeScript
import { mat3, mat4, vec3, vec4, quat, vec2 } from "gl-matrix";
|
|
import Misc from "./Misc"
|
|
import { ObjectCache, CanvasBase } from "./CanvasBase"
|
|
import Mesh from "./Mesh";
|
|
import ShaderProgram from "./Shaders";
|
|
import WebGLMeshBuffers from "./WebGLMeshBuffers";
|
|
import { MeshPicker } from "./MeshPicker";
|
|
|
|
// Mesh representing a single cube used to show the user where the focus point
|
|
// Generated by "ts_assets.py"
|
|
var FocusPointMeshDefinition = {
|
|
"Color": "eAFjYGiwZwCDBnsACIIBfwMAAAAB",
|
|
"IndexBufferType": "UInt16",
|
|
"LineBuffer": "eAEDAAAAAAEAAAAAAg==",
|
|
"PrimitiveType": "SingleColorMesh",
|
|
"TriangleBuffer": "eAENxEkCQDAQALDYKYqi/v9Tc0hotIRObzDGo8lskeJktdnlODucLiUubo9XjavPDycwAZkMAAAAAw==",
|
|
"VertexBuffer": "eAF1kYENACEIA9nsV3OzX+0fBa2lkJDoaVqqZvba6aiR+0dwZx1XOsCHr7NAR/K/Cw+9ji/TOMdGjhmYKx3gM08W6EjuuZjnOzT3p23605ybU67ClQ7k2t4wz8XpHwu/dD4ziz2hGAAAAAY="
|
|
};
|
|
|
|
// Default camera values
|
|
var DefaultCamera =
|
|
{
|
|
"WorldToCamera": mat4.fromValues(1.0, 0.0, 0.0, 0.0,
|
|
0.0, 1.0, 0.0, 0.0,
|
|
0.0, 0.0, 1.0, 0.0,
|
|
0.0, 0.0, -4.0, 1.0),
|
|
"FoVYDegrees": 45.0
|
|
};
|
|
|
|
var DefaultNearCropDistance = 0.01;
|
|
var DefaultFarCropDistance = 20.0;
|
|
var DegreesToRadians = Math.PI / 180.0;
|
|
|
|
// Default shading parameters
|
|
var DefaultShading =
|
|
{
|
|
"BackgroundColor": [0.0, 0.0, 0.0, 1.0],
|
|
"AmbientLightColor": [0.5, 0.5, 0.5],
|
|
"DirectionalLightColor": [0.5, 0.5, 0.5],
|
|
"DirectionalLightDir": [2.0, -1.0, 1.0]
|
|
};
|
|
|
|
// Represents an instantiation of a mesh in a frame
|
|
class MeshInstance {
|
|
meshId: string;
|
|
transform: mat4;
|
|
|
|
constructor(meshId: string, transform: mat4) {
|
|
this.meshId = meshId;
|
|
this.transform = transform;
|
|
}
|
|
}
|
|
|
|
class MeshData {
|
|
constructor(
|
|
public mesh: Mesh,
|
|
public m2wMatrix: mat4,
|
|
public m2vMatrix: mat4,
|
|
public buffer: WebGLMeshBuffers,
|
|
public opacity: number,
|
|
public filled: boolean,
|
|
public wireframe: boolean,
|
|
public viewDistance: number) { }
|
|
|
|
public Render(gl: WebGL2RenderingContext, v2sMatrix: mat4) {
|
|
// Optionally turn off back-face culling
|
|
if (this.mesh.doubleSided)
|
|
gl.disable(gl.CULL_FACE);
|
|
else
|
|
gl.enable(gl.CULL_FACE);
|
|
|
|
// Render
|
|
this.buffer.RenderBuffer(v2sMatrix, this.m2vMatrix, this.opacity, this.filled, this.wireframe);
|
|
}
|
|
|
|
public ComputeCentroid(): vec3 {
|
|
let centroid = vec3.create();
|
|
const norm = 1.0 / this.mesh.CountVertices();
|
|
for (let i = 0, j = 0; i < this.mesh.CountVertices(); i++, j += this.mesh.ElementsPerVertex) {
|
|
let p = vec3.transformMat4(vec3.create(),
|
|
<vec3>this.mesh.vertexBuffer.subarray(j, j + 3),
|
|
this.m2wMatrix);
|
|
vec3.scale(p, p, norm)
|
|
vec3.add(centroid, centroid, p);
|
|
}
|
|
|
|
if (this.mesh.CountInstances() == 1) {
|
|
return centroid;
|
|
}
|
|
|
|
let instanceCentroid = vec3.create();
|
|
const instanceNorm = 1.0 / this.mesh.CountInstances();
|
|
for (let i = 0, j = 0; i < this.mesh.CountInstances(); i++, j += this.mesh.ElementsPerInstance) {
|
|
let p = vec3.add(vec3.create(),
|
|
<vec3>this.mesh.instanceBuffer.subarray(j, j + 3),
|
|
instanceCentroid);
|
|
vec3.scale(p, p, instanceNorm);
|
|
vec3.add(instanceCentroid, instanceCentroid, p);
|
|
}
|
|
|
|
return instanceCentroid;
|
|
}
|
|
}
|
|
|
|
export default class Canvas3D extends CanvasBase {
|
|
allMeshes: any = null // Maps from meshIds to mesh objects
|
|
gl: WebGL2RenderingContext;
|
|
sp: ShaderProgram;
|
|
|
|
// Dictionary from layerId to layer settings (wireframe, filled, opacity)
|
|
layerSettings: { [layerId: string]: { [key: string]: number | boolean } } = {};
|
|
layerIds: string[] = [];
|
|
|
|
// Shading parameters
|
|
bgColor: vec4;
|
|
ambientLightColor: vec3;
|
|
directionalLightColor: vec3;
|
|
directionalLightDir: vec3;
|
|
globalFill = true;
|
|
globalWireframe = false;
|
|
globalOpacity = 1.0;
|
|
|
|
// Mesh picker
|
|
setFocusToPicked: boolean;
|
|
pickPoint: vec2;
|
|
meshPicker: MeshPicker;
|
|
|
|
// Frame instances
|
|
frameInstances: MeshInstance[][] = []; // [frameIndex][mesh instance within frame]
|
|
|
|
// Mesh buffers *for the current frame*
|
|
meshBuffers: any = {}; // The webgl mesh buffers for the currently selected frame: dictionary from meshId to webGLMeshBuffer
|
|
|
|
// Pointer speeds
|
|
pointerAltKeyMultiplier = 0.2;
|
|
pointerRotationSpeed = 0.01;
|
|
mouseWheelTranslationSpeed = 0.005;
|
|
keyDownSpeed = 0.1;
|
|
thumbStickDeadZoneAmount = 0.2;
|
|
thumbStickRotationSpeed = 0.1;
|
|
thumbStickTranslationSpeed = 0.01;
|
|
touchpadAsButtonThreshold = 0.5;
|
|
buttonTranslationSpeed = 0.01;
|
|
buttonScaleSpeed = 1.01;
|
|
|
|
// first person mode
|
|
cameraVelocity = vec3.fromValues(0.0, 0.0, 0.0);
|
|
cameraRotationalVelocity = vec2.fromValues(0.0, 0.0);
|
|
dirKeyPresses = {
|
|
"w": 0,
|
|
"a": 0,
|
|
"s": 0,
|
|
"d": 0,
|
|
"q": 0,
|
|
"e": 0
|
|
}
|
|
|
|
// Focus points for user interaction: center of rotation and used for locking view
|
|
globalFocusPoint: Float32Array = new Float32Array([0.0, 0.0, 0.0]);
|
|
initialFocusPoints: Float32Array[] = []; // Per-frame focus points (initial version - used for reset camera)
|
|
currentFocusPoints: Float32Array[] = []; // Per-frame focus points (current version - used for display)
|
|
focusPointMeshBuffer: WebGLMeshBuffers;
|
|
showFocusPoint: boolean = false;
|
|
|
|
// Copy of last set camera config
|
|
globalCameraParams: Object = null;
|
|
frameCameraParams: Object[] = []; // Per-frame cameras
|
|
onCameraTrack: boolean = false;
|
|
|
|
// Frame layer settings
|
|
frameLayerSettings: Object[] = [];
|
|
|
|
// Lock view settings
|
|
lockViewXY = false;
|
|
lockViewOrientation = false;
|
|
|
|
// Orbit settings
|
|
orbitCamera = false;
|
|
lastOrbitTime: Date = null;
|
|
|
|
// View matrices
|
|
w2vMatrix: mat4; // World to view (i.e. camera)
|
|
v2sMatrix: mat4; // View to screen (i.e. projection)
|
|
|
|
constructor(canvasId: string, public frameRate: number, public width: number, public height: number, allMeshes: any, objectCache: ObjectCache, public SetStatus: (status: string) => void, public SetWarning: (message: string) => void, public RequestRedraw: () => void, public ReportFrameIdChange: (canvasId: string, frameId: string) => void, public SimulateKeyPress: (canvasId: string, key: string) => void) {
|
|
// Base class constructor
|
|
super(canvasId, frameRate, width, height, objectCache, SetStatus, SetWarning, RequestRedraw, ReportFrameIdChange);
|
|
|
|
// Store meshes
|
|
this.allMeshes = allMeshes;
|
|
|
|
this.dropdown.style.visibility = "visible";
|
|
|
|
// Init gl
|
|
this.InitializeWebGL();
|
|
window.addEventListener("webglcontextlost", event => { this.TearDownWebGL(); event.preventDefault(); });
|
|
window.addEventListener("webglcontextrestored", event => { this.InitializeWebGL(); event.preventDefault(); });
|
|
|
|
// Set default scene values
|
|
this.SetCamera(DefaultCamera);
|
|
this.globalCameraParams = DefaultCamera;
|
|
this.onCameraTrack = true;
|
|
this.SetShading(DefaultShading);
|
|
|
|
this.cameraModeDisplay.style.visibility = "visible";
|
|
|
|
// Create dropdown
|
|
this.SetLayerSettings({});
|
|
|
|
// Start render loop
|
|
this.StartRenderLoop();
|
|
}
|
|
|
|
InitializeWebGL() {
|
|
// Init gl
|
|
try {
|
|
// Get gl context
|
|
this.gl = this.htmlCanvas.getContext("webgl2");
|
|
|
|
// Add support for uint draw_elements
|
|
this.gl.getExtension("OES_element_index_uint");
|
|
}
|
|
catch (e) { }
|
|
if (this.gl == null) {
|
|
this.SetWarning("Could not init WebGL");
|
|
return;
|
|
}
|
|
|
|
// Create shader program
|
|
this.sp = new ShaderProgram(this.gl, "defaultVertex", "defaultFragment");
|
|
|
|
// Create focus point cuboid
|
|
var focusPointMesh = Mesh.Parse(FocusPointMeshDefinition);
|
|
this.focusPointMeshBuffer = new WebGLMeshBuffers(this.gl, this.sp, focusPointMesh);
|
|
|
|
// Create picker
|
|
this.meshPicker = new MeshPicker(this.gl, this.width, this.height);
|
|
|
|
// Recreate buffers
|
|
this.PrepareBuffers();
|
|
}
|
|
|
|
TearDownWebGL() {
|
|
this.gl = null;
|
|
this.sp = null;
|
|
if (this.focusPointMeshBuffer != null)
|
|
this.focusPointMeshBuffer.Finalize();
|
|
this.focusPointMeshBuffer = null;
|
|
this.PrepareBuffers();
|
|
}
|
|
|
|
private updateCameraVelocityValue(keyPlus: string, keyMinus: string, index: number) {
|
|
if (this.dirKeyPresses[keyPlus] > this.dirKeyPresses[keyMinus]) {
|
|
this.cameraVelocity[index] = this.keyDownSpeed
|
|
}
|
|
else if (this.dirKeyPresses[keyMinus] > 0) {
|
|
this.cameraVelocity[index] = -this.keyDownSpeed;
|
|
}
|
|
else {
|
|
this.cameraVelocity[index] = 0;
|
|
}
|
|
}
|
|
|
|
private updateCameraVelocity() {
|
|
this.updateCameraVelocityValue("w", "s", 0);
|
|
this.updateCameraVelocityValue("a", "d", 1);
|
|
this.updateCameraVelocityValue("q", "e", 2);
|
|
}
|
|
|
|
HandlePointerMoveWithTwist(point: vec2, twistAngle: number, event: PointerEvent) {
|
|
const countPointers = this.pointerCoords.size;
|
|
if (countPointers == 0) return;
|
|
|
|
const old = this.pointerCoords.get(event.pointerId);
|
|
let delta = vec2.subtract(vec2.create(), point, old);
|
|
if (event.altKey) {
|
|
vec2.scale(delta, delta, this.pointerAltKeyMultiplier);
|
|
}
|
|
|
|
if (this.FirstPerson) {
|
|
const init = this.initPointerCoords.get(event.pointerId);
|
|
let diff = vec2.subtract(vec2.create(), point, init);
|
|
|
|
const length = vec2.length(diff);
|
|
const deadZone = 10;
|
|
if (length > deadZone) {
|
|
let scale = Math.min(2, (length - deadZone) / deadZone);
|
|
vec2.scale(diff, diff, scale / length);
|
|
}
|
|
else {
|
|
diff = vec2.create();
|
|
}
|
|
|
|
vec2.scale(diff, diff, this.pointerRotationSpeed);
|
|
this.SetCameraRotationalVelocity(diff);
|
|
}
|
|
else if (countPointers == 2) {
|
|
// Deal with pinch-zoom
|
|
const pinchZoom = this.PinchZoom(point, event);
|
|
|
|
// Change in distances
|
|
var zOld = this.GetCurrentFocusPointInViewSpace()[2];
|
|
var zNew = zOld / pinchZoom.distanceRatio;
|
|
this.TranslateCamera(vec3.fromValues(0.0, 0.0, zNew - zOld));
|
|
|
|
const focusDelta = this.ComputeFocusPointRelativeViewSpaceTranslation(old, point);
|
|
this.TranslateCamera(focusDelta);
|
|
|
|
this.RotateCamera(0.0, 0.0, -pinchZoom.angleDelta);
|
|
}
|
|
else {
|
|
// Deal with basic events
|
|
if (event.ctrlKey) {
|
|
// Treat as twist of camera
|
|
this.RotateCamera(0.0, 0.0, twistAngle);
|
|
}
|
|
else if (this.showFocusPoint) {
|
|
// Translate the 3D center of rotation
|
|
this.SetFocusPointPositionFromPixelCoordinates(point);
|
|
}
|
|
else if (event.shiftKey) // Treat as translation of camera
|
|
{
|
|
const focusDelta = this.ComputeFocusPointRelativeViewSpaceTranslation(old, point);
|
|
this.TranslateCamera(focusDelta);
|
|
}
|
|
else if (countPointers == 1) // Treat as rotation of camera about center of rotation
|
|
{
|
|
vec2.scale(delta, delta, this.pointerRotationSpeed);
|
|
// NB y and x are deliberately crossed over
|
|
this.RotateCamera(delta[1], delta[0], 0.0);
|
|
}
|
|
}
|
|
|
|
super.HandlePointerMove(point, event);
|
|
}
|
|
|
|
HandlePointerUp(event: PointerEvent) {
|
|
this.SetCameraRotationalVelocity(vec2.create());
|
|
super.HandlePointerUp(event);
|
|
}
|
|
|
|
HandleMouseWheel(event: WheelEvent) {
|
|
let deltaZ = -event.deltaY * this.mouseWheelTranslationSpeed;
|
|
|
|
if (event.altKey) {
|
|
deltaZ *= this.pointerAltKeyMultiplier;
|
|
}
|
|
|
|
var delta = vec3.fromValues(0.0, 0.0, deltaZ);
|
|
|
|
this.TranslateCamera(delta);
|
|
}
|
|
|
|
HandleKeyUp(key: string) {
|
|
key = key.toLowerCase();
|
|
if (this.firstPerson && key in this.dirKeyPresses) {
|
|
this.dirKeyPresses[key] = 0;
|
|
this.updateCameraVelocity();
|
|
}
|
|
else {
|
|
for (let key in this.dirKeyPresses) {
|
|
this.dirKeyPresses[key] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
HandleKeyDown(key: string): [boolean, boolean] {
|
|
var result = super.HandleKeyDown(key);
|
|
if (result[0]) return result; // Already handled
|
|
|
|
var handled = true;
|
|
key = key.toLowerCase();
|
|
switch (key) {
|
|
case "r":
|
|
this.ResetView();
|
|
break;
|
|
case "l":
|
|
this.ToggleLockView(true, false);
|
|
break;
|
|
case "o":
|
|
this.ToggleLockView(false, true);
|
|
break;
|
|
case "\\":
|
|
this.ToggleOrbitCamera();
|
|
break;
|
|
case "capslock":
|
|
this.showFocusPoint = !this.showFocusPoint;
|
|
break;
|
|
default:
|
|
handled = false;
|
|
break;
|
|
}
|
|
|
|
if (this.firstPerson && key in this.dirKeyPresses) {
|
|
this.dirKeyPresses[key] = Date.now();
|
|
this.updateCameraVelocity();
|
|
}
|
|
|
|
return [handled, false];
|
|
}
|
|
|
|
// Adds UI for the control of certain layers
|
|
SetLayerSettings(layerSettings: any) {
|
|
// Delete any previous controls
|
|
while (this.dropdownTable.childElementCount > 2)
|
|
this.dropdownTable.removeChild(this.dropdownTable.lastChild);
|
|
|
|
this.layerSettings = layerSettings;
|
|
this.layerIds = [null];
|
|
for (let layerId in layerSettings) {
|
|
this.layerIds.push(layerId);
|
|
}
|
|
|
|
// Add header row
|
|
var BorderStyle = "1px solid #cccccc";
|
|
var headerRow = document.createElement("tr");
|
|
headerRow.style.borderBottom = BorderStyle;
|
|
for (var headerName of ["Wire", "Fill", "Opacity", "Layer Id"]) {
|
|
var headerItem = document.createElement("th");
|
|
headerItem.className = "scenepic-dropdown-header";
|
|
headerItem.innerHTML = headerName;
|
|
headerRow.appendChild(headerItem);
|
|
}
|
|
this.dropdownTable.appendChild(headerRow);
|
|
|
|
// Create row helper function
|
|
var createRow = (id, label) => {
|
|
// Create wireframe checkbox
|
|
var checkboxWireframe = document.createElement("input");
|
|
checkboxWireframe.type = "checkbox";
|
|
checkboxWireframe.checked = this.ShowLayerWireframe(id);
|
|
checkboxWireframe.className = "scenepic-table-control";
|
|
checkboxWireframe.addEventListener("change", event => { this.SetLayerWireframe(id, checkboxWireframe.checked); this.PrepareBuffers(); event.stopPropagation(); });
|
|
|
|
// Create fill checkbox
|
|
var checkboxFill = document.createElement("input");
|
|
checkboxFill.type = "checkbox";
|
|
checkboxFill.checked = this.ShowLayerFilled(id);
|
|
checkboxFill.addEventListener("change", event => { this.SetLayerFilled(id, checkboxFill.checked); this.PrepareBuffers(); event.stopPropagation(); });
|
|
|
|
// Create opacity slider
|
|
var sliderOpacity = document.createElement("input");
|
|
sliderOpacity.type = "range";
|
|
sliderOpacity.min = "0";
|
|
sliderOpacity.max = "100";
|
|
sliderOpacity.value = String(100.0 * this.GetLayerOpacity(id));
|
|
sliderOpacity.addEventListener("change", event => { this.SetLayerOpacity(id, Number(sliderOpacity.value) / 100.0); this.PrepareBuffers(); event.stopPropagation(); })
|
|
sliderOpacity.style.width = "50px";
|
|
sliderOpacity.style.height = "10px";
|
|
|
|
// Add layer label
|
|
var labelLayer = document.createElement("label");
|
|
labelLayer.appendChild(document.createTextNode(label));
|
|
|
|
return [checkboxWireframe, checkboxFill, sliderOpacity, labelLayer];
|
|
};
|
|
|
|
var addRow = (rowItems, border) => {
|
|
// Create table row
|
|
var tr = document.createElement("tr");
|
|
if (border)
|
|
tr.style.borderTop = BorderStyle;
|
|
var addControl = (el, className) => {
|
|
var td = document.createElement("td");
|
|
td.appendChild(el);
|
|
td.className = className;
|
|
tr.appendChild(td);
|
|
};
|
|
addControl(rowItems[0], "scenepic-table-control");
|
|
addControl(rowItems[1], "scenepic-table-control");
|
|
addControl(rowItems[2], "scenepic-table-control");
|
|
addControl(rowItems[3], "scenepic-table-layerid");
|
|
this.dropdownTable.appendChild(tr);
|
|
}
|
|
|
|
// Add new controls
|
|
Object.keys(layerSettings).forEach(id => addRow(createRow(id, id), false));
|
|
addRow(createRow("<<<GLOBAL>>>", "Global"), true);
|
|
}
|
|
|
|
ConfigureUserInterface(command: any) {
|
|
if ("PointerAltKeyMultiplier" in command) this.pointerAltKeyMultiplier = command["PointerAltKeyMultiplier"];
|
|
if ("PointerRotationSpeed" in command) this.pointerRotationSpeed = command["PointerRotationSpeed"];
|
|
if ("MouseWheelTranslationSpeed" in command) this.mouseWheelTranslationSpeed = command["MouseWheelTranslationSpeed"];
|
|
if ("KeyDownSpeed" in command) this.keyDownSpeed = command["KeyDownSpeed"];
|
|
if ("LayerDropdownVisibility" in command) this.dropdown.style.visibility = command["LayerDropdownVisibility"]
|
|
}
|
|
|
|
ParseFocusPoint(command: any) {
|
|
var position = Misc.Base64ToFloat32Array(command["Position"]);
|
|
if ("OrientationAxisAngle" in command) {
|
|
var focusPoint = new Float32Array(6);
|
|
focusPoint.set(position, 0);
|
|
focusPoint.set(Misc.Base64ToFloat32Array(command["OrientationAxisAngle"]), 3);
|
|
return focusPoint;
|
|
}
|
|
else {
|
|
return position;
|
|
}
|
|
}
|
|
|
|
SetGlobalCamera(value: Object) {
|
|
if (value == null) return;
|
|
|
|
this.globalCameraParams = value;
|
|
for (var frameIndex = 0; frameIndex < this.frameCameraParams.length; frameIndex++)
|
|
this.SetPerFrameCamera(frameIndex, value);
|
|
|
|
this.SetCamera(value);
|
|
}
|
|
|
|
// Execute a single canvas command
|
|
ExecuteCanvasCommand(command: any) {
|
|
switch (command["CommandType"]) {
|
|
case "ConfigureUserInterface":
|
|
this.ConfigureUserInterface(command);
|
|
break;
|
|
|
|
case "SetCamera":
|
|
var value = command["Value"];
|
|
this.SetGlobalCamera(value);
|
|
break;
|
|
|
|
case "SetFocusPoint":
|
|
var focusPoint = this.ParseFocusPoint(command);
|
|
this.SetGlobalFocusPoint(focusPoint);
|
|
break;
|
|
|
|
case "SetShading":
|
|
var value = command["Value"];
|
|
this.SetShading(value);
|
|
break;
|
|
|
|
case "SetLayerSettings":
|
|
var layerSettings = Misc.GetDefault(command, "Value", null);
|
|
if (layerSettings != null)
|
|
this.SetLayerSettings(layerSettings);
|
|
break;
|
|
|
|
default:
|
|
super.ExecuteCanvasCommand(command);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Execute a single frame command
|
|
ExecuteFrameCommand(command: any, frameIndex: number) {
|
|
switch (command["CommandType"]) {
|
|
case "SetFocusPoint":
|
|
var focusPoint = this.ParseFocusPoint(command);
|
|
this.SetPerFrameFocusPoint(frameIndex, focusPoint);
|
|
break;
|
|
|
|
case "SetCamera":
|
|
var value = command["Value"];
|
|
this.SetPerFrameCamera(frameIndex, value);
|
|
break;
|
|
|
|
case "AddMesh":
|
|
var meshId = command["MeshId"];
|
|
var transform = <mat4>Misc.Base64ToFloat32Array(Misc.GetDefault(command, "Transform", mat4.create()));
|
|
this.AddMesh(frameIndex, meshId, transform);
|
|
break;
|
|
|
|
case "RemoveMesh":
|
|
var meshId = command["MeshId"];
|
|
this.RemoveMesh(frameIndex, meshId);
|
|
break;
|
|
|
|
case "SetLayerSettings":
|
|
var layerSettings = Misc.GetDefault(command, "Value", null);
|
|
if (layerSettings != null) {
|
|
this.SetPerFrameLayerSettings(frameIndex, layerSettings);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
super.ExecuteFrameCommand(command, frameIndex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SetCamera(params: Object) {
|
|
if (params == null) return;
|
|
|
|
if (params.hasOwnProperty("WorldToCamera")) {
|
|
this.w2vMatrix = <mat4>Misc.Base64ToFloat32Array(params["WorldToCamera"]);
|
|
this.v2sMatrix = <mat4>Misc.Base64ToFloat32Array(params["Projection"]);
|
|
}
|
|
else {
|
|
console.warn("The legacy SetCamera command is deprecated.")
|
|
var camCenter = <vec3>Misc.Base64ToFloat32Array(params["Center"]);
|
|
var camLookAt = <vec3>Misc.Base64ToFloat32Array(params["LookAt"]);
|
|
var camUpDir = <vec3>Misc.Base64ToFloat32Array(params["UpDir"]);
|
|
this.w2vMatrix = mat4.create();
|
|
this.v2sMatrix = mat4.create();
|
|
mat4.lookAt(this.w2vMatrix, camCenter, camLookAt, camUpDir);
|
|
mat4.perspective(this.v2sMatrix, params["FoVYDegrees"] * DegreesToRadians, this.width / this.height, DefaultNearCropDistance, DefaultFarCropDistance);
|
|
}
|
|
}
|
|
|
|
ResetView() {
|
|
this.onCameraTrack = true;
|
|
var currentFrame = this.currentFrameIndex;
|
|
this.SetCamera(this.frameCameraParams[currentFrame]);
|
|
this.currentFocusPoints[currentFrame] = new Float32Array(this.initialFocusPoints[currentFrame])
|
|
}
|
|
|
|
ToggleLockView(translation: boolean, orientation: boolean) {
|
|
if (translation) this.lockViewXY = !this.lockViewXY;
|
|
if (orientation) this.lockViewOrientation = !this.lockViewOrientation;
|
|
}
|
|
|
|
ToggleOrbitCamera() {
|
|
this.orbitCamera = !this.orbitCamera;
|
|
this.lastOrbitTime = null;
|
|
}
|
|
|
|
SetPerFrameFocusPoint(frameIndex: number, focusPoint: Float32Array) {
|
|
if (focusPoint == null) return;
|
|
|
|
this.initialFocusPoints[frameIndex] = focusPoint; // For reset support
|
|
this.currentFocusPoints[frameIndex] = new Float32Array(focusPoint); // Copy
|
|
}
|
|
|
|
SetPerFrameCamera(frameIndex: number, value: Object) {
|
|
if (value == null) return;
|
|
|
|
this.frameCameraParams[frameIndex] = value; // For reset support
|
|
}
|
|
|
|
SetPerFrameLayerSettings(frameIndex: number, value: Object) {
|
|
if (value == null) return;
|
|
|
|
this.frameLayerSettings[frameIndex] = value;
|
|
}
|
|
|
|
SetGlobalFocusPoint(focusPoint: Float32Array) {
|
|
if (focusPoint == null) return;
|
|
|
|
this.globalFocusPoint = focusPoint;
|
|
for (var frameIndex = 0; frameIndex < this.initialFocusPoints.length; frameIndex++)
|
|
this.SetPerFrameFocusPoint(frameIndex, focusPoint); // Nice side-effect: object is aliased so will stay shared when user moves focus point
|
|
}
|
|
|
|
SetShading(params: Object) {
|
|
// Background color
|
|
this.bgColor = <vec4>Misc.Base64ToFloat32Array(params["BackgroundColor"]);
|
|
|
|
// Lighting
|
|
this.ambientLightColor = <vec3>Misc.Base64ToFloat32Array(params["AmbientLightColor"]);
|
|
this.directionalLightColor = <vec3>Misc.Base64ToFloat32Array(params["DirectionalLightColor"]);
|
|
this.directionalLightDir = <vec3>Misc.Base64ToFloat32Array(params["DirectionalLightDir"]);
|
|
vec3.normalize(this.directionalLightDir, this.directionalLightDir);
|
|
}
|
|
|
|
AllocateFrame() {
|
|
this.frameInstances.push([]);
|
|
this.initialFocusPoints.push(this.globalFocusPoint);
|
|
this.currentFocusPoints.push(this.globalFocusPoint);
|
|
this.frameCameraParams.push(this.globalCameraParams);
|
|
}
|
|
|
|
DeallocateFrame(frameIndex: number) {
|
|
this.frameInstances[frameIndex] = [];
|
|
}
|
|
|
|
AddMesh(frameIndex: number, meshId: string, meshTransform: mat4) {
|
|
// Add the mesh instance
|
|
var instance = new MeshInstance(meshId, meshTransform);
|
|
var instances = this.frameInstances[frameIndex];
|
|
instances.push(instance);
|
|
|
|
let mesh = <Mesh>this.allMeshes[meshId];
|
|
if (mesh.layerId != null && !(mesh.layerId in this.layerSettings)) {
|
|
this.layerSettings[mesh.layerId] = {};
|
|
this.SetLayerSettings(this.layerSettings);
|
|
}
|
|
|
|
// Update display if this is the selected mesh
|
|
if (this.currentFrameIndex == frameIndex) {
|
|
if (!(meshId in this.allMeshes))
|
|
this.SetWarning("Mesh " + meshId + " does not exist!");
|
|
|
|
// Rebuild buffers as needed
|
|
this.PrepareBuffers();
|
|
}
|
|
}
|
|
|
|
RemoveMesh(frameIndex: number, meshId: string) {
|
|
// Add the mesh id
|
|
var instances = this.frameInstances[frameIndex];
|
|
var meshIndex = 0;
|
|
var edited = false;
|
|
while (meshIndex < instances.length) {
|
|
if (instances[meshIndex].meshId == meshId) {
|
|
// Remove
|
|
instances.splice(meshIndex, 1);
|
|
edited = true;
|
|
}
|
|
else {
|
|
meshIndex++;
|
|
}
|
|
}
|
|
|
|
// Update display if this is the selected mesh
|
|
if (edited && this.currentFrameIndex == frameIndex)
|
|
this.PrepareBuffers();
|
|
}
|
|
|
|
NotifyMeshUpdated(meshId: string) {
|
|
if (this.frameInstances.length == 0) return; // No frames
|
|
var currentFrameInstances = this.frameInstances[this.currentFrameIndex];
|
|
for (var instance of currentFrameInstances) {
|
|
if (instance.meshId == meshId) {
|
|
// Clear up any existing buffers
|
|
if (meshId in this.meshBuffers && this.meshBuffers[meshId] != null)
|
|
this.meshBuffers[meshId].Finalize();
|
|
|
|
// Recreate new buffers using updated mesh data
|
|
this.meshBuffers[meshId] = this.GetMeshBuffers(this.allMeshes[meshId]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private GetMeshBuffers(mesh: Mesh) {
|
|
var textureId = mesh.textureId;
|
|
var textureSrc = null;
|
|
if (textureId != null) {
|
|
var textureSrc = this.objectCache.GetObject(textureId);
|
|
if (textureSrc == null)
|
|
return null; // Not able to create buffers yet
|
|
}
|
|
return new WebGLMeshBuffers(this.gl, this.sp, mesh, textureSrc);
|
|
}
|
|
|
|
NotifyTextureUpdated(textureId: string) {
|
|
if (this.frameInstances.length == 0) return; // No frames
|
|
var currentFrameInstances = this.frameInstances[this.currentFrameIndex];
|
|
for (var instance of currentFrameInstances) {
|
|
var meshId = instance.meshId;
|
|
var mesh = <Mesh>this.allMeshes[meshId];
|
|
|
|
if (mesh.textureId == textureId) {
|
|
if (this.meshBuffers[meshId] != null)
|
|
this.meshBuffers[meshId].Finalize(); // Clear up existing buffers
|
|
this.meshBuffers[meshId] = this.GetMeshBuffers(mesh); // Recreate
|
|
}
|
|
}
|
|
}
|
|
|
|
PrepareBuffers(): void {
|
|
super.PrepareBuffers();
|
|
// Get new set of meshIds
|
|
var meshIds: string[] = [];
|
|
if (this.gl != null && this.currentFrameIndex < this.frameInstances.length) // Check for e.g. webgl context lost
|
|
{
|
|
for (var instance of this.frameInstances[this.currentFrameIndex])
|
|
meshIds.push(instance.meshId);
|
|
}
|
|
|
|
// Get set of meshIds that need turning in to buffers
|
|
var meshIdsToAdd: string[] = [];
|
|
for (var meshId of meshIds) {
|
|
var mesh = <Mesh>this.allMeshes[meshId];
|
|
if (mesh != null && this.IsLayerVisible(mesh.layerId) && !(meshId in this.meshBuffers)) // Visible and not already cached
|
|
{
|
|
meshIdsToAdd.push(meshId);
|
|
|
|
// Request texture image be cached
|
|
if (mesh.textureId != null)
|
|
this.objectCache.AddUser(mesh.textureId);
|
|
}
|
|
}
|
|
|
|
// Finalize meshIds not currently used
|
|
for (var meshId in this.meshBuffers) {
|
|
var mesh = <Mesh>this.allMeshes[meshId];
|
|
if (!this.IsLayerVisible(mesh.layerId) || meshIds.indexOf(meshId) == -1) // Invisible or no longer present
|
|
{
|
|
if (meshId in this.meshBuffers) {
|
|
if (this.meshBuffers[meshId] != null)
|
|
this.meshBuffers[meshId].Finalize();
|
|
delete this.meshBuffers[meshId];
|
|
}
|
|
// Remove user of texture image from cache
|
|
if (mesh.textureId != null)
|
|
this.objectCache.RemoveUser(mesh.textureId);
|
|
}
|
|
}
|
|
|
|
// Loop over meshes
|
|
for (var meshId of meshIdsToAdd) {
|
|
var mesh = <Mesh>this.allMeshes[meshId];
|
|
this.meshBuffers[meshId] = this.GetMeshBuffers(mesh);
|
|
}
|
|
}
|
|
|
|
GetCurrentFocusPointInViewSpace() {
|
|
var focusPoint = this.currentFocusPoints[this.currentFrameIndex];
|
|
var focusPointPosition = <vec3>focusPoint.subarray(0, 3);
|
|
var focusPointView = vec3.create();
|
|
vec3.transformMat4(focusPointView, focusPointPosition, this.w2vMatrix);
|
|
return focusPointView;
|
|
}
|
|
|
|
ComputeCameraTwist(point: vec2, event: PointerEvent): number {
|
|
const old = this.pointerCoords.get(event.pointerId);
|
|
if (old == undefined) {
|
|
return 0;
|
|
}
|
|
|
|
// Compute projection of focal point into canvas image
|
|
var focusPointImage = vec3.create();
|
|
vec3.transformMat4(focusPointImage, this.GetCurrentFocusPointInViewSpace(), this.v2sMatrix);
|
|
const focus = vec2.fromValues((focusPointImage[0] + 1.0) * 0.5 * this.width,
|
|
(-focusPointImage[1] + 1.0) * 0.5 * this.height);
|
|
|
|
// Compute rotation angle
|
|
const angleInitial = Math.atan2(old[1] - focus[1], old[0] - focus[0]);
|
|
const angleNew = Math.atan2(point[1] - focus[1], point[0] - focus[0]);
|
|
return angleInitial - angleNew;
|
|
}
|
|
|
|
SetFocusPointPositionFromPixelCoordinates(pixel: vec2) {
|
|
if (this.lockViewXY || this.lockViewOrientation) return;
|
|
|
|
this.pickPoint = pixel;
|
|
this.setFocusToPicked = true;
|
|
}
|
|
|
|
ComputeFocusPointRelativeViewSpaceTranslation(old: vec2, point: vec2) {
|
|
var focusPointZ = Math.abs(this.GetCurrentFocusPointInViewSpace()[2]);
|
|
|
|
var clientRect = this.htmlCanvas.getBoundingClientRect();
|
|
var s2vMatrix = mat4.create();
|
|
mat4.invert(s2vMatrix, this.v2sMatrix);
|
|
|
|
var oldScreen = vec3.fromValues(2.0 * (old[0] / clientRect.width - 0.5), 2.0 * (0.5 - old[1] / clientRect.height), 1.0);
|
|
var oldView = vec3.create();
|
|
vec3.transformMat4(oldView, oldScreen, s2vMatrix);
|
|
vec3.scale(oldView, oldView, focusPointZ / oldView[2]) // Fix z
|
|
|
|
var newScreen = vec3.fromValues(2.0 * (point[0] / clientRect.width - 0.5), 2.0 * (0.5 - point[1] / clientRect.height), 1.0);
|
|
var newView = vec3.create();
|
|
vec3.transformMat4(newView, newScreen, s2vMatrix);
|
|
vec3.scale(newView, newView, focusPointZ / newView[2]) // Fix z
|
|
|
|
return vec3.fromValues(oldView[0] - newView[0], oldView[1] - newView[1], 0.0); // oldView[2] - newView[2] should be == 0.0
|
|
}
|
|
|
|
RotateCamera(rotateAboutX: number, rotateAboutY: number, rotateAboutZ: number) {
|
|
this.onCameraTrack = false;
|
|
var delta = vec3.fromValues(rotateAboutX, rotateAboutY, rotateAboutZ);
|
|
|
|
// Compute focus point location in view space
|
|
var focusPointView = this.GetCurrentFocusPointInViewSpace();
|
|
|
|
// Compute transformation matrix
|
|
var transform = mat4.create();
|
|
|
|
// Translate to be centered on focus point
|
|
mat4.translate(transform, transform, focusPointView);
|
|
|
|
// 3 rotation
|
|
mat4.rotateZ(transform, transform, delta[2]); // Rotate Z
|
|
mat4.rotateY(transform, transform, delta[1]); // Rotate Y
|
|
mat4.rotateX(transform, transform, delta[0]); // Rotate X
|
|
|
|
// Translate back
|
|
vec3.negate(focusPointView, focusPointView);
|
|
mat4.translate(transform, transform, focusPointView);
|
|
|
|
// Apply change to w2v matrix
|
|
mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix);
|
|
}
|
|
|
|
SetCameraRotationalVelocity(rotate: vec2) {
|
|
vec2.copy(this.cameraRotationalVelocity, rotate);
|
|
}
|
|
|
|
SwivelCamera(rotate: vec2) {
|
|
this.onCameraTrack = false;
|
|
|
|
let transform = mat4.create();
|
|
mat4.rotateY(transform, transform, rotate[0]);
|
|
mat4.rotateX(transform, transform, rotate[1]);
|
|
|
|
mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix);
|
|
}
|
|
|
|
MoveCamera(moveForward: number, moveRight: number, moveUp: number) {
|
|
this.onCameraTrack = false;
|
|
|
|
let transform = mat4.create();
|
|
mat4.fromTranslation(transform, [moveRight, moveUp, moveForward])
|
|
mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix);
|
|
}
|
|
|
|
ScaleCamera(factor: number) {
|
|
this.onCameraTrack = false;
|
|
|
|
// Compute focus point location in view space
|
|
var focusPointView = this.GetCurrentFocusPointInViewSpace();
|
|
|
|
// Compute transformation matrix
|
|
var transform = mat4.create();
|
|
|
|
// Translate to be centered on focus point
|
|
mat4.translate(transform, transform, focusPointView);
|
|
|
|
// Apply scale factor
|
|
mat4.scale(transform, transform, [factor, factor, factor]);
|
|
|
|
// Translate back
|
|
vec3.negate(focusPointView, focusPointView);
|
|
mat4.translate(transform, transform, focusPointView);
|
|
|
|
// Apply change to w2v matrix
|
|
mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix);
|
|
}
|
|
|
|
TranslateCamera(delta: vec3) {
|
|
this.onCameraTrack = false;
|
|
|
|
// Compute translation matrix
|
|
var transform = mat4.create();
|
|
mat4.fromTranslation(transform, delta);
|
|
|
|
// Translate
|
|
mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix);
|
|
}
|
|
|
|
IsLayerVisible(layerId: string): boolean {
|
|
var showFilled = this.ShowLayerFilled(layerId) && this.globalFill;
|
|
var showWireframe = this.ShowLayerWireframe(layerId) || (this.ShowLayerFilled(layerId) && this.globalWireframe);
|
|
var opacity = this.GetLayerOpacity(layerId) * this.globalOpacity;
|
|
return (showFilled || showWireframe) && opacity > 0.0;
|
|
}
|
|
|
|
ToggleLayerFilled(index: number) {
|
|
if (index >= this.layerIds.length) {
|
|
return
|
|
}
|
|
|
|
let layerId = this.layerIds[index];
|
|
let filled = this.ShowLayerFilled(layerId);
|
|
this.SetLayerFilled(layerId, !filled);
|
|
}
|
|
|
|
ShowLayerFilled(layerId: string): boolean {
|
|
if (layerId == "<<<GLOBAL>>>")
|
|
return this.globalFill;
|
|
if (layerId == null)
|
|
return true;
|
|
if (!(layerId in this.layerSettings))
|
|
return true;
|
|
if ("filled" in this.layerSettings[layerId] && !this.layerSettings[layerId]["filled"])
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
SetLayerFilled(layerId: string, filled: boolean) {
|
|
if (layerId == null)
|
|
return;
|
|
if (layerId == "<<<GLOBAL>>>") {
|
|
this.globalFill = filled;
|
|
}
|
|
else {
|
|
if (!(layerId in this.layerSettings))
|
|
this.layerSettings[layerId] = {};
|
|
this.layerSettings[layerId]["filled"] = filled;
|
|
}
|
|
|
|
// Prepare buffers and force redraw
|
|
this.PrepareBuffers();
|
|
}
|
|
|
|
ShowLayerWireframe(layerId: string): boolean {
|
|
if (layerId == "<<<GLOBAL>>>")
|
|
return this.globalWireframe;
|
|
if (layerId == null)
|
|
return false;
|
|
if (!(layerId in this.layerSettings))
|
|
return false;
|
|
if ("wireframe" in this.layerSettings[layerId] && this.layerSettings[layerId]["wireframe"])
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
SetLayerWireframe(layerId: string, wireframe: boolean) {
|
|
if (layerId == null)
|
|
return;
|
|
|
|
if (layerId == "<<<GLOBAL>>>") {
|
|
this.globalWireframe = wireframe;
|
|
}
|
|
else {
|
|
if (!(layerId in this.layerSettings))
|
|
this.layerSettings[layerId] = {};
|
|
this.layerSettings[layerId]["wireframe"] = wireframe;
|
|
}
|
|
|
|
// Prepare buffers and force redraw
|
|
this.PrepareBuffers();
|
|
}
|
|
|
|
GetLayerOpacity(layerId: string) {
|
|
if (layerId == "<<<GLOBAL>>>")
|
|
return this.globalOpacity;
|
|
if (layerId == null || !(layerId in this.layerSettings) || !("opacity" in this.layerSettings[layerId]))
|
|
return 1.0; // Opaque
|
|
else
|
|
return <number>this.layerSettings[layerId]["opacity"];
|
|
}
|
|
|
|
SetLayerOpacity(layerId: string, opacity: number) {
|
|
if (layerId == null)
|
|
return;
|
|
|
|
if (layerId == "<<<GLOBAL>>>") {
|
|
this.globalOpacity = opacity;
|
|
}
|
|
else {
|
|
if (!(layerId in this.layerSettings))
|
|
this.layerSettings[layerId] = {};
|
|
this.layerSettings[layerId]["opacity"] = opacity;
|
|
}
|
|
|
|
// Prepare buffers and force redraw
|
|
this.PrepareBuffers();
|
|
}
|
|
|
|
GetLayerRenderOrder(layerId: string) {
|
|
if (layerId == null || !(layerId in this.layerSettings) || !("renderOrder" in this.layerSettings[layerId]))
|
|
return 1e3; // Last layer - bit of a hack
|
|
else
|
|
return <number>this.layerSettings[layerId]["renderOrder"];
|
|
}
|
|
|
|
AllMeshBuffersReady() {
|
|
if (this.frameInstances.length > 0) {
|
|
for (var instance of this.frameInstances[this.currentFrameIndex]) {
|
|
// Look up mesh
|
|
var meshId = instance.meshId;
|
|
var mesh = <Mesh>this.allMeshes[meshId];
|
|
|
|
// Check mesh layer visibility
|
|
if (mesh == null || !this.IsLayerVisible(mesh.layerId))
|
|
continue;
|
|
|
|
// Check buffer is ready
|
|
if (this.meshBuffers[meshId] == null)
|
|
return false; // Don't draw anything until all buffers ready (to prevent possible flicker)
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Override base class implementation to deal with focus point lock
|
|
ShowFrame(frameIndex: number) {
|
|
frameIndex = Math.min(frameIndex, this.frameIds.length - 1); // Just in case
|
|
|
|
// Get old and new focus points
|
|
var oldFocusPoint = this.currentFocusPoints[this.currentFrameIndex];
|
|
var newFocusPoint = this.currentFocusPoints[frameIndex];
|
|
|
|
if (this.frameLayerSettings[frameIndex]) {
|
|
let layerSettings = this.frameLayerSettings[frameIndex];
|
|
for (let layerId in layerSettings) {
|
|
this.SetLayerFilled(layerId, layerSettings[layerId]["filled"]);
|
|
this.SetLayerWireframe(layerId, layerSettings[layerId]["wireframe"]);
|
|
this.SetLayerOpacity(layerId, layerSettings[layerId]["opacity"]);
|
|
if ("renderOrder" in layerSettings) {
|
|
this.layerSettings[layerId]["renderOrder"] = layerSettings[layerId]["renderOrder"]
|
|
}
|
|
|
|
this.PrepareBuffers();
|
|
}
|
|
}
|
|
|
|
var w2vMatrixOld = mat4.create();
|
|
mat4.copy(w2vMatrixOld, this.w2vMatrix);
|
|
|
|
// Lock camera rotation to focus point?
|
|
if (this.lockViewOrientation && oldFocusPoint.length == 6 && newFocusPoint.length == 6) // Does the focus point contain an axis angle rotation?
|
|
{
|
|
var aaToMat = (axisAngle: vec3) => { var angle = vec3.length(axisAngle); var axis = vec3.create(); vec3.normalize(axis, axisAngle); var rotation = mat4.create(); mat4.fromRotation(rotation, angle, axis); return rotation; }
|
|
|
|
var oldRotation = aaToMat(<vec3>oldFocusPoint.subarray(3));
|
|
var newRotation = aaToMat(<vec3>newFocusPoint.subarray(3));
|
|
|
|
// Compute delta
|
|
var deltaRotation = oldRotation;
|
|
mat4.invert(newRotation, newRotation);
|
|
mat4.multiply(deltaRotation, deltaRotation, newRotation);
|
|
|
|
// Apply delta
|
|
mat4.multiply(this.w2vMatrix, this.w2vMatrix, deltaRotation);
|
|
}
|
|
|
|
// Lock camera translation to focus point?
|
|
if (this.lockViewXY || this.lockViewOrientation) {
|
|
var oldFPView = vec3.create();
|
|
vec3.transformMat4(oldFPView, <vec3>oldFocusPoint.subarray(0, 3), w2vMatrixOld);
|
|
|
|
var newFPView = vec3.create();
|
|
vec3.transformMat4(newFPView, <vec3>newFocusPoint.subarray(0, 3), this.w2vMatrix);
|
|
|
|
// Compute delta
|
|
var delta = vec3.create();
|
|
vec3.subtract(delta, oldFPView, newFPView);
|
|
|
|
// Apply delta
|
|
var deltaMat = mat4.create();
|
|
mat4.fromTranslation(deltaMat, delta);
|
|
mat4.multiply(this.w2vMatrix, deltaMat, this.w2vMatrix);
|
|
}
|
|
|
|
// Call base class
|
|
return super.ShowFrame(frameIndex);
|
|
}
|
|
|
|
Render() {
|
|
// Check for gl to be ready
|
|
if (this.gl == null)
|
|
return;
|
|
|
|
// Wait until all resources are ready
|
|
if (!this.AllMeshBuffersReady())
|
|
return;
|
|
|
|
if (this.firstPerson) {
|
|
this.MoveCamera(this.cameraVelocity[0], this.cameraVelocity[1], this.cameraVelocity[2]);
|
|
if (this.cameraRotationalVelocity[0] != 0 || this.cameraRotationalVelocity[1] != 0) {
|
|
this.SwivelCamera(this.cameraRotationalVelocity);
|
|
}
|
|
}
|
|
|
|
this.gl.clearColor(this.bgColor[0], this.bgColor[1], this.bgColor[2], this.bgColor[3]);
|
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); // Applies to whole canvas
|
|
this.gl.viewport(0, 0, this.htmlCanvas.width, this.htmlCanvas.height);
|
|
this.RenderViewport();
|
|
}
|
|
|
|
// Render scene method with given w2v and v2s matrices
|
|
RenderViewport() {
|
|
const gl = this.gl;
|
|
const v2sMatrix = this.v2sMatrix;
|
|
|
|
if (this.onCameraTrack) {
|
|
this.SetCamera(this.frameCameraParams[this.currentFrameIndex])
|
|
}
|
|
|
|
// Get copy of w2vMatrix
|
|
var w2vMatrix = mat4.create();
|
|
mat4.copy(w2vMatrix, this.w2vMatrix);
|
|
|
|
|
|
// Set up rendering parameters
|
|
gl.useProgram(this.sp.program);
|
|
gl.enable(gl.DEPTH_TEST);
|
|
gl.cullFace(gl.BACK);
|
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
|
|
// Set lighting
|
|
gl.uniform3fv(this.sp.ambientLightColorPtr, this.ambientLightColor);
|
|
gl.uniform3fv(this.sp.directionalLightColorPtr, this.directionalLightColor);
|
|
gl.uniform3fv(this.sp.directionalLightDirPtr, this.directionalLightDir);
|
|
|
|
// Get list of meshes to draw
|
|
let opaqueMeshData: MeshData[] = [];
|
|
let transparentMeshData: MeshData[] = [];
|
|
if (this.frameInstances.length > 0) {
|
|
var instances = this.frameInstances[this.currentFrameIndex];
|
|
|
|
for (var meshInstance of instances) // Loop over mesh instances
|
|
{
|
|
// Look up mesh
|
|
var meshId = meshInstance.meshId;
|
|
var mesh = <Mesh>this.allMeshes[meshId];
|
|
if (mesh == null) continue;
|
|
|
|
// Check mesh layer visibility and opacity
|
|
var filled = this.ShowLayerFilled(mesh.layerId) && this.globalFill;
|
|
var wireframe = this.ShowLayerWireframe(mesh.layerId) || (this.ShowLayerFilled(mesh.layerId) && this.globalWireframe);
|
|
var opacity = this.GetLayerOpacity(mesh.layerId) * this.globalOpacity;
|
|
if (!(filled || wireframe) || opacity == 0.0)
|
|
continue;
|
|
|
|
// Get buffer
|
|
var buffer = <WebGLMeshBuffers>this.meshBuffers[meshId];
|
|
if (buffer == null) continue;
|
|
|
|
// Compute per-mesh transform
|
|
var m2vMatrix = mat4.create();
|
|
if (mesh.cameraSpace)
|
|
mat4.copy(m2vMatrix, meshInstance.transform);
|
|
else
|
|
mat4.multiply(m2vMatrix, w2vMatrix, meshInstance.transform);
|
|
|
|
// Set properties for labels
|
|
if (mesh.isLabel) {
|
|
var textureSrc = this.objectCache.GetObject(mesh.textureId);
|
|
if (textureSrc != null) {
|
|
buffer.labelWidthNormalized = textureSrc.desiredWidthPixels / this.htmlCanvas.width;
|
|
buffer.labelHeightNormalized = textureSrc.desiredHeightPixels / this.htmlCanvas.height;
|
|
|
|
buffer.labelTranslateScreenX = textureSrc.proportionTranslateScreenX * buffer.labelWidthNormalized;
|
|
buffer.labelTranslateScreenY = textureSrc.proportionTranslateScreenY * buffer.labelHeightNormalized;
|
|
buffer.labelTranslateWorldX = Misc.Sign(textureSrc.proportionTranslateScreenX) * textureSrc.offsetDistance;
|
|
buffer.labelTranslateWorldY = Misc.Sign(textureSrc.proportionTranslateScreenY) * textureSrc.offsetDistance;
|
|
}
|
|
}
|
|
|
|
// Is this transparent or not?
|
|
if (opacity < 1.0 || (mesh.textureId != null && (mesh.useTextureAlpha || mesh.isLabel))) {
|
|
// Transform center of mass to view coordinates to get z-ordering
|
|
var viewCoM = vec3.create();
|
|
vec3.transformMat4(viewCoM, mesh.centerOfMass, m2vMatrix);
|
|
var viewDistance = viewCoM[2];
|
|
|
|
// Additionally sort using render order which overrides view distance
|
|
var renderOrder = this.GetLayerRenderOrder(mesh.layerId);
|
|
viewDistance += renderOrder * 1e4; // 1e4 is hack to force rendering in batches according to render order. Will break down if viewDistance can be larger than 1e4.
|
|
|
|
transparentMeshData.push(new MeshData(mesh, meshInstance.transform, m2vMatrix, buffer, opacity, filled, wireframe, viewDistance));
|
|
}
|
|
else {
|
|
opaqueMeshData.push(new MeshData(mesh, meshInstance.transform, m2vMatrix, buffer, 1.0, filled, wireframe, 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw opaque meshes
|
|
gl.depthMask(true);
|
|
gl.disable(gl.BLEND);
|
|
|
|
opaqueMeshData.forEach(data => data.Render(gl, v2sMatrix));
|
|
|
|
// Draw transparent meshes, sorted by decreasing distance from camera
|
|
gl.depthMask(false);
|
|
gl.enable(gl.BLEND);
|
|
var sortedTransparentMeshData = transparentMeshData.sort((a, b) => a.viewDistance - b.viewDistance);
|
|
sortedTransparentMeshData.forEach(data => data.Render(gl, v2sMatrix));
|
|
|
|
// Show focus point
|
|
if (this.showFocusPoint) {
|
|
gl.disable(gl.DEPTH_TEST);
|
|
var focusPoint = this.currentFocusPoints[this.currentFrameIndex];
|
|
var focusPointPosition = <vec3>focusPoint.subarray(0, 3);
|
|
var focusPointView = vec3.create();
|
|
vec3.transformMat4(focusPointView, focusPointPosition, w2vMatrix);
|
|
var size = 0.02 * Math.abs(focusPointView[2]);
|
|
mat4.fromRotationTranslationScale(this.focusPointMeshBuffer.m2wMatrix, quat.create(), focusPointPosition, <vec3>new Float32Array([size, size, size]));
|
|
this.focusPointMeshBuffer.RenderBuffer(v2sMatrix, w2vMatrix, 1.0, true, false);
|
|
}
|
|
|
|
// If enabled, orbit the camera
|
|
if (this.orbitCamera) {
|
|
// Adjust for any framerate changes
|
|
var now = new Date();
|
|
if (this.lastOrbitTime != null) {
|
|
var deltaTime = now.getTime() - this.lastOrbitTime.getTime();
|
|
this.RotateCamera(0.0, 0.0025 * deltaTime, 0.0); // Rotate about y axis
|
|
}
|
|
this.lastOrbitTime = now;
|
|
}
|
|
|
|
if (this.setFocusToPicked && opaqueMeshData.length > 0) {
|
|
let buffers: [WebGLMeshBuffers, mat4][] = [];
|
|
for (let i = 0; i < opaqueMeshData.length; i++) {
|
|
let meshData = opaqueMeshData[i];
|
|
if (meshData.mesh.cameraSpace) {
|
|
continue;
|
|
}
|
|
|
|
meshData.buffer.id = i + 1;
|
|
buffers.push([meshData.buffer, meshData.m2vMatrix]);
|
|
}
|
|
const picked = this.meshPicker.Pick(gl, buffers, this.pickPoint, v2sMatrix)
|
|
if (picked == 0) {
|
|
var focusPointView = this.GetCurrentFocusPointInViewSpace();
|
|
|
|
var s2vMatrix = mat4.create();
|
|
var v2wMatrix = mat4.create();
|
|
mat4.invert(s2vMatrix, this.v2sMatrix);
|
|
mat4.invert(v2wMatrix, this.w2vMatrix);
|
|
|
|
// Convert from pixel to screen coordinates
|
|
var clientRect = this.htmlCanvas.getBoundingClientRect();
|
|
var screen = vec3.fromValues(2.0 * (this.pickPoint[0] / clientRect.width - 0.5),
|
|
2.0 * (0.5 - this.pickPoint[1] / clientRect.height), 1.0);
|
|
|
|
// Convert from screen to view coordinates
|
|
var view = vec3.create();
|
|
vec3.transformMat4(view, screen, s2vMatrix);
|
|
vec3.scale(view, view, focusPointView[2] / view[2]) // Fix z value to existing focus point z value
|
|
|
|
// Convert from view to world coordinates
|
|
var focusPoint = this.currentFocusPoints[this.currentFrameIndex];
|
|
var focusPointPosition = <vec3>focusPoint.subarray(0, 3);
|
|
vec3.transformMat4(focusPointPosition, view, v2wMatrix);
|
|
} else {
|
|
const pickedCentroid = opaqueMeshData[picked - 1].ComputeCentroid();
|
|
var focusPoint = this.currentFocusPoints[this.currentFrameIndex];
|
|
focusPoint[0] = pickedCentroid[0];
|
|
focusPoint[1] = pickedCentroid[1];
|
|
focusPoint[2] = pickedCentroid[2];
|
|
}
|
|
|
|
this.setFocusToPicked = false;
|
|
}
|
|
}
|
|
}
|