diff --git a/main.js b/main.js index 5b58094..fd2f81b 100644 --- a/main.js +++ b/main.js @@ -259,14 +259,22 @@ let tableScaleMatrix = Mat4.create() let tableTranslateMatrix = Mat4.create() let initialTableRotation = Mat4.create() -function mapToSphere(mouseX, mouseY, canvas) { - const radius = 3 +function toClipSpace(canvas, x, y) { const res = Math.max(canvas.getBoundingClientRect().width, canvas.getBoundingClientRect().height) - 1 - const x = (2 * (mouseX - canvas.getBoundingClientRect().x) - canvas.getBoundingClientRect().width - 1) / res - const y = (2 * (mouseY - canvas.getBoundingClientRect().y) - canvas.getBoundingClientRect().height - 1) / res + return [ + (2 * (x - canvas.getBoundingClientRect().x) - canvas.getBoundingClientRect().width - 1) / res, + (2 * (y - canvas.getBoundingClientRect().y) - canvas.getBoundingClientRect().height - 1) / res + ] +} + +function mapToSphere(mouseX, mouseY, canvas) { + const xy = toClipSpace(canvas, mouseX, mouseY) + const x = xy[0] + const y = xy[1] const lengthSquared = x * x + y * y - console.log(lengthSquared) + const radius = 3 + // Map to sphere when x^2 + y^2 <= r^2 / 2 - otherwise map to the hyperbolic function f(x,y) = (r^2 / 2) / sqrt(x^2 + y^2). if (2 * lengthSquared <= radius * radius) { return new Vector3(x, y, Math.sqrt((radius * radius) - lengthSquared)) @@ -362,7 +370,9 @@ function initialize3DTableGraphic(moodyReport) { return } cumulativeZoomFactor *= zoomFactor + // TODO: Keep zoom centered at mouse cursor. + // See: https://stackoverflow.com/questions/57892652/webgl-2d-camera-zoom-to-mouse-point tableScaleMatrix.scale([zoomFactor, zoomFactor, zoomFactor]) } @@ -469,7 +479,7 @@ function initialize3DTableGraphic(moodyReport) { requestAnimationFrame(render) } -let then = 0 +let then = 0 const frameTimes = [] let frameCursor = 0 let numFrames = 0 @@ -676,20 +686,26 @@ const fsSource = `#version 300 es ` function getBuffers(gl, moodyReport, zMultiplier) { - const positionBuffer = getPositionBuffer(gl, moodyReport, zMultiplier) - const lineColorBuffer = getColorBuffer(gl, moodyReport, positionBuffer.triangleVertices) + const buffers = getNonColorBuffers(gl, moodyReport, zMultiplier) + const lineColorBuffer = getColorBuffer(gl, moodyReport, buffers.triangleVertices) + + const areEqual = (x, y, z, w, v) => x === y && y === z && z === w && w === v + console.assert(areEqual(buffers.positions.length / 3, buffers.normals.length / 3, buffers.textureCoordinates.length / 2, buffers.types.length, lineColorBuffer.colors.length / 4), + `All buffers must be of the same size (per vertex) but were not. Position buffer: ${buffers.positions.length / 3}, + Normal buffer: ${buffers.normals.length / 3}, Texture buffer: ${buffers.textureCoordinates.length / 2}, Type buffer: ${buffers.types.length}, + Color buffer: ${lineColorBuffer.colors.length / 4}`) return { - positionBuffer: positionBuffer.positionBuffer, - normalBuffer: positionBuffer.normalBuffer, - triangleVertices: positionBuffer.triangleVertices, - textureBuffer: positionBuffer.textureBuffer, - typeBuffer: positionBuffer.typeBuffer, - lineColors: lineColorBuffer, + positionBuffer: buffers.positionBuffer, + normalBuffer: buffers.normalBuffer, + triangleVertices: buffers.triangleVertices, + textureBuffer: buffers.textureBuffer, + typeBuffer: buffers.typeBuffer, + lineColors: lineColorBuffer.colorBuffer, } } -function getPositionBuffer(gl, moodyReport, zMultiplier) { +function getNonColorBuffers(gl, moodyReport, zMultiplier) { const vertices = moodyReport.vertices(zMultiplier).map(vertex => new Vertex(vertex[0], vertex[1], vertex[2])).flat(1) const triangulation = bowyerWatson(vertices) @@ -711,7 +727,6 @@ function getPositionBuffer(gl, moodyReport, zMultiplier) { 0, 0, 0, 0, 0, axisSize) ) - console.log("Position buffer size: " + positions.length / 3) const positionBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW) @@ -729,7 +744,6 @@ function getPositionBuffer(gl, moodyReport, zMultiplier) { triangle.surfaceNormal().x / length, triangle.surfaceNormal().y / length, triangle.surfaceNormal().z / length] }).flat(1) const normals = new Float32Array(lineNormals.concat(triangleNormals).concat(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - console.log("Normals buffer size: " + normals.length / 3) const normalBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer) gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW) @@ -746,13 +760,16 @@ function getPositionBuffer(gl, moodyReport, zMultiplier) { // Map [minX, maxX] => [0, 1] and [minY, maxY] => [0, 1] // (val - A) * (b - a) / (B - A) + a const triangleTextureCoords = triangulation.map((triangle) => [ - ((triangle.v0.x - boundingBoxCache[zMultiplier].maxX) * (numRepeatsX + 1)) / (boundingBoxCache[zMultiplier].maxX - boundingBoxCache[zMultiplier].minX), ((triangle.v0.y - boundingBoxCache[zMultiplier].maxY) * (numRepeatsY + 1)) / (boundingBoxCache[zMultiplier].maxY - boundingBoxCache[zMultiplier].minY), - ((triangle.v1.x - boundingBoxCache[zMultiplier].maxX) * (numRepeatsX + 1)) / (boundingBoxCache[zMultiplier].maxX - boundingBoxCache[zMultiplier].maxX), ((triangle.v1.y - boundingBoxCache[zMultiplier].maxY) * (numRepeatsY + 1)) / (boundingBoxCache[zMultiplier].maxY - boundingBoxCache[zMultiplier].minY), - ((triangle.v2.x - boundingBoxCache[zMultiplier].maxX) * (numRepeatsX + 1)) / (boundingBoxCache[zMultiplier].maxX - boundingBoxCache[zMultiplier].minX), ((triangle.v2.y - boundingBoxCache[zMultiplier].maxY) * (numRepeatsY + 1)) / (boundingBoxCache[zMultiplier].maxY - boundingBoxCache[zMultiplier].minY),] + ((triangle.v0.x - boundingBoxCache[zMultiplier].maxX) * (numRepeatsX + 1)) / (boundingBoxCache[zMultiplier].maxX - boundingBoxCache[zMultiplier].minX), + ((triangle.v0.y - boundingBoxCache[zMultiplier].maxY) * (numRepeatsY + 1)) / (boundingBoxCache[zMultiplier].maxY - boundingBoxCache[zMultiplier].minY), + ((triangle.v1.x - boundingBoxCache[zMultiplier].maxX) * (numRepeatsX + 1)) / (boundingBoxCache[zMultiplier].maxX - boundingBoxCache[zMultiplier].maxX), + ((triangle.v1.y - boundingBoxCache[zMultiplier].maxY) * (numRepeatsY + 1)) / (boundingBoxCache[zMultiplier].maxY - boundingBoxCache[zMultiplier].minY), + ((triangle.v2.x - boundingBoxCache[zMultiplier].maxX) * (numRepeatsX + 1)) / (boundingBoxCache[zMultiplier].maxX - boundingBoxCache[zMultiplier].minX), + ((triangle.v2.y - boundingBoxCache[zMultiplier].maxY) * (numRepeatsY + 1)) / (boundingBoxCache[zMultiplier].maxY - boundingBoxCache[zMultiplier].minY), + ] ).flat(1) const textureCoordinates = new Float32Array(lineTextureCoords.concat(triangleTextureCoords).concat(0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0)) - console.log("Texture buffer size: " + textureCoordinates.length / 2) const textureBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer) gl.bufferData(gl.ARRAY_BUFFER, textureCoordinates, gl.STATIC_DRAW) @@ -760,13 +777,15 @@ function getPositionBuffer(gl, moodyReport, zMultiplier) { const types = new Float32Array( moodyReport.vertices(zMultiplier).map(v => [0.0]).flat(1) // "Union jack" colored lines have type "0.0" .concat(triangulation.map(_ => [1.0, 1.0, 1.0]).flat(1)) // Table vertices have type "1.0" - .concat(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) // Axes vertices have type "0.0" - console.log("Type buffer size: " + types.length) + .concat(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) // Axis vertices have type "0.0" + const typeBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, typeBuffer) gl.bufferData(gl.ARRAY_BUFFER, types, gl.STATIC_DRAW) - return { positionBuffer: positionBuffer, normalBuffer: normalBuffer, textureBuffer: textureBuffer, typeBuffer: typeBuffer, triangleVertices: triangulatedVertices } + return { positionBuffer: positionBuffer, positions: positions, normalBuffer: normalBuffer, normals: normals, + textureBuffer: textureBuffer, textureCoordinates: textureCoordinates, typeBuffer: typeBuffer, types: types, + triangleVertices: triangulatedVertices } } function getColorBuffer(gl, moodyReport, triangleVertices) { @@ -784,16 +803,15 @@ function getColorBuffer(gl, moodyReport, triangleVertices) { .concat(new Array(moodyReport.horizontalCenterTable.numStations).fill([0.0, 0.749019607843137, 0.8470588235294118, 1.0]).flat(1)) .concat(new Array(moodyReport.verticalCenterTable.numStations).fill([0.607843137254902, 0.1568627450980392, 0.6862745098039216, 1.0]).flat(1)) .concat(colorMappedZValues.flat(1)) // Add color mapped colors for the triangles z-value. - .concat(1, 1, 1, 1, 1, 0, 0, 1, - 1, 1, 1, 1, 0, 1, 0, 1, - 1, 1, 1, 1, 0, 0, 1, 1) // Add colors for axes lines. + .concat(1, 1, 1, 1, 1, 0, 0, 1, + 1, 1, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 0, 0, 1, 1) // Add colors for axes lines. - console.log("Color buffer size: " + colors.length / 4) const colorBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW) - return colorBuffer + return { colorBuffer: colorBuffer, colors: colors } } function setPositionAttribute(gl, buffers, programInfo) { diff --git a/moody.js b/moody.js index cd54c68..5c98089 100644 --- a/moody.js +++ b/moody.js @@ -61,6 +61,9 @@ class Table { surfacePlateWidthInches surfacePlateDiagonalInches suggestedDiagonalInset + plateDiagonalAngle + xInset + yInset constructor(lineSegment, arcSecondData, reflectorFootSpacingInches, surfacePlateHeightInches, surfacePlateWidthInches, suggestedDiagonalInset) { this.lineSegment = lineSegment @@ -69,6 +72,9 @@ class Table { this.surfacePlateHeightInches = surfacePlateHeightInches this.surfacePlateWidthInches = surfacePlateWidthInches this.suggestedDiagonalInset = suggestedDiagonalInset + this.plateDiagonalAngle = Math.atan(this.surfacePlateWidthInches / this.surfacePlateHeightInches) + this.xInset = this.suggestedDiagonalInset * Math.sin(this.plateDiagonalAngle) + this.yInset = this.suggestedDiagonalInset * Math.cos(this.plateDiagonalAngle) } printDebug() { @@ -177,25 +183,22 @@ class DiagonalTable extends Table { // We could make it so we also calculate the suggestedHorizontal and suggestedVertical insets. Right now we are assuming the reflectorFootSpacing evenly divides into the plate width/height - but what if it is non-standard? // (0,0) origin is bottom left corner of surface plate. vertices(zMultiplier = 1) { - const plateDiagonalAngle = Math.atan(this.surfacePlateWidthInches / this.surfacePlateHeightInches) - const xInset = this.suggestedDiagonalInset * Math.sin(plateDiagonalAngle) - const yInset = this.suggestedDiagonalInset * Math.cos(plateDiagonalAngle) if (this.lineSegment.start == Direction.Northwest) { // Top-Starting Diagonal // y = -(table_width/table_height) * x + table_height // sin(theta) = y / reflector_foot_spacing // tan(theta) = y / x return this.displacementsFromBaseLineLinear.map((z, i) => { - const x = xInset + (i * Math.sin(plateDiagonalAngle) * this.reflectorFootSpacingInches) - const y = this.surfacePlateHeightInches - yInset - (i * Math.cos(plateDiagonalAngle) * this.reflectorFootSpacingInches) + const x = this.xInset + (i * Math.sin(this.plateDiagonalAngle) * this.reflectorFootSpacingInches) + const y = this.surfacePlateHeightInches - this.yInset - (i * Math.cos(this.plateDiagonalAngle) * this.reflectorFootSpacingInches) return [x, y, z * zMultiplier] }) } else { // Bottom starting diagonal // y = (table_width/table_height) * x return this.displacementsFromBaseLineLinear.map((z, i) => { - const x = this.surfacePlateWidthInches - xInset - (i * Math.sin(plateDiagonalAngle) * this.reflectorFootSpacingInches) - const y = this.surfacePlateHeightInches - yInset - (i * Math.cos(plateDiagonalAngle) * this.reflectorFootSpacingInches) + const x = this.surfacePlateWidthInches - this.xInset - (i * Math.sin(this.plateDiagonalAngle) * this.reflectorFootSpacingInches) + const y = this.surfacePlateHeightInches - this.yInset - (i * Math.cos(this.plateDiagonalAngle) * this.reflectorFootSpacingInches) return [x, y, z * zMultiplier] }) } @@ -226,21 +229,18 @@ class PerimeterTable extends Table { // (0,0) origin is bottom left corner of surface plate. vertices(zMultiplier = 1) { - const plateDiagonalAngle = Math.atan(this.surfacePlateWidthInches / this.surfacePlateHeightInches) - const xInset = this.suggestedDiagonalInset * Math.sin(plateDiagonalAngle) - const yInset = this.suggestedDiagonalInset * Math.cos(plateDiagonalAngle) if (this.lineSegment.start == Direction.Northeast && this.lineSegment.end == Direction.Northwest) { // North Perimeter - return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches - xInset - (i * this.reflectorFootSpacingInches), this.surfacePlateHeightInches - yInset, z * zMultiplier]) + return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches - this.xInset - (i * this.reflectorFootSpacingInches), this.surfacePlateHeightInches - this.yInset, z * zMultiplier]) } else if (this.lineSegment.start == Direction.Northeast && this.lineSegment.end == Direction.Southeast) { // East Perimeter - return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches - xInset, this.surfacePlateHeightInches - yInset - (i * this.reflectorFootSpacingInches), z * zMultiplier]) + return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches - this.xInset, this.surfacePlateHeightInches - this.yInset - (i * this.reflectorFootSpacingInches), z * zMultiplier]) } else if (this.lineSegment.start == Direction.Southeast && this.lineSegment.end == Direction.Southwest) { // South Perimeter - return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches - xInset - (i * this.reflectorFootSpacingInches), yInset, z * zMultiplier]) + return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches - this.xInset - (i * this.reflectorFootSpacingInches), this.yInset, z * zMultiplier]) } else if (this.lineSegment.start == Direction.Northwest && this.lineSegment.end == Direction.Southwest) { // West Perimeter - return this.displacementsFromBaseLineLinear.map((z, i) => [xInset, this.surfacePlateHeightInches - yInset - (i * this.reflectorFootSpacingInches), z * zMultiplier]) + return this.displacementsFromBaseLineLinear.map((z, i) => [this.xInset, this.surfacePlateHeightInches - this.yInset - (i * this.reflectorFootSpacingInches), z * zMultiplier]) } } } @@ -308,15 +308,12 @@ class CenterTable extends Table { // (0,0) origin is bottom left corner of surface plate. vertices(zMultiplier = 1) { - const plateDiagonalAngle = Math.atan(this.surfacePlateWidthInches / this.surfacePlateHeightInches) - const xInset = this.suggestedDiagonalInset * Math.sin(plateDiagonalAngle) - const yInset = this.suggestedDiagonalInset * Math.cos(plateDiagonalAngle) if (this.lineSegment.start == Direction.East) { // Horizontal Center Line - return this.displacementsFromBaseLineLinear.map((z, i) => { return [this.surfacePlateWidthInches - xInset - (i * this.reflectorFootSpacingInches), this.surfacePlateHeightInches / 2, z * zMultiplier] }) + return this.displacementsFromBaseLineLinear.map((z, i) => { return [this.surfacePlateWidthInches - this.xInset - (i * this.reflectorFootSpacingInches), this.surfacePlateHeightInches / 2, z * zMultiplier] }) } else if (this.lineSegment.start == Direction.North) { // Vertical Center Line - return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches / 2, this.surfacePlateHeightInches - yInset - (i * this.reflectorFootSpacingInches), z * zMultiplier]) + return this.displacementsFromBaseLineLinear.map((z, i) => [this.surfacePlateWidthInches / 2, this.surfacePlateHeightInches - this.yInset - (i * this.reflectorFootSpacingInches), z * zMultiplier]) } } @@ -420,8 +417,8 @@ class MoodyReport { this.southPerimeterTable.midStationValue(this.southPerimeterTable.displacementsFromDatumPlane), verticalCenterReadings, surfacePlate.reflectorFootSpacingInches, surfacePlate.surfacePlateHeightInches, surfacePlate.surfacePlateWidthInches, surfacePlate.suggestedDiagonalInset) - // Moody uses North Perimeter Line as the example, for the other one's it requires a bit of thinking as to which - // diagonal line values need to be copied to the other perimeter lines for consistency. + // Moody uses the North Perimeter Line as an example; for the other one's, it requires a bit of thinking as to which + // diagonal line values need to be copied to the other perimeter lines for consistency (with the example). // Copy value in Column #6 of NE end of NE-SW diagonal in to Columns #5 and #6 for perimeter lines. this.northPerimeterTable.cumulativeCorrectionFactors.unshift(this.topStartingDiagonalTable.displacementsFromDatumPlane[0]) @@ -442,7 +439,7 @@ class MoodyReport { this.westPerimeterTable.displacementsFromDatumPlane.unshift(this.topStartingDiagonalTable.displacementsFromDatumPlane[0]) this.westPerimeterTable.displacementsFromDatumPlane.unshift(this.bottomStartingDiagonalTable.displacementsFromDatumPlane.at(-1)) - // Moody uses the Horizontal Center Line as the example, for the other one (Vertical Center Line), it requires a bit of + // Moody uses the Horizontal Center Line as an example; for the other one (Vertical Center Line), it requires a bit of // thinking as to which perimeter line values need to be copied to the other center line for consistency. // Enter the value for the midpoint of the east perimeter line opposite the first station in Columns #5 and #6.