From 85170e46dd88743581aacf0f1c088f78f228c07b Mon Sep 17 00:00:00 2001 From: Tibor Udvari Date: Wed, 4 Sep 2024 16:30:32 +0200 Subject: [PATCH] Fix to work with p5 v1.10.0 * Bump p5 version to 1.10.0 * Monkey patch p5 camera copy for linePerspective * Monkey patch p5 line vert shader that doesn't work with small coors * Set the newly available modelMatrix to fix rendering * Added a smaller default line width for linePerspective mode, assuming folks would be using real world coordinates --- package-lock.json | 14 +- package.json | 2 +- src/app.js | 1 + src/p5xr/core/p5overrides.js | 45 +++ src/p5xr/core/p5xr.js | 18 +- src/p5xr/core/p5xrViewer.js | 52 ++- src/p5xr/shaders/lineShader.js | 340 ++++++++++++++++++ src/p5xr/utilities/versionComparator.js | 19 + .../unit/p5xr/utilities/versionComparator.js | 23 ++ 9 files changed, 485 insertions(+), 29 deletions(-) create mode 100644 src/p5xr/core/p5overrides.js create mode 100644 src/p5xr/shaders/lineShader.js create mode 100644 src/p5xr/utilities/versionComparator.js create mode 100644 tests/unit/p5xr/utilities/versionComparator.js diff --git a/package-lock.json b/package-lock.json index 5bcb803..084a4c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "karma-sinon": "^1.0.5", "mocha": "^10.2.0", "openssl": "^2.0.0", - "p5": "~1.9.2", + "p5": "~1.10.0", "parcel": "^2.6.2", "sinon": "^7.5.0", "standard-version": "^9.5.0", @@ -14019,9 +14019,9 @@ } }, "node_modules/p5": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/p5/-/p5-1.9.4.tgz", - "integrity": "sha512-dhiZ9mvXx5pm8eRwml34xbeUwce4uS9Q2za0YOHg2p97N9iNAb5hTIHAt77CHKHXAh6A16u/oalz5egRfTyFWw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/p5/-/p5-1.10.0.tgz", + "integrity": "sha512-6cWYBFhnZz7jNC6p1VWvlt3QReMqrRSmO90bgECQIKB9oko2w/sKrOAVMyei5tjIzSYcSY0JHy+BRtSAWq24jQ==", "dev": true, "license": "LGPL-2.1" }, @@ -28553,9 +28553,9 @@ "dev": true }, "p5": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/p5/-/p5-1.9.4.tgz", - "integrity": "sha512-dhiZ9mvXx5pm8eRwml34xbeUwce4uS9Q2za0YOHg2p97N9iNAb5hTIHAt77CHKHXAh6A16u/oalz5egRfTyFWw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/p5/-/p5-1.10.0.tgz", + "integrity": "sha512-6cWYBFhnZz7jNC6p1VWvlt3QReMqrRSmO90bgECQIKB9oko2w/sKrOAVMyei5tjIzSYcSY0JHy+BRtSAWq24jQ==", "dev": true }, "package-json": { diff --git a/package.json b/package.json index 73ee0e9..da0c11b 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "karma-sinon": "^1.0.5", "mocha": "^10.2.0", "openssl": "^2.0.0", - "p5": "~1.9.2", + "p5": "~1.10.0", "parcel": "^2.6.2", "sinon": "^7.5.0", "standard-version": "^9.5.0", diff --git a/src/app.js b/src/app.js index bbe924f..9a721e1 100644 --- a/src/app.js +++ b/src/app.js @@ -1,3 +1,4 @@ +import './p5xr/core/p5overrides'; import p5vr from './p5xr/p5vr/p5vr'; import p5ar from './p5xr/p5ar/p5ar'; diff --git a/src/p5xr/core/p5overrides.js b/src/p5xr/core/p5overrides.js new file mode 100644 index 0000000..0d8e4d7 --- /dev/null +++ b/src/p5xr/core/p5overrides.js @@ -0,0 +1,45 @@ +import { lineVert, lineFrag } from '../shaders/lineShader'; +import compareVersions from '../utilities/versionComparator'; + +/** + * Override default p5 line shader to avoid issue in v1.10.0 + * https://github.com/processing/p5.js/issues/7200 + * @private + * @ignore + */ +const overrideLineVertShader = compareVersions(p5.VERSION, '1.10.0') >= 0; +if (overrideLineVertShader) { + console.log('p5.xr: overriding p5 v1.10.x line vert shader for v1.9.4'); + p5.RendererGL.prototype._getLineShader = function () { + if (!this._customLineShader) { + this._customLineShader = new p5.Shader( + this, + this._webGL2CompatibilityPrefix('vert', 'mediump') + lineVert, + this._webGL2CompatibilityPrefix('frag', 'mediump') + lineFrag, + ); + } + + return this._customLineShader; + }; +} + +const overrideMatrixCopy = compareVersions(p5.VERSION, '1.9.2') >= 0; +if (overrideMatrixCopy) { + console.log( + 'p5.xr: Overriding camera copy so linePerspective works with push pop', + ); + const originalCopy = p5.Camera.prototype.copy; + + p5.Camera.prototype.copy = function () { + const copiedCamera = originalCopy.call(this); + copiedCamera.useLinePerspective = this.useLinePerspective; + return copiedCamera; + }; + + const originalSet = p5.Camera.prototype.set; + p5.Camera.prototype.set = function (cam) { + originalSet.call(this, cam); + this.useLinePerspective = cam.useLinePerspective; + return this; + }; +} diff --git a/src/p5xr/core/p5xr.js b/src/p5xr/core/p5xr.js index 65bd375..b29d3b1 100644 --- a/src/p5xr/core/p5xr.js +++ b/src/p5xr/core/p5xr.js @@ -100,8 +100,13 @@ export default class p5xr { */ __setupSensibleXRDefaults() { if (typeof linePerspective !== 'undefined') { - // Stroke weight of 1mm - strokeWeight(0.001); + if (linePerspective()) { + // Stroke weight of 1mm + console.log( + 'p5xr: linePerspective is active, setting stroke width to 0.001', + ); + strokeWeight(0.001); + } } } @@ -112,7 +117,6 @@ export default class p5xr { */ __setupCanvas() { createCanvas(windowWidth, windowHeight, WEBGL); - this.__setupSensibleXRDefaults(); p5.instance._setupDone = true; } @@ -216,10 +220,16 @@ export default class p5xr { this.canvas = p5.instance.canvas; this.canvas.style.visibility = 'visible'; + p5.instance._renderer._curCamera.cameraType = 'custom'; + p5.instance._renderer._curCamera.useLinePerspective = false; + if (typeof window.setup === 'function') { window.setup(); p5.instance._millisStart = window.performance.now(); } + + this.__setupSensibleXRDefaults(); + const refSpaceRequest = this.isImmersive ? 'local' : 'viewer'; this.xrSession.requestReferenceSpace(refSpaceRequest).then((refSpace) => { this.xrRefSpace = refSpace; @@ -242,9 +252,7 @@ export default class p5xr { * @ignore */ __onRequestSession() { - p5.instance._renderer._curCamera.cameraType = 'custom'; const refSpaceRequest = this.isImmersive ? 'local' : 'viewer'; - this.gl = this.canvas.getContext(p5.instance.webglVersion); this.gl .makeXRCompatible() diff --git a/src/p5xr/core/p5xrViewer.js b/src/p5xr/core/p5xrViewer.js index 436229c..da2bd8e 100644 --- a/src/p5xr/core/p5xrViewer.js +++ b/src/p5xr/core/p5xrViewer.js @@ -41,6 +41,11 @@ class p5xrViewer { */ set view(newView) { this._view = newView; + + // eslint-disable-next-line no-unused-expressions + p5.instance._renderer.uViewMatrix?.set(this._view.transform.inverse.matrix); + + // has not effect in v1.10.0, but kept for older version p5.instance._renderer.uMVMatrix.set(this._view.transform.inverse.matrix); p5.instance._renderer.uPMatrix.set(this._view.projectionMatrix); p5.instance._renderer._curCamera.cameraMatrix.set( @@ -86,18 +91,21 @@ class p5xrViewer { // transform ray origin to view space const rayOriginCopy = ray.origin.copy(); - ray.origin.x = initialMVMatrix[0] * rayOriginCopy.x - + initialMVMatrix[1] * rayOriginCopy.y - + initialMVMatrix[2] * rayOriginCopy.z - + initialMVMatrix[3]; - ray.origin.y = initialMVMatrix[4] * rayOriginCopy.x - + initialMVMatrix[5] * rayOriginCopy.y - + initialMVMatrix[6] * rayOriginCopy.z - + initialMVMatrix[7]; - ray.origin.z = initialMVMatrix[8] * rayOriginCopy.x - + initialMVMatrix[9] * rayOriginCopy.y - + initialMVMatrix[10] * rayOriginCopy.z - + initialMVMatrix[11]; + ray.origin.x = + initialMVMatrix[0] * rayOriginCopy.x + + initialMVMatrix[1] * rayOriginCopy.y + + initialMVMatrix[2] * rayOriginCopy.z + + initialMVMatrix[3]; + ray.origin.y = + initialMVMatrix[4] * rayOriginCopy.x + + initialMVMatrix[5] * rayOriginCopy.y + + initialMVMatrix[6] * rayOriginCopy.z + + initialMVMatrix[7]; + ray.origin.z = + initialMVMatrix[8] * rayOriginCopy.x + + initialMVMatrix[9] * rayOriginCopy.y + + initialMVMatrix[10] * rayOriginCopy.z + + initialMVMatrix[11]; // get ray direction from left eye const leftDirection = new p5.Vector(screenX, screenY, -1); @@ -108,8 +116,14 @@ class p5xrViewer { leftPMatrixInverse = leftPMatrixInverse.mat4; const leftDirectionCopy = leftDirection.copy(); - leftDirection.x = leftPMatrixInverse[0] * leftDirectionCopy.x + leftPMatrixInverse[1] * leftDirectionCopy.y + leftPMatrixInverse[2] * leftDirectionCopy.z; - leftDirection.y = leftPMatrixInverse[4] * leftDirectionCopy.x + leftPMatrixInverse[5] * leftDirectionCopy.y + leftPMatrixInverse[6] * leftDirectionCopy.z; + leftDirection.x = + leftPMatrixInverse[0] * leftDirectionCopy.x + + leftPMatrixInverse[1] * leftDirectionCopy.y + + leftPMatrixInverse[2] * leftDirectionCopy.z; + leftDirection.y = + leftPMatrixInverse[4] * leftDirectionCopy.x + + leftPMatrixInverse[5] * leftDirectionCopy.y + + leftPMatrixInverse[6] * leftDirectionCopy.z; leftDirection.normalize(); // get ray direction from right eye @@ -121,8 +135,14 @@ class p5xrViewer { rightPMatrixInverse = rightPMatrixInverse.mat4; const rightDirectionCopy = rightDirection.copy(); - rightDirection.x = rightPMatrixInverse[0] * rightDirectionCopy.x + rightPMatrixInverse[1] * rightDirectionCopy.y + rightPMatrixInverse[2] * rightDirectionCopy.z; - rightDirection.y = rightPMatrixInverse[4] * rightDirectionCopy.x + rightPMatrixInverse[5] * rightDirectionCopy.y + rightPMatrixInverse[6] * rightDirectionCopy.z; + rightDirection.x = + rightPMatrixInverse[0] * rightDirectionCopy.x + + rightPMatrixInverse[1] * rightDirectionCopy.y + + rightPMatrixInverse[2] * rightDirectionCopy.z; + rightDirection.y = + rightPMatrixInverse[4] * rightDirectionCopy.x + + rightPMatrixInverse[5] * rightDirectionCopy.y + + rightPMatrixInverse[6] * rightDirectionCopy.z; rightDirection.normalize(); // combine both ray directions diff --git a/src/p5xr/shaders/lineShader.js b/src/p5xr/shaders/lineShader.js new file mode 100644 index 0000000..fc61dab --- /dev/null +++ b/src/p5xr/shaders/lineShader.js @@ -0,0 +1,340 @@ +export const lineFrag = ` +#ifdef WEBGL2 + +#define IN in +#define OUT out + +#ifdef FRAGMENT_SHADER +out vec4 outColor; +#define OUT_COLOR outColor +#endif +#define TEXTURE texture + +#else + +#ifdef FRAGMENT_SHADER +#define IN varying +#else +#define IN attribute +#endif +#define OUT varying +#define TEXTURE texture2D + +#ifdef FRAGMENT_SHADER +#define OUT_COLOR gl_FragColor +#endif + +#endif +#define STROKE_CAP_ROUND 0 +#define STROKE_CAP_PROJECT 1 +#define STROKE_CAP_SQUARE 2 +#define STROKE_JOIN_ROUND 0 +#define STROKE_JOIN_MITER 1 +#define STROKE_JOIN_BEVEL 2 +precision mediump int; + +uniform vec4 uMaterialColor; +uniform int uStrokeCap; +uniform int uStrokeJoin; +uniform float uStrokeWeight; + +IN vec4 vColor; +IN vec2 vTangent; +IN vec2 vCenter; +IN vec2 vPosition; +IN float vMaxDist; +IN float vCap; +IN float vJoin; + +float distSquared(vec2 a, vec2 b) { + vec2 aToB = b - a; + return dot(aToB, aToB); +} + +void main() { + if (vCap > 0.) { + if ( + uStrokeCap == STROKE_CAP_ROUND && + distSquared(vPosition, vCenter) > uStrokeWeight * uStrokeWeight * 0.25 + ) { + discard; + } else if ( + uStrokeCap == STROKE_CAP_SQUARE && + dot(vPosition - vCenter, vTangent) > 0. + ) { + discard; + } + // Use full area for PROJECT + } else if (vJoin > 0.) { + if ( + uStrokeJoin == STROKE_JOIN_ROUND && + distSquared(vPosition, vCenter) > uStrokeWeight * uStrokeWeight * 0.25 + ) { + discard; + } else if (uStrokeJoin == STROKE_JOIN_BEVEL) { + vec2 normal = vec2(-vTangent.y, vTangent.x); + if (abs(dot(vPosition - vCenter, normal)) > vMaxDist) { + discard; + } + } + // Use full area for MITER + } + OUT_COLOR = vec4(vColor.rgb, 1.) * vColor.a; +} +`; + +/* + * Line vert shader from p5 v1.9.4 + */ +export const lineVert = ` +#ifdef WEBGL2 + +#define IN in +#define OUT out + +#ifdef FRAGMENT_SHADER +out vec4 outColor; +#define OUT_COLOR outColor +#endif +#define TEXTURE texture + +#else + +#ifdef FRAGMENT_SHADER +#define IN varying +#else +#define IN attribute +#endif +#define OUT varying +#define TEXTURE texture2D + +#ifdef FRAGMENT_SHADER +#define OUT_COLOR gl_FragColor +#endif + +#endif +#define STROKE_CAP_ROUND 0 +#define STROKE_CAP_PROJECT 1 +#define STROKE_CAP_SQUARE 2 +#define STROKE_JOIN_ROUND 0 +#define STROKE_JOIN_MITER 1 +#define STROKE_JOIN_BEVEL 2 +/* + Part of the Processing project - http://processing.org + Copyright (c) 2012-15 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +#define PROCESSING_LINE_SHADER + +precision mediump int; + +uniform mat4 uModelViewMatrix; +uniform mat4 uProjectionMatrix; +uniform float uStrokeWeight; + +uniform bool uUseLineColor; +uniform vec4 uMaterialColor; + +uniform vec4 uViewport; +uniform int uPerspective; +uniform int uStrokeJoin; + +IN vec4 aPosition; +IN vec3 aTangentIn; +IN vec3 aTangentOut; +IN float aSide; +IN vec4 aVertexColor; + +OUT vec4 vColor; +OUT vec2 vTangent; +OUT vec2 vCenter; +OUT vec2 vPosition; +OUT float vMaxDist; +OUT float vCap; +OUT float vJoin; + +vec2 lineIntersection(vec2 aPoint, vec2 aDir, vec2 bPoint, vec2 bDir) { + // Rotate and translate so a starts at the origin and goes out to the right + bPoint -= aPoint; + vec2 rotatedBFrom = vec2( + bPoint.x*aDir.x + bPoint.y*aDir.y, + bPoint.y*aDir.x - bPoint.x*aDir.y + ); + vec2 bTo = bPoint + bDir; + vec2 rotatedBTo = vec2( + bTo.x*aDir.x + bTo.y*aDir.y, + bTo.y*aDir.x - bTo.x*aDir.y + ); + float intersectionDistance = + rotatedBTo.x + (rotatedBFrom.x - rotatedBTo.x) * rotatedBTo.y / + (rotatedBTo.y - rotatedBFrom.y); + return aPoint + aDir * intersectionDistance; +} + +void main() { + // Caps have one of either the in or out tangent set to 0 + vCap = (aTangentIn == vec3(0.)) != (aTangentOut == (vec3(0.))) + ? 1. : 0.; + + // Joins have two unique, defined tangents + vJoin = ( + aTangentIn != vec3(0.) && + aTangentOut != vec3(0.) && + aTangentIn != aTangentOut + ) ? 1. : 0.; + + vec4 posp = uModelViewMatrix * aPosition; + vec4 posqIn = uModelViewMatrix * (aPosition + vec4(aTangentIn, 0)); + vec4 posqOut = uModelViewMatrix * (aPosition + vec4(aTangentOut, 0)); + + float facingCamera = pow( + // The word space tangent's z value is 0 if it's facing the camera + abs(normalize(posqIn-posp).z), + + // Using pow() here to ramp facingCamera up from 0 to 1 really quickly + // so most lines get scaled and don't get clipped + 0.25 + ); + + // using a scale <1 moves the lines towards the camera + // in order to prevent popping effects due to half of + // the line disappearing behind the geometry faces. + float scale = mix(1.0, 0.995, facingCamera); + // Moving vertices slightly toward the camera + // to avoid depth-fighting with the fill triangles. + // Discussed here: + // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=252848 + posp.xyz = posp.xyz * scale; + posqIn.xyz = posqIn.xyz * scale; + posqOut.xyz = posqOut.xyz * scale; + + vec4 p = uProjectionMatrix * posp; + vec4 qIn = uProjectionMatrix * posqIn; + vec4 qOut = uProjectionMatrix * posqOut; + vCenter = p.xy; + + // formula to convert from clip space (range -1..1) to screen space (range 0..[width or height]) + // screen_p = (p.xy/p.w + <1,1>) * 0.5 * uViewport.zw + + // prevent division by W by transforming the tangent formula (div by 0 causes + // the line to disappear, see https://github.com/processing/processing/issues/5183) + // t = screen_q - screen_p + // + // tangent is normalized and we don't care which aDirection it points to (+-) + // t = +- normalize( screen_q - screen_p ) + // t = +- normalize( (q.xy/q.w+<1,1>)*0.5*uViewport.zw - (p.xy/p.w+<1,1>)*0.5*uViewport.zw ) + // + // extract common factor, <1,1> - <1,1> cancels out + // t = +- normalize( (q.xy/q.w - p.xy/p.w) * 0.5 * uViewport.zw ) + // + // convert to common divisor + // t = +- normalize( ((q.xy*p.w - p.xy*q.w) / (p.w*q.w)) * 0.5 * uViewport.zw ) + // + // remove the common scalar divisor/factor, not needed due to normalize and +- + // (keep uViewport - can't remove because it has different components for x and y + // and corrects for aspect ratio, see https://github.com/processing/processing/issues/5181) + // t = +- normalize( (q.xy*p.w - p.xy*q.w) * uViewport.zw ) + + vec2 tangentIn = normalize((qIn.xy*p.w - p.xy*qIn.w) * uViewport.zw); + vec2 tangentOut = normalize((qOut.xy*p.w - p.xy*qOut.w) * uViewport.zw); + + vec2 curPerspScale; + if(uPerspective == 1) { + // Perspective --- + // convert from world to clip by multiplying with projection scaling factor + // to get the right thickness (see https://github.com/processing/processing/issues/5182) + + // The y value of the projection matrix may be flipped if rendering to a Framebuffer. + // Multiplying again by its sign here negates the flip to get just the scale. + curPerspScale = (uProjectionMatrix * vec4(1, sign(uProjectionMatrix[1][1]), 0, 0)).xy; + } else { + // No Perspective --- + // multiply by W (to cancel out division by W later in the pipeline) and + // convert from screen to clip (derived from clip to screen above) + curPerspScale = p.w / (0.5 * uViewport.zw); + } + + vec2 offset; + if (vJoin == 1.) { + vTangent = normalize(tangentIn + tangentOut); + vec2 normalIn = vec2(-tangentIn.y, tangentIn.x); + vec2 normalOut = vec2(-tangentOut.y, tangentOut.x); + float side = sign(aSide); + float sideEnum = abs(aSide); + + // We generate vertices for joins on either side of the centerline, but + // the "elbow" side is the only one needing a join. By not setting the + // offset for the other side, all its vertices will end up in the same + // spot and not render, effectively discarding it. + if (sign(dot(tangentOut, vec2(-tangentIn.y, tangentIn.x))) != side) { + // Side enums: + // 1: the side going into the join + // 2: the middle of the join + // 3: the side going out of the join + if (sideEnum == 2.) { + // Calculate the position + tangent on either side of the join, and + // find where the lines intersect to find the elbow of the join + vec2 c = (posp.xy/posp.w + vec2(1.,1.)) * 0.5 * uViewport.zw; + vec2 intersection = lineIntersection( + c + (side * normalIn * uStrokeWeight / 2.), + tangentIn, + c + (side * normalOut * uStrokeWeight / 2.), + tangentOut + ); + offset = (intersection - c); + + // When lines are thick and the angle of the join approaches 180, the + // elbow might be really far from the center. We'll apply a limit to + // the magnitude to avoid lines going across the whole screen when this + // happens. + float mag = length(offset); + float maxMag = 3. * uStrokeWeight; + if (mag > maxMag) { + offset *= maxMag / mag; + } + } else if (sideEnum == 1.) { + offset = side * normalIn * uStrokeWeight / 2.; + } else if (sideEnum == 3.) { + offset = side * normalOut * uStrokeWeight / 2.; + } + } + if (uStrokeJoin == STROKE_JOIN_BEVEL) { + vec2 avgNormal = vec2(-vTangent.y, vTangent.x); + vMaxDist = abs(dot(avgNormal, normalIn * uStrokeWeight / 2.)); + } else { + vMaxDist = uStrokeWeight / 2.; + } + } else { + vec2 tangent = aTangentIn == vec3(0.) ? tangentOut : tangentIn; + vTangent = tangent; + vec2 normal = vec2(-tangent.y, tangent.x); + + float normalOffset = sign(aSide); + // Caps will have side values of -2 or 2 on the edge of the cap that + // extends out from the line + float tangentOffset = abs(aSide) - 1.; + offset = (normal * normalOffset + tangent * tangentOffset) * + uStrokeWeight * 0.5; + vMaxDist = uStrokeWeight / 2.; + } + vPosition = vCenter + offset; + + gl_Position.xy = p.xy + offset.xy * curPerspScale; + gl_Position.zw = p.zw; + + vColor = (uUseLineColor ? aVertexColor : uMaterialColor); +} +`; diff --git a/src/p5xr/utilities/versionComparator.js b/src/p5xr/utilities/versionComparator.js new file mode 100644 index 0000000..0bac796 --- /dev/null +++ b/src/p5xr/utilities/versionComparator.js @@ -0,0 +1,19 @@ +/** + * Helper to compare p5 versions of form x.x.x (ex 1.10.0) + * Used to conditionally monkey patch p5.js features when it breaks p5.xr + * Result is negative if a < b, positive if a > b and 0 if a === b + * @private + * @ignore + */ +export default function compareVersions(a, b) { + const an = a + .split('.') + .reverse() + .reduce((acc, current, index) => acc + current * 1000 ** index, 0); + const bn = b + .split('.') + .reverse() + .reduce((acc, current, index) => acc + current * 1000 ** index, 0); + if (an === bn) return 0; + return (an - bn) / Math.abs(an - bn); +} diff --git a/tests/unit/p5xr/utilities/versionComparator.js b/tests/unit/p5xr/utilities/versionComparator.js new file mode 100644 index 0000000..6154048 --- /dev/null +++ b/tests/unit/p5xr/utilities/versionComparator.js @@ -0,0 +1,23 @@ +import compareVersions from "../../../../src/p5xr/utilities/versionComparator" + +suite('utilities', function() { + test('version smaller than ', function() { + const res = compareVersions('1.9.2', '1.10.0'); + assert.isBelow(res, 0); + }); + + test('version larger than ', function() { + const res = compareVersions('1.10.0', '1.9.0'); + assert.isAbove(res, 0); + }); + + test('versions equal ', function() { + const res = compareVersions('1.10.0', '1.10.0'); + assert.equal(res, 0); + }); + + test('invalid version NaN', function() { + const res = compareVersions('v1.10.0', '1.10.0'); + assert.isNaN(res); + }); +});