From d7aa1460e6153bb09c45cf79a1ad5fb5cd08e212 Mon Sep 17 00:00:00 2001 From: Juan Carlos Ponce Campuzano <37394697+jcponce@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:14:01 +1000 Subject: [PATCH] ok --- .DS_Store | Bin 40964 -> 40964 bytes 3d-plotter/index.html | 8 +- 3d-plotter/main.js | 21 +- 3d-plotter/style.css | 4 +- source/mapping/OrbitControlls.js | 1177 +++++++++++++++++++++++++++++ source/mapping/functionLibrary.js | 187 +++++ source/mapping/index.html | 231 ++++++ source/mapping/main.js | 624 +++++++++++++++ source/mapping/parser.js | 184 +++++ source/mapping/plot-2d.svg | 547 ++++++++++++++ source/mapping/shader.js | 180 +++++ source/mapping/style.css | 127 ++++ 12 files changed, 3285 insertions(+), 5 deletions(-) create mode 100644 source/mapping/OrbitControlls.js create mode 100644 source/mapping/functionLibrary.js create mode 100644 source/mapping/index.html create mode 100644 source/mapping/main.js create mode 100644 source/mapping/parser.js create mode 100644 source/mapping/plot-2d.svg create mode 100644 source/mapping/shader.js create mode 100644 source/mapping/style.css diff --git a/.DS_Store b/.DS_Store index 8f466db8dc3dd40cd1bc8b5941af74ce2f5f5e4d..5b003a017e1e0702a913c7e057dec28e2600ffed 100644 GIT binary patch delta 711 zcma))KWI}?6vpp;No}ytzJ$D_g=)QRXst2E_hSAuAf%E`HfRGP9mK{^48bNZ)hwn} zYE6h4qzRu0HKL2oD%V9?unMAsXcwm_=;UG@#4ZZrZKe)yaQHaqUcTS??txogm&)r> z{SSOb|9*Fu_ zR6mN_^C6T6T=VJ6jwa~YY3Q{VRB*}c7uxj1!U&KTr{e30UjXj*rw5{d@nM+&Vji4Z=$Qju_D z(ZShb&5A>pKXdENEh{#%e&mM^pm}oDDmLxh&f#~@ex%o#946OMf zx4%6k34|U{pX7WfC{aHI^|7!hf*_)zNLWzxA;|7E3yNk0{o&8y{P~^#P<#DlRTY>`4SqsiB@sudjIWoDWa*Zg4<<{({F zSkBd`917O2Z09}R06!1p>QrkL>^9||gMq$iQ0V<01Yy_&V?K_ND!_zqIeclTgjuUz z_7c59uc6zvi@uI;Ky$*j2xH^^Lg*eHfb~cB!9-v^^rrXIyiy6-24}{_z=3`pm@)&$ za&i?CT3Qyan4E^}w3JLyy!iT#gElPffF(gTnvl;TPoMwn&C`F}X7R-?* z`=Ay+Zn7$W$_^hkl|fyTK`rH3FufEcZ5!X`!HXTm4 zR+({)V)=YSbDeqcmQ70U1}lgsvK>q0P@>C4XQ0Wx2CD4^ST;`+-!w_V!%P{eZ3|L4 oGF+!%?g9tHi7VvZ@w*tSr0+?5n~z{xUrMqg4b-28U?e%d0p9UpEC2ui diff --git a/3d-plotter/index.html b/3d-plotter/index.html index 95ab0f9..f745f3c 100644 --- a/3d-plotter/index.html +++ b/3d-plotter/index.html @@ -141,8 +141,14 @@ +
+
+
+
+
+
About this program
@@ -241,7 +247,7 @@

About

This program was originally created by David Brock. The source code is available in - replit.

diff --git a/3d-plotter/main.js b/3d-plotter/main.js index 1394de3..27840e8 100644 --- a/3d-plotter/main.js +++ b/3d-plotter/main.js @@ -55,6 +55,13 @@ function initEquation() { } }); + $("#createLink").click(function () { + var search = "?equation=" + encodeURIComponent(graph.currEquation); + var url = location.href.split("?")[0] + search; + + prompt("Use this URL to shere or save the current equation.", url); + }); + function update() { const equation = equationInput.val(); const plot = graph.objects.plot; @@ -581,9 +588,19 @@ function animate() { requestAnimationFrame(animate); }; +//function readURLParms() { +// var hash = decodeURIComponent(location.hash.substring(1)); //"substr" is deprecated +// if (hash.length > 1) $("#equations").val(hash); +//} + function readURLParms() { - var hash = decodeURIComponent(location.hash.substring(1)); //"substr" is deprecated - if (hash.length > 1) $("#equations").val(hash); + location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) { + value = decodeURIComponent(value); + if (key == "equation") { + graph.currEquation = value; + $("#equations").val(value); + } + }); } $(window).resize(onResize); diff --git a/3d-plotter/style.css b/3d-plotter/style.css index c8cd21d..74a8360 100644 --- a/3d-plotter/style.css +++ b/3d-plotter/style.css @@ -97,9 +97,9 @@ h1, h2, h3, p { background: rgb(231, 231, 231) !important; border-radius: 5px; overflow: auto; - line-height: 20px; + line-height: 18px; z-index: 1; - font-size: 16px; + font-size: 16px !important; transition: height 0.3s ease, width 0.3s ease; } diff --git a/source/mapping/OrbitControlls.js b/source/mapping/OrbitControlls.js new file mode 100644 index 0000000..754d745 --- /dev/null +++ b/source/mapping/OrbitControlls.js @@ -0,0 +1,1177 @@ +console.warn( "THREE.OrbitControls: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/index.html#manual/en/introduction/Import-via-modules." ); +/** + * @author qiao / https://github.com/qiao + * @author mrdoob / http://mrdoob.com + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author erich666 / http://erichaines.com + * @author ScieCode / http://github.com/sciecode + */ + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +THREE.OrbitControls = function ( object, domElement ) { + + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new THREE.Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // Set to false to disable use of the keys + this.enableKeys = true; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new THREE.Vector3(); + + // so camera.up is the orbit axis + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().inverse(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + var twoPI = 2 * Math.PI; + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + var min = scope.minAzimuthAngle; + var max = scope.maxAzimuthAngle; + + if ( isFinite ( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min < max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); + scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); + + scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove, false ); + scope.domElement.ownerDocument.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new THREE.Spherical(); + var sphericalDelta = new THREE.Spherical(); + + var scale = 1; + var panOffset = new THREE.Vector3(); + var zoomChanged = false; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new THREE.Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new THREE.Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new THREE.Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( /*event*/ ) { + + // no-op + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + var needsUpdate = false; + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan( event ) { + + if ( event.touches.length == 1 ) { + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enablePan ) handleTouchStartPan( event ); + + } + + function handleTouchStartDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enableRotate ) handleTouchStartRotate( event ); + + } + + function handleTouchMoveRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( event.touches.length == 1 ) { + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( /*event*/ ) { + + // no-op + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + + // Prevent the browser from scrolling. + event.preventDefault(); + + // Manually set the focus since calling preventDefault above + // prevents the browser from setting it automatically. + + scope.domElement.focus ? scope.domElement.focus() : window.focus(); + + var mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case THREE.MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case THREE.MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case THREE.MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove, false ); + scope.domElement.ownerDocument.addEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove, false ); + scope.domElement.ownerDocument.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + event.stopPropagation(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + + switch ( event.touches.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case THREE.TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case THREE.TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan( event ); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case THREE.TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case THREE.TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate( event ); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + event.stopPropagation(); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); + + scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); + + scope.domElement.addEventListener( 'keydown', onKeyDown, false ); + + // make sure element can receive keys. + + if ( scope.domElement.tabIndex === - 1 ) { + + scope.domElement.tabIndex = 0; + + } + + // force an update at start + + this.update(); + +}; + +THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +THREE.MapControls = function ( object, domElement ) { + + THREE.OrbitControls.call( this, object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = THREE.MOUSE.PAN; + this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; + + this.touches.ONE = THREE.TOUCH.PAN; + this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; + +}; + +THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.MapControls.prototype.constructor = THREE.MapControls; diff --git a/source/mapping/functionLibrary.js b/source/mapping/functionLibrary.js new file mode 100644 index 0000000..3092ebb --- /dev/null +++ b/source/mapping/functionLibrary.js @@ -0,0 +1,187 @@ +/* + Author: David Block + Source: https://replit.com/@DavidBrock1/complexplotter#functionLibrary.js +*/ + +//Create GLSL code for all function +var commonShaderCode = ""; +var baseScope = { + variables: [], + functions: [] +}; + +//Add variables that are defines in shader code +baseScope.variables.push("c"); +baseScope.variables.push("i"); +baseScope.variables.push("e"); +baseScope.variables.push("pi"); + +//Math constants +commonShaderCode += "uniform vec2 c;\n"; +commonShaderCode += "const vec2 i = vec2(0.0, 1.0);\n"; +commonShaderCode += "const vec2 e = vec2(" + Math.E + ", 0.0);\n"; +commonShaderCode += "const vec2 pi = vec2(" + Math.PI + ", 0.0);\n"; + +//Add basic functions that are defined in shader code +baseScope.functions.push("re1"); +baseScope.functions.push("im1"); +baseScope.functions.push("conj1"); +baseScope.functions.push("abs1"); +baseScope.functions.push("mul2"); +baseScope.functions.push("div2"); +baseScope.functions.push("polar1"); +baseScope.functions.push("ln1"); +baseScope.functions.push("exp1"); +baseScope.functions.push("gamma1"); +baseScope.functions.push("zeta1"); +baseScope.functions.push("zeta2"); + +//Enementry functions that other functions are defined in terms of +commonShaderCode += ` +vec2 re_C(vec2 a) { + return vec2(a.x, 0.0); +} +vec2 im_C(vec2 a) { + return vec2(a.y, 0.0); +} +vec2 conj_C(vec2 a) { + return vec2(a.x, -a.y); +} +vec2 abs_C(vec2 a) { + return vec2(sqrt(a.x*a.x + a.y*a.y), 0.0); +} +vec2 mul_C(vec2 a, vec2 b) { + return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); +} +vec2 div_C(vec2 a, vec2 b) { + return mul_C(a, conj_C(b)) / (b.x*b.x + b.y*b.y); +} +vec2 polar_C(vec2 a) { + return vec2(abs_C(a).x, atan(a.y, a.x)); +} +vec2 ln_C(vec2 a) { + return vec2(log(abs_C(a).x), atan(a.y, a.x)); +} +vec2 exp_C(vec2 a) { + return exp(a.x) * vec2(cos(a.y), sin(a.y)); +}`; + +commonShaderCode += GLcompile("pow(b, p) = exp(ln(b) * p)", baseScope); +commonShaderCode += GLcompile("sqrt(z) = z^0.5", baseScope); +commonShaderCode += GLcompile("log(z) = ln(z) / 2.30258509;", baseScope); +commonShaderCode += GLcompile("log(z, b) = ln(z) / ln(b)", baseScope); + +commonShaderCode += GLcompile("sin(z) = (e^(i*z) - e^(-i*z)) / 2i", baseScope); +commonShaderCode += GLcompile("cos(z) = (e^(i*z) + e^(-i*z)) / 2", baseScope); +commonShaderCode += GLcompile("tan(z) = sin(z)/cos(z)", baseScope); +commonShaderCode += GLcompile("sec(z) = 1/cos(z)", baseScope); +commonShaderCode += GLcompile("csc(z) = 1/sin(z)", baseScope); +commonShaderCode += GLcompile("cot(z) = 1/tan(z)", baseScope); + +commonShaderCode += GLcompile("asin(z) = -i * ln(i*z + sqrt(1 - z^2))", baseScope); +commonShaderCode += GLcompile("acos(z) = -i * ln(z + sqrt(z^2 - 1))", baseScope); +commonShaderCode += GLcompile("atan(z) = (i/2) * ln((i + z) / (i - z))", baseScope); + +commonShaderCode += GLcompile("sinh(z) = (e^z - e^(-z)) / 2", baseScope); +commonShaderCode += GLcompile("cosh(z) = (e^z + e^(-z)) / 2", baseScope); +commonShaderCode += GLcompile("tanh(z) = sinh(z)/cosh(z)", baseScope); + +commonShaderCode += GLcompile("asinh(z) = ln(z + sqrt(z^2 + 1))", baseScope); +commonShaderCode += GLcompile("acosh(z) = ln(z + sqrt(z^2 - 1))", baseScope); +commonShaderCode += GLcompile("atanh(z) = (1/2) * ln((1 + z) / (1 - z))", baseScope); + +//These functions are a bit too complex to write in one line +commonShaderCode += ` +vec2 gammaPartial_C(vec2 z) { + z.x -= 1.0; + vec2 x = vec2(1.0, 0.0); + + x += ${GLcompile("+676.5203681218851 / (z+1)", {variables: ["z"]})}; + x += ${GLcompile("-1259.139216722403 / (z+2)", {variables: ["z"]})}; + x += ${GLcompile("+771.3234287776531 / (z+3)", {variables: ["z"]})}; + x += ${GLcompile("-176.6150291621406 / (z+4)", {variables: ["z"]})}; + x += ${GLcompile("+12.50734327868691 / (z+5)", {variables: ["z"]})}; + x += ${GLcompile("-0.1385710952657201 / (z+6)", {variables: ["z"]})}; + x += ${GLcompile("+0.000009984369578019572 / (z+7)", {variables: ["z"]})}; + x += ${GLcompile("+0.0000001505632735149312 / (z+8)", {variables: ["z"]})}; + + ${GLcompile("t = z + 7.5", {variables: ["z"]})}; + return ${GLcompile("sqrt(2pi) * t^(z+0.5) * exp(-t) * x", {variables: ["z", "t", "x"]})}; +} + +vec2 gamma_C(vec2 z) { + vec2 y; + if (z.x < 0.5) { + y = ${GLcompile("pi / (sin(pi*z) * gammaPartial(1-z))", {variables: ["z"], functions: ["gammaPartial1"]})}; //Reflection formula + } else { + y = gammaPartial_C(z); + } + return y; +} + +vec2 zetaPartial_C(vec2 s, vec2 count) { + vec2 total = vec2(1.0, 0.0); + vec2 n1 = vec2(0.0); + vec2 n2 = vec2(0.0); + + for (float i = 1.0; i < 1024.0; i++) { + n1.x = 2.0 * i; + n2.x = 2.0 * i + 1.0; + total += ${GLcompile("-1/n1^s + 1/n2^s", {variables: ["n1", "n2", "s"]})}; + if (2.0 * i >= count.x) break; + } + return ${GLcompile("1/(1-2^(1-s)) * total", {variables: ["s", "total"]})}; +} + +vec2 zeta_C(vec2 s, vec2 count) { + vec2 y; + if (s.x < 0.5) { + y = ${GLcompile("2^s * pi^(s-1) * sin(pi*s/2) * gamma(1-s) * zetaPartial(1-s, count)", {variables: ["s", "count"], functions: ["zetaPartial2"]})}; + } else { + y = zetaPartial_C(s, count); + } + return y; +} + +vec2 zeta_C(vec2 s) { + return zeta_C(s, vec2(64.0, 0.0)); +}`; + +//Import functions not included in the math library so they can be used in JavaScript +math.import({ + zeta: math.typed({ + "Complex, Complex": (function () { + var sumTerm = math.compile("total -1/n1^s +1/n2^s"); + var result = math.compile("1/(1-2^(1-s)) * total"); + var reflection = math.compile("2^s * pi^(s-1) * sin(pi*s/2) * gamma(1-s) * zetaPartial(1-s, count)"); + + function zetaPartial(s, count) { + var total = math.complex(1.0, 0.0); + var n1 = 0.0; + var n2 = 0.0; + + for (var i = 1.0; i < 1024.0; i++) { + n1 = 2.0 * i; + n2 = 2.0 * i + 1.0; + total = sumTerm.evaluate({s: s, total: total, n1: n1, n2: n2}); + if (2.0 * i >= count) break; + } + return result.evaluate({s: s, total: total}); + } + + return function (s, count) { + var y; + if (s.re < 0.5) { + y = reflection.evaluate({s: s, count: count.re, zetaPartial: zetaPartial}); + } else { + y = zetaPartial(s, count.re); + } + return y; + }; + })(), + + "Complex": function (s) { + return math.zeta(s, 64); + } + }), +}); \ No newline at end of file diff --git a/source/mapping/index.html b/source/mapping/index.html new file mode 100644 index 0000000..fe62c03 --- /dev/null +++ b/source/mapping/index.html @@ -0,0 +1,231 @@ + + + + + + + Complex Plotter + + + + + + + + + + + + + + + + +
+ 1+2i +
+ +
+ Enter equation + (?)
+ + + +
+ +
+ c = 0 +
+ Re:
+ Im: +
+
+ +
+ +
+ + + + (?) +
+ + + + + +

+ + + + (?) +
+ +
+ + + + +
+
+ +
+

+ + + + + + (?) +
+ +
+ + + +

+ + + + (?) +
+ + +
+
+ +
+ +
+
+
+ About this program
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + \ No newline at end of file diff --git a/source/mapping/main.js b/source/mapping/main.js new file mode 100644 index 0000000..e45129d --- /dev/null +++ b/source/mapping/main.js @@ -0,0 +1,624 @@ +/* + Author: David Block + Source: https://replit.com/@DavidBrock1/complexplotter#main.js +*/ + +"use strict" + +//Namespace to store everything +let graph = { + settings: {}, + props: {}, + objects: {}, + currEquation: "f(z)=z", +}; +let lastJson = ""; +var DEBUG = true; + +function initEquation() { + const equationInput = $("#equations"); + const updateDelay = 500; + let lastEquation = ""; + let lastEquationChange = null; + let cMin = -1; + let cMax = 1; + + $("#cReInput").on("change input", function () { + uniforms.c.value.x = $(this).val() * (cMax - cMin) + cMin; + $(".c.value").text(`${uniforms.c.value.x.toFixed(4)} + ${uniforms.c.value.y.toFixed(4)}i`); + }).change(); + + $("#cImInput").on("change input", function () { + uniforms.c.value.y = $(this).val() * (cMax - cMin) + cMin; + $(".c.value").text(`${uniforms.c.value.x.toFixed(4)} + ${uniforms.c.value.y.toFixed(4)}i`); + }).change(); + + $("#cMin").change(function () { + cMin = Number($(this).val()) || 0; + }).change(); + + $("#cMax").change(function () { + cMax = Number($(this).val()) || 1; + }).change(); + + $(".c.value").click(function () { + let re = prompt("Real part of c", uniforms.c.value.x); + let im = prompt("Imaginary part of c", uniforms.c.value.y); + + if (re && im) { + $("#cReInput").val((Number(re) - cMin) / (cMax - cMin)); + $("#cImInput").val((Number(im) - cMin) / (cMax - cMin)); + $(".c.value").text(`${uniforms.c.value.x.toFixed(4)} + ${uniforms.c.value.y.toFixed(4)}i`); + + uniforms.c.value.x = Number(re); + uniforms.c.value.y = Number(im); + } + }); + + $("#createLink").click(function () { + var search = "?equation=" + encodeURIComponent(graph.currEquation); + var url = location.href.split("?")[0] + search; + + prompt("Use this URL to shere or save the current equation.", url); + }); + + function update() { + const equation = equationInput.val(); + const plot = graph.objects.plot; + + if (equation != lastEquation) { + lastEquation = equation; + lastEquationChange = Date.now(); + } + + if (lastEquationChange != null && Date.now() - lastEquationChange >= updateDelay) { + try { + plot.material = compileMaterial(lastEquation, "plot"); + // conformal.material = compileMaterial(lastEquation, "conformal"); + graph.currEquation = lastEquation; + $("#errors").text(""); + } catch (err) { + $("#errors").text(err.message) + } + + lastEquationChange = null; + } + + requestAnimationFrame(update); + } + + update(); +} + +function initOptions() { + const groups = ["equation", "about", "c", "plot", "conformal", "grid", "cursor"]; + + groups.forEach(function (name) { + $(`.${name}.showSettings`).change(function () { + if ($(this).prop("checked")) { + $(`.${name}.setting`).slideDown(200); + } else { + $(`.${name}.setting`).slideUp(200); + } + }).change(); + + $(`.${name}.showHelp`).click(function () { + $(`.${name}.popUpWrapper`).fadeIn(200); + }); + }); + + $(".popUpWrapper").click(function (e) { + if (e.target == this) { + $(this).fadeOut(200); + } + }); + + $(".popUpClose").click(function () { + $(this).parents(".popUpWrapper").fadeOut(200); + }); + + $(".about.popUpWrapper").show(); +} + +function createPlot() { + const geometry = new THREE.PlaneBufferGeometry(10, 10, 256, 256); + const material = compileMaterial(graph.currEquation, "plot"); + const plot = new THREE.Mesh(geometry, material); + + plot.lookAt(0, 1, 0); + + $("#plotShow").change(function () { + graph.settings.plotShow = $(this).prop("checked"); + plot.visible = graph.settings.plotShow; + }).change(); + + $("#plotHeight").change(function () { + graph.settings.plotHeight = Number($(this).val()); + }).change(); + + $("#plotColor").change(function () { + graph.settings.plotColor = Number($(this).val()); + if (graph.settings.plotColor == 1 || graph.settings.plotColor == 3) { + $(".plotSaturation.setting").slideDown(300); + } else { + $(".plotSaturation.setting").slideUp(300); + } + }).change(); + + $("#plotSaturation").on("change input", function () { + graph.settings.plotSaturation = Number($(this).val()); + }).change(); + + plot.onBeforeRender = function () { + uniforms.plotHeight.value = graph.settings.plotHeight; + uniforms.plotColor.value = graph.settings.plotColor; + uniforms.plotSaturation.value = graph.settings.plotSaturation; + }; + + return plot; +} + +function drawCanvasGrid(ctx, offset, scale, sizePx, labels, spacing) { + const originX = -offset.x / scale * sizePx + sizePx / 2; + const originY = -offset.z / scale * sizePx + sizePx / 2; + + let gridSpace = 1; + let gridSize = gridSpace * sizePx / scale; + + while (true) { + if (gridSize < spacing) { + if (gridSpace.toExponential(0).substr(0, 1) == "2") gridSpace *= 2.5; + else gridSpace *= 2; + gridSize = gridSpace * sizePx / scale; + + } else if (gridSize > spacing * 3) { + if (gridSpace.toExponential(0).substr(0, 1) == "5") gridSpace /= 2.5; + else gridSpace /= 2; + gridSize = gridSpace * sizePx / scale; + + } else break; + } + + let gridStartX = originX % gridSize; + if (gridStartX < 0) gridStartX += gridSize; + let gridStartY = originY % gridSize; + if (gridStartY < 0) gridStartY += gridSize; + + ctx.clearRect(0, 0, sizePx, sizePx); + + //Draw major axes + ctx.strokeStyle = "#FFFFFF"; + ctx.lineWidth = 5; + ctx.beginPath(); + ctx.moveTo(0, originY); + ctx.lineTo(sizePx, originY); + ctx.moveTo(originX, 0); + ctx.lineTo(originX, sizePx); + ctx.stroke(); + + //Draw minor geid lines + ctx.font = "30px Arial"; + ctx.fillStyle = "#FFFFFF"; + ctx.lineWidth = 2; + ctx.strokeStyle = "#df883a"; + ctx.beginPath(); + //Draw vertical lines + for (let x = gridStartX, i = 0; x <= sizePx; x += gridSize, i++) { + ctx.moveTo(x, 0); + ctx.lineTo(x, sizePx); + if (labels) { + let xPos = (x - sizePx / 2) / sizePx * scale + offset.x; + xPos = Math.round(xPos * 1e+9) / 1e+9; + ctx.fillText(xPos, x + 5, sizePx - 10); + } + } + + ctx.stroke(); + ctx.strokeStyle = "#00BFFF"; + ctx.beginPath(); + + //Draw horizontal lines + for (let y = gridStartY, i = 0; y <= sizePx; y += gridSize, i++) { + ctx.moveTo(0, y); + ctx.lineTo(sizePx, y); + if (labels) { + let yPos = (y - sizePx / 2) / sizePx * scale + offset.z; + yPos = Math.round(yPos * 1e+9) / 1e+9; + ctx.fillText(-yPos + "i", 10, y - 5); + } + } + + ctx.stroke(); +} + +function createConformal() { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + const size = 10; + const sizePx = 1024; + const labelFrequency = 1; + + canvas.width = sizePx; + canvas.height = sizePx; + + const texture = new THREE.CanvasTexture(canvas); + uniforms.grid.value = texture; + + const material = compileMaterial(graph.currEquation, "conformal"); + + const geometry = new THREE.PlaneBufferGeometry(size, size, 256, 256); + const conformal = new THREE.Mesh(geometry, material); + + conformal.lookAt(0, 1, 0); + + $("#conformalShow").change(function () { + graph.settings.conformalShow = $(this).prop("checked"); + conformal.visible = graph.settings.conformalShow; + }).change(); + + $("#conformalLabels").change(function () { + graph.settings.conformalLabels = $(this).prop("checked"); + }).change(); + + conformal.onBeforeRender = function () { + drawCanvasGrid( + ctx, graph.settings.position, + graph.settings.scale * size, sizePx, + graph.settings.conformalLabels, + graph.settings.gridSpacing + ); + + texture.needsUpdate = true; + }; + + return conformal; +} + +function createGrid() { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + const size = 10; + const sizePx = 1024; + const labelFrequency = 1; + + canvas.width = sizePx; + canvas.height = sizePx; + + const texture = new THREE.CanvasTexture(canvas); + // texture.minFilter = THREE.NearestFilter; + + const material = new THREE.MeshBasicMaterial({ + map: texture, + side: THREE.DoubleSide, + transparent: true, + depthWrite: true, + }); + + const geometry = new THREE.PlaneBufferGeometry(size, size); + const grid = new THREE.Mesh(geometry, material); + + grid.lookAt(0, 1, 0); + + $("#gridShow").change(function () { + graph.settings.gridShow = $(this).prop("checked"); + grid.visible = graph.settings.gridShow; + }).change(); + + $("#gridSpacing").on("change input", function () { + graph.settings.gridSpacing = Number($(this).val()) || 40; + }).change(); + + $("#gridLabels").change(function () { + graph.settings.gridLabels = $(this).prop("checked"); + }).change(); + + grid.onBeforeRender = function () { + drawCanvasGrid( + ctx, graph.props.position, + graph.props.scale * size, sizePx, + graph.settings.gridLabels, + graph.settings.gridSpacing * 10 + ); + + texture.needsUpdate = true; + }; + + return grid; +} + +function createBBox() { + const color = 0xFF8C00; + const size = 10; + const height = 10; + + const geometry = new THREE.BoxBufferGeometry(size, size, height); + const edges = new THREE.EdgesGeometry(geometry); + const material = new THREE.LineBasicMaterial({ color: color }); + const box = new THREE.LineSegments(edges, material); + + return box; +} + +function createCursor() { + const color = 0xFF8C00; + const size = 10; + const height = 10; + + var material = new THREE.LineDashedMaterial({ + color: color, + scale: 10, + dashSize: 2, + gapSize: 2, + }); + + var point1 = new THREE.Vector3(0, height / 2, 0); + var point2 = new THREE.Vector3(0, -height / 2, 0); + + var geometry = new THREE.Geometry(); + geometry.vertices.push(point1); + geometry.vertices.push(point2); + + var line = new THREE.Line(geometry, material); + line.computeLineDistances(); + + var raycaster = new THREE.Raycaster(); + graph.props.cursorPos = new THREE.Vector2(); + + $("#cursorShow").change(function () { + graph.settings.cursorShow = $(this).prop("checked"); + if (!graph.settings.cursorShow) { + $("#cursor").hide(); + line.visible = false; + } + }).change(); + + $("#cursorContinuous").change(function () { + graph.settings.cursorContinuous = $(this).prop("checked"); + }).change(); + + $("#plot").on("mousedown touchstart", function (event) { + let x = event.pageX; + let y = event.pageY; + if (event.touches !== undefined && event.touches.length > 0) { + let x = event.touches[0].clientX; + let y = event.touches[0].clientY; + } + + if (graph.settings.cursorShow && x !== undefined && y !== undefined) { + setCursor(x, y, false); + } + }); + + $("#plot").on("mousemove", function (event) { + let x = event.pageX; + let y = event.pageY; + if (event.touches !== undefined && event.touches.length > 0) { + let x = event.touches[0].clientX; + let y = event.touches[0].clientY; + } + + if (graph.settings.cursorShow && x !== undefined && y !== undefined) { + if (graph.settings.cursorContinuous) { + setCursor(x, y, true); + } + + if (raycastToGrid(x, y).length > 0) { + $(this).css({cursor: "crosshair"}); + } else { + $(this).css({cursor: "default"}); + } + } + }); + + function raycastToGrid(x, y) { + raycaster.setFromCamera(new THREE.Vector2( + x / window.innerWidth * 2 - 1, + -y / window.innerHeight * 2 + 1 + ), graph.objects.camera); + + return raycaster.intersectObject(graph.objects.grid); + } + + function setCursor(x, y, hideCursor) { + var hit = raycastToGrid(x, y); + + if (hit.length > 0) { + graph.props.cursorPos.x = (hit[0].uv.x - 0.5) * size; + graph.props.cursorPos.y = (hit[0].uv.y - 0.5) * size; + + updateCursor(); + + $("#cursor").css({left: x, top: y}); + $("#cursor").show(); + + } else if (hideCursor) { + graph.props.cursorPos.x = null; + graph.props.cursorPos.y = null; + line.visible = false; + $("#cursor").hide(); + } + } + + function updateCursor() { + var pos = graph.props.cursorPos; + point1.x = pos.x; + point1.z = -pos.y; + point2.x = pos.x; + point2.z = -pos.y; + geometry.verticesNeedUpdate = true; + line.visible = true; + + var z = math.complex( + pos.x * uniforms.scale.value + uniforms.offset.value.x, + pos.y * uniforms.scale.value + uniforms.offset.value.y + ); + + var scope = { + c: math.complex(uniforms.c.value.x, uniforms.c.value.y) + }; + math.evaluate(graph.currEquation, scope); + var w = math.Complex(scope.f(z)); + + $("#cursor").html(z.format(5) + ",
" + w.format(5)); + } + + line.onBeforeRender = function () { + updateCursor() + }; + + return line; +} + +function initScene() { + const plotElement = $("#plot"); + const backgroundColor = 0x555555; + const cameraSize = 7; + const initHeight = 10; + const plotRows = 1024; + const canvasSizePx = 512; + const minGridSize = 40; + const labelFrequency = 2; + + let renderer = new THREE.WebGLRenderer(); + plotElement.append(renderer.domElement); + + let scene = new THREE.Scene(); + scene.background = new THREE.Color(backgroundColor); + + let camera = new THREE.OrthographicCamera(-cameraSize, cameraSize, cameraSize, -cameraSize, 0, 100); + camera.position.set(0, 0, 50); + scene.add(camera); + + let fakeCamera = new THREE.OrthographicCamera(-cameraSize, cameraSize, cameraSize, -cameraSize, 0, 100); + fakeCamera.position.copy(camera.position); + scene.add(fakeCamera); + + let controls = new THREE.OrbitControls(fakeCamera, renderer.domElement); + controls.object.position.set(0, 50, 0); + controls.saveState(); + + let plotGroup = new THREE.Group(); + scene.add(plotGroup); + + let plot = createPlot(); + plotGroup.add(plot); + + // var conformal = createConformal(); + // plotGroup.add(conformal); + + let grid = createGrid(); + plotGroup.add(grid); + + let bBox = createBBox(); + plotGroup.add(bBox); + + let cursor = createCursor(); + plotGroup.add(cursor); + + $("#viewReset").click(function () { + controls.reset(); + }); + + graph.objects.renderer = renderer; + graph.objects.scene = scene; + graph.objects.camera = camera; + graph.objects.fakeCamera = fakeCamera; + graph.objects.controls = controls; + graph.objects.plotGroup = plotGroup; + graph.objects.plot = plot; + graph.objects.grid = grid; + graph.objects.bBox = bBox; + graph.objects.cursor = cursor; +} + +function initStats() { + var stats = new Stats(); + stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom + document.body.appendChild(stats.domElement); + $(stats.domElement).addClass("stats"); + + graph.objects.stats = stats; +} + +function onResize() { + const plotElement = $("#plot"); + const width = plotElement.width(); + const height = plotElement.height(); + const aspect = width / height; + const camera = graph.objects.camera; + const renderer = graph.objects.renderer; + + camera.left = -aspect * camera.top; + camera.right = aspect * camera.top; + + camera.updateProjectionMatrix(); + renderer.setSize(width, height); + graph.props.aspect = aspect; +} + +function animate() { + const controls = graph.objects.controls; + const plotGroup = graph.objects.plotGroup; + const renderer = graph.objects.renderer; + const scene = graph.objects.scene; + const camera = graph.objects.camera; + + controls.update(); + graph.objects.stats.update(); + + graph.props.position = controls.target.clone(); + graph.props.scale = 1 / controls.object.zoom; + graph.props.orientation = controls.object.quaternion.conjugate(); + + plotGroup.quaternion.copy(graph.props.orientation); + uniforms.offset.value.x = graph.props.position.x; + uniforms.offset.value.y = -graph.props.position.z; + uniforms.scale.value = graph.props.scale; + + let currJson = JSON.stringify([ + graph.settings, + graph.props, + graph.currEquation, + uniforms, + ]); + + if (currJson != lastJson) { + renderer.render(scene, camera); + lastJson = currJson; + } + + requestAnimationFrame(animate); +}; + +function readURLParms() { + location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) { + value = decodeURIComponent(value); + if (key == "equation") { + graph.currEquation = value; + $("#equations").val(value); + } + }); +} + +$(window).resize(onResize); + +window.onerror = function (message, src, line, col, err) { + if (DEBUG) { + alert( + "An unexpected error occured:\n" + + message + "\n" + + "line " + line + + " in " + src + ); + } +}; + +$(document).ready(function () { + readURLParms(); + initEquation(); + initOptions(); + initScene(); + initStats(); + onResize(); + animate(); +}); \ No newline at end of file diff --git a/source/mapping/parser.js b/source/mapping/parser.js new file mode 100644 index 0000000..df1f83f --- /dev/null +++ b/source/mapping/parser.js @@ -0,0 +1,184 @@ +/* + Author: David Block + Soruce: https://replit.com/@DavidBrock1/complexplotter#parser.js +*/ + +function GLcompile(node, scope) { + if (scope === undefined) { + scope = {}; + } + + if (scope.variables === undefined) scope.variables = []; + if (scope.functions === undefined) scope.functions = []; + + if (typeof node === "string") { + node = math.parse(node); + } + + //If multiline, each line must be a variable or function assignment + if (node.type == "BlockNode") { + var code = ""; + + //compile all variables + for (var i = 0; i < node.blocks.length; i++) { + switch (node.blocks[i].node.type) { + case "AssignmentNode": + case "FunctionAssignmentNode": + code += GLcompile(node.blocks[i].node, scope); + break; + + default: + throw new Error("Each line must be a variable or function assignment, not " + node.blocks[i].node.type); + } + } + + return code; + } + + switch (node.type) { + case "ConstantNode": + var num = Number(node.value); + return `vec2(${num.toFixed(8)},0.0)`; + + case "ParenthesisNode": + return `(${GLcompile(node.content, scope)})`; + + case "OperatorNode": + switch (node.fn) { + case "unaryPlus": + return `(+${GLcompile(node.args[0], scope)})`; + case "unaryMinus": + return `(-${GLcompile(node.args[0], scope)})`; + case "add": + return `${GLcompile(node.args[0], scope)}+${GLcompile(node.args[1], scope)}`; + case "subtract": + return `${GLcompile(node.args[0], scope)}-${GLcompile(node.args[1], scope)}`; + case "multiply": + return `mul_C(${GLcompile(node.args[0], scope)},${GLcompile(node.args[1], scope)})`; + case "divide": + return `div_C(${GLcompile(node.args[0], scope)},${GLcompile(node.args[1], scope)})`; + case "pow": + return `pow_C(${GLcompile(node.args[0], scope)},${GLcompile(node.args[1], scope)})`; + default: + throw new Error("Unknown operator: " + node.fn); + } + + case "FunctionNode": + var key = node.fn + node.args.length; + if (!scope.functions.includes(key) && !baseScope.functions.includes(key)) { + throw new Error("Function " + node.fn + " with " + node.args.length + " argument(s) is undefined"); + } + + var args = node.args.map(function (n) { + return GLcompile(n, scope) + }).join(","); + + return `${node.fn + "_C"}(${args})`; + + case "SymbolNode": + var key = node.name; + if (!scope.variables.includes(key) && !baseScope.variables.includes(key)) { + throw new Error("Variable " + node.name + " is undefined"); + } + + return node.name; + + case "AssignmentNode": + if (node.object.type != "SymbolNode") { + throw new Error("Can only assign to variables"); + } + + var key = node.object.name; + if (scope.variables.includes(key) || baseScope.variables.includes(key)) { + throw new Error("Variable " + node.object.name + " is already defined"); + } + scope.variables.push(key); + + return `vec2 ${node.object.name} = ${GLcompile(node.value, scope)};`; + + case "FunctionAssignmentNode": + var key = node.name + node.params.length; + if (scope.functions.includes(key) || baseScope.functions.includes(key)) { + throw new Error("Function " + node.name + " is already defined"); + } + + var newScope = { + variables: scope.variables.concat(node.params), + functions: scope.functions + }; + var args = node.params.map(function (a) { + return "vec2 " + a; + }).join(", "); + + code = ` +vec2 ${node.name}_C(${args}) { + return ${GLcompile(node.expr, newScope)}; +}`; + + scope.functions.push(key); + return code + + default: + throw new Error("Unknown node type: " + node.type); + } +} +/* +function GLcompileNode(node, scope) { + compileFunctions = { + ConstantNode: function (node) { + var num = Number(node.value); + return `vec2(${num.toFixed(10)},0.0)`; + }, + + ParenthesisNode: function (node) { + return `(${GLcompileNode(node.content)})`; + }, + + OperatorNode: function (node) { + switch (node.fn) { + case "unaryPlus": + return `(+${GLcompileNode(node.args[0])})`; + case "unaryMinus": + return `(-${GLcompileNode(node.args[0])})`; + case "add": + return `${GLcompileNode(node.args[0])}+${GLcompileNode(node.args[1])}`; + case "subtract": + return `${GLcompileNode(node.args[0])}-${GLcompileNode(node.args[1])}`; + case "multiply": + return `mulC(${GLcompileNode(node.args[0])},${GLcompileNode(node.args[1])})`; + case "divide": + return `divC(${GLcompileNode(node.args[0])},${GLcompileNode(node.args[1])})`; + case "pow": + return `powC(${GLcompileNode(node.args[0])},${GLcompileNode(node.args[1])})`; + default: + throw new Error("Unknown operator: " + node.fn); + } + }, + + FunctionNode: function (node) { + if (scope.functions[node.fn] == undefined) { + throw new Error("Function " + node.fn + " is undefined"); + } + return `${node.fn + "C"}(${node.args.map(GLcompileNode).join(",")})`; + }, + + SymbolNode: function (node) { + if (!scope.variables.includes(node.name)) { + throw new Error("Variable " + node.name + " is undefined"); + } + return node.name; + }, + + AssignmentNode: function (node) { + if (node.object.type != "SymbolNode") { + throw new Error("Can only assign to variables"); + } + if (scope.variables.includes(node.object.name)) { + throw new Error("Variable " + node.name + " is already defined"); + } + return `vec2 ${node.object.name} = ${GLcompileNode(node.value)};`; + }, + } + + return compileFunctions[node.type](node); +}*/ \ No newline at end of file diff --git a/source/mapping/plot-2d.svg b/source/mapping/plot-2d.svg new file mode 100644 index 0000000..0b863e2 --- /dev/null +++ b/source/mapping/plot-2d.svg @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Function f +f(x) = x³ + 3x² + 1 / 2 x - 1 + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + + + + + +f\left(x \right)\, = \,x^{3} + 3 \; x^{2} + \frac{1}{2} \; x - 1 +Text text1: FormulaText(f, true, true) + + + + + + + diff --git a/source/mapping/shader.js b/source/mapping/shader.js new file mode 100644 index 0000000..11a0703 --- /dev/null +++ b/source/mapping/shader.js @@ -0,0 +1,180 @@ +/* + Author: David Block + Soruce: https://replit.com/@DavidBrock1/complexplotter#shader.js +*/ + +var uniforms = { + offset: {value: new THREE.Vector2()}, + scale: {value: 1}, + c: {value: new THREE.Vector2()}, + width: {value: 5}, + plotHeight: {value: 1}, //Height option + plotColor: {value: 1}, //Color option + plotSaturation: {value: 0.67}, + grid: {value: null}, +}; + +function compileMaterial(expr, type) { + var scope = {}; + + if (expr.split("\n").length > 1) { + throw new Error("Only one equation is currently supported."); + } + + var calculation = ` +//Compiled code --------------- +/* ${expr} */ + +${GLcompile(expr, scope)} +//End of compiled code---------`; + + if (!scope.functions.includes("f1")) { + throw new Error("Code must define a function named 'f' with one argument"); + } + + console.log(calculation); + + var vShader, fShader; + + if (type == "plot") { + vShader = plotVertexShader.replace("/*{Calculation}*/", calculation); + fShader = plotFragmentShader.replace("/*{Calculation}*/", calculation); + } else { + vShader = conformalVertexShader.replace("/*{Calculation}*/", calculation); + fShader = conformalFragmentShader.replace("/*{Calculation}*/", calculation); + } + + return new THREE.ShaderMaterial({ + uniforms: uniforms, + vertexShader: vShader, + fragmentShader: fShader, + side: THREE.DoubleSide, + transparent: true, + depthWrite: true, + }); +} + +var plotVertexShader = ` +uniform vec2 offset; +uniform float scale; +uniform float plotHeight; +uniform float plotColor; +uniform float width; +varying vec3 vUv; +varying float discardTri; + +${commonShaderCode} + +/*{Calculation}*/ + +void main() { + vec3 vertex = position; + + //calculate point + vec2 z = vertex.xy * scale + offset; + vec2 w = f_C(z); + + + if (plotHeight == 1.0) vertex.z = abs_C(w).x / scale; + else if (plotHeight == 2.0) vertex.z = re_C(w).x / scale; + else if (plotHeight == 3.0) vertex.z = im_C(w).x / scale; + else if (plotHeight == 4.0) vertex.z = -0.1; + + vUv = vertex; + + if (abs(vertex.z) < width * 1.0e+3) discardTri = 0.0; + else discardTri = 1.0; + + vec4 modelViewPosition = modelViewMatrix * vec4(vertex, 1.0); + gl_Position = projectionMatrix * modelViewPosition; +} +`; + +var plotFragmentShader = ` +uniform vec2 offset; +uniform float scale; +uniform float plotHeight; +uniform float plotColor; +uniform float plotSaturation; +uniform float width; +varying vec3 vUv; +varying float discardTri; + +${commonShaderCode} + +vec3 hsl2rgb(in vec3 c) { + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0); + return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0)); +} + +/*{Calculation}*/ + +void main() { + //calculate point + vec2 z = vUv.xy * scale + offset; + vec2 w = f_C(z); + + if (abs(vUv.z) > width || discardTri > 0.0) discard; + + if (w.x != w.x || w.y != w.y || abs(w.x) > 1.0e+38 || abs(w.y) > 1.0e+38) discard; + + if (plotColor == 1.0) { + vec2 polar = polar_C(w); + float hue = polar.y / 2.0 / pi.x; + float light = min(1.0 - pow(max(1.0 - plotSaturation, 0.0), polar.x / scale), 1.0); + gl_FragColor = vec4(hsl2rgb(vec3(hue, 1.0, light)), 1.0); + + } else if (plotColor == 2.0) { + vec2 polar = polar_C(w); + float hue = polar.y / 2.0 / pi.x; + float light = 0.5; + gl_FragColor = vec4(hsl2rgb(vec3(hue, 1.0, light)), 1.0); + + } else if (plotColor == 3.0) { + vec2 polar = polar_C(w); + float hue = 0.0; + float light = min(1.0 - pow(max(1.0 - plotSaturation, 0.0), polar.x / scale), 1.0); + gl_FragColor = vec4(hsl2rgb(vec3(hue, 1.0, light)), 1.0); + + } else { + gl_FragColor = vec4(0.22, 0.55, 0.27, 1.0); //Desmos green + } +} +`; + +var conformalVertexShader = ` +uniform vec2 offset; +uniform float scale; +uniform float width; +varying vec3 vUv; + +${commonShaderCode} + +/*{Calculation}*/ + +void main() { + vec3 vertex = position; + vUv = vertex; + + //calculate point + vec2 z = vUv.xy; + vec2 w = f_C(z); + + vertex.xy = (w - offset) / scale; + + vec4 modelViewPosition = modelViewMatrix * vec4(vertex, 1.0); + gl_Position = projectionMatrix * modelViewPosition; +} +`; + +var conformalFragmentShader = ` +uniform float width; +uniform sampler2D grid; +varying vec3 vUv; + +/*{Calculation}*/ + +void main() { + gl_FragColor = texture2D(grid, vUv.xy / width / 2.0 + 0.5); +} +`; \ No newline at end of file diff --git a/source/mapping/style.css b/source/mapping/style.css new file mode 100644 index 0000000..0b08f19 --- /dev/null +++ b/source/mapping/style.css @@ -0,0 +1,127 @@ +html, body { + margin: 0px; + padding: 0px; + width: 100%; + height: 100%; + overflow: hidden; +} + +#plot { + padding: 0px; + margin: 0px; + width: 100%; + height: 100%; + z-index: -1; +} + +#cursor { + position: absolute; + left: 0px; + top: 0px; + padding: 8px; + background: rgba(255, 255, 255, 0.8); + border-radius: 5px; + font-family: "Times New Roman", Times, serif; + display: none; +} + +#panel { + position: absolute; + left: 0px; + top: 0px; + margin: 5px; + padding: 8px; + height: 300px; + width: 200px; + background: rgba(255, 255, 255, 0.8); + border-radius: 5px; + overflow: auto; + line-height: 20px; + z-index: 1; +} + +.showHelp { + color: blue; + text-decoration: underline; + cursor: pointer; + float: right; +} + +.about.showHelp { + float: none; +} + +#equations { + white-space: pre; + overflow-wrap: normal; + overflow-x: auto; +} + +#errors { + color: orangered; +} + +.c.value { + font-style: italic; + text-decoration: underline; + cursor: pointer; +} + +input[name=cInput] { + width: 100%; +} + +input[name=plotSaturation] { + width: 100px; +} + +#settingsWrapper .setting { + padding-left: 10px; +} + +.setting label { + font-style: italic +} + +.c input[type=text] { + width: 40px; +} + +.grid input[type=number] { + width: 40px; +} + +.popUpWrapper { + position: fixed; + z-index: 2; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0.5,0.5,0.5,0.25); + display: none; +} + +.popUp { + background-color: #fefefe; + margin: 35px auto; + padding: 20px; + border: 1px solid #888; + width: 80%; + border-radius: 8px; +} + +.popUpClose { + float: right; + font-size: 30px; + padding-right: 5px; + cursor: pointer; +} + +.stats { + position: absolute; + top: 8px; + right: 8px; + z-index: 1; +} \ No newline at end of file