├── .gitignore ├── LICENSE ├── README.md ├── addScreenPositionFunction.js ├── examples ├── 01-basic.html ├── 02-instance-mode.html ├── 03-reset-apply.html └── 04-createGraphics.html ├── img ├── screenPos-Cover-small.png └── screenPos-Cover.png └── lib └── p5.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.ai 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hartmut Bohnacker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # p5js-screenPosition 2 | 3 |  4 | 5 | This function provides a solution to the missing screenX and screenY functions in p5.js. See https://github.com/processing/p5.js/issues/1553 for the discussion. 6 | 7 | **This is work in progress.** 8 | 9 | As far as I could find out there should be anything implemented to make it work perfectly in any mode (2D, WEBGL, angleMode DEGREES or RADIANS, normal or instance mode). If you'll find a case that isn't working correctly, please file an issue. 10 | 11 | ## Usage 12 | 13 | For a quick look on what it's doing see https://editor.p5js.org/bohnacker/sketches/nUk3bVW7b. 14 | 15 | ### 1. Download 16 | Download the latest release of `addScreenPositionFunction.js`. 17 | 18 | ### 2. Use it in your project 19 | Link to it in your HTML file. It's only working in combination with p5.js. 20 | 21 | ``` html 22 | 23 | 24 | ``` 25 | 26 | ### 3. Initializing 27 | Call the function `addScreenPositionFunction()` after creating the canvas. 28 | 29 | ``` javascript 30 | function setup() { 31 | createCanvas(400, 400); 32 | addScreenPositionFunction(); 33 | } 34 | ``` 35 | 36 | If your using p5.js in instance mode you have to pass the instance: 37 | 38 | ``` javascript 39 | p.setup = function() { 40 | p.createCanvas(400, 400); 41 | addScreenPositionFunction( p ); 42 | }; 43 | ``` 44 | 45 | Quite similar if you want to use the function with offscreen graphics: 46 | 47 | ``` javascript 48 | graphics = createGraphics(400, 400); 49 | addScreenPositionFunction( graphics ); 50 | ``` 51 | 52 | 53 | ### 4. Getting the screen position 54 | Now you can use `screenPosition(x, y, [z])` to get the position of a coordinate on screen. It returns a p5.Vector. 55 | ``` javascript 56 | var p = screenPosition(-100, 50, 0); 57 | ``` 58 | 59 | Or giving a p5.Vector: 60 | ``` javascript 61 | var v = createVector(-100, 50, 0) 62 | var p = screenPosition( v ); 63 | ``` 64 | 65 | Or giving an array with x, y, z coordinates: 66 | ``` javascript 67 | var v = [-100, 50, 0]; 68 | var p = screenPosition( v ); 69 | ``` 70 | 71 | ## Acknowledgements 72 | Thanks to Thibault Coppex (@tcoppex) for the 3d-modelview-projection-math he supplied in the issue discussion thread (https://github.com/processing/p5.js/issues/1553). I had to adjust it a bit maybe because p5js changed the way webgl is handled since 2016. 73 | -------------------------------------------------------------------------------- /addScreenPositionFunction.js: -------------------------------------------------------------------------------- 1 | // Acknowledgement to Thibault Coppex (@tcoppex) for the 3d-modelview-projection-math. 2 | // Had to adjust it a bit maybe because p5js changed the way webgl is handled since 2016. 3 | 4 | // See: https://editor.p5js.org/bohnacker/sketches/nUk3bVW7b on how to use it 5 | 6 | 7 | function addScreenPositionFunction(p5Instance) { 8 | let p = p5Instance || this; 9 | 10 | // find out which context we're in (2D or WEBGL) 11 | const R_2D = 0; 12 | const R_WEBGL = 1; 13 | let context = getObjectName(p._renderer.drawingContext).search("2D") >= 0 ? R_2D : R_WEBGL; 14 | 15 | // the stack to keep track of matrices when using push and pop 16 | if (context == R_2D) { 17 | p._renderer.matrixStack = [new p5.Matrix()]; 18 | } 19 | 20 | // replace all necessary functions to keep track of transformations 21 | 22 | if (p.draw instanceof Function) { 23 | let drawNative = p.draw; 24 | p.draw = function(...args) { 25 | if (context == R_2D) p._renderer.matrixStack = [new p5.Matrix()]; 26 | drawNative.apply(p, args); 27 | }; 28 | } 29 | 30 | 31 | if (p.resetMatrix instanceof Function) { 32 | let resetMatrixNative = p.resetMatrix; 33 | p.resetMatrix = function(...args) { 34 | if (context == R_2D) p._renderer.matrixStack = [new p5.Matrix()]; 35 | resetMatrixNative.apply(p, args); 36 | }; 37 | } 38 | 39 | if (p.translate instanceof Function) { 40 | let translateNative = p.translate; 41 | p.translate = function(...args) { 42 | if (context == R_2D) last(p._renderer.matrixStack).translate(args); 43 | translateNative.apply(p, args); 44 | }; 45 | } 46 | 47 | if (p.rotate instanceof Function) { 48 | let rotateNative = p.rotate; 49 | p.rotate = function(...args) { 50 | if (context == R_2D) { 51 | let rad = p._toRadians(args[0]); 52 | last(p._renderer.matrixStack).rotateZ(rad); 53 | } 54 | rotateNative.apply(p, args); 55 | }; 56 | } 57 | 58 | if (p.rotateX instanceof Function) { 59 | let rotateXNative = p.rotateX; 60 | p.rotateX = function(...args) { 61 | if (context == R_2D) { 62 | let rad = p._toRadians(args[0]); 63 | last(p._renderer.matrixStack).rotateX(rad); 64 | } 65 | rotateXNative.apply(p, args); 66 | }; 67 | } 68 | if (p.rotateY instanceof Function) { 69 | let rotateYNative = p.rotateY; 70 | p.rotateY = function(...args) { 71 | if (context == R_2D) { 72 | let rad = p._toRadians(args[0]); 73 | last(p._renderer.matrixStack).rotateY(rad); 74 | } 75 | rotateYNative.apply(p, args); 76 | }; 77 | } 78 | if (p.rotateZ instanceof Function) { 79 | let rotateZNative = p.rotateZ; 80 | p.rotateZ = function(...args) { 81 | if (context == R_2D) { 82 | let rad = p._toRadians(args[0]); 83 | last(p._renderer.matrixStack).rotateZ(rad); 84 | } 85 | rotateZNative.apply(p, args); 86 | }; 87 | } 88 | 89 | if (p.scale instanceof Function) { 90 | let scaleNative = p.scale; 91 | p.scale = function(...args) { 92 | if (context == R_2D) { 93 | let m = last(p._renderer.matrixStack); 94 | let sx = args[0]; 95 | let sy = args[1] || sx; 96 | let sz = context == R_2D ? 1 : args[2]; 97 | m.scale([sx, sy, sz]); 98 | } 99 | scaleNative.apply(p, args); 100 | }; 101 | } 102 | 103 | // Help needed: don't know what transformation matrix to use 104 | // Solved: Matrix multiplication had to be in reversed order. 105 | // Still, this looks like it could be simplified. 106 | 107 | if (p.shearX instanceof Function) { 108 | let shearXNative = p.shearX; 109 | p.shearX = function(...args) { 110 | if (context == R_2D) { 111 | let rad = p._toRadians(args[0]); 112 | let stack = p._renderer.matrixStack; 113 | let m = last(stack); 114 | let sm = new p5.Matrix(); 115 | sm.mat4[4] = Math.tan(rad); 116 | sm.mult(m); 117 | stack[stack.length - 1] = sm; 118 | } 119 | shearXNative.apply(p, args); 120 | }; 121 | } 122 | 123 | if (p.shearY instanceof Function) { 124 | let shearYNative = p.shearY; 125 | p.shearY = function(...args) { 126 | if (context == R_2D) { 127 | let rad = p._toRadians(args[0]); 128 | let stack = p._renderer.matrixStack; 129 | let m = last(stack); 130 | let sm = new p5.Matrix(); 131 | sm.mat4[1] = Math.tan(rad); 132 | sm.mult(m); 133 | stack[stack.length - 1] = sm; 134 | } 135 | shearYNative.apply(p, args); 136 | }; 137 | } 138 | 139 | 140 | if (p.applyMatrix instanceof Function) { 141 | let applyMatrixNative = p.applyMatrix; 142 | p.applyMatrix = function(...args) { 143 | if (context == R_2D) { 144 | let stack = p._renderer.matrixStack; 145 | let m = last(stack); 146 | let sm = new p5.Matrix(); 147 | sm.mat4[0] = args[0]; 148 | sm.mat4[1] = args[1]; 149 | sm.mat4[4] = args[2]; 150 | sm.mat4[5] = args[3]; 151 | sm.mat4[12] = args[4]; 152 | sm.mat4[13] = args[5]; 153 | sm.mult(m); 154 | stack[stack.length - 1] = sm; 155 | } 156 | applyMatrixNative.apply(p, args); 157 | }; 158 | } 159 | 160 | 161 | if (p.push instanceof Function) { 162 | let pushNative = p.push; 163 | p.push = function(...args) { 164 | if (context == R_2D) { 165 | let m = last(p._renderer.matrixStack); 166 | p._renderer.matrixStack.push(m.copy()); 167 | } 168 | pushNative.apply(p, args); 169 | }; 170 | } 171 | if (p.pop instanceof Function) { 172 | let popNative = p.pop; 173 | p.pop = function(...args) { 174 | if (context == R_2D) p._renderer.matrixStack.pop(); 175 | popNative.apply(p, args); 176 | }; 177 | } 178 | 179 | 180 | 181 | p.screenPosition = function(x, y, z) { 182 | if (x instanceof p5.Vector) { 183 | let v = x; 184 | x = v.x; 185 | y = v.y; 186 | z = v.z; 187 | } else if (x instanceof Array) { 188 | let rg = x; 189 | x = rg[0]; 190 | y = rg[1]; 191 | z = rg[2] || 0; 192 | } 193 | z = z || 0; 194 | 195 | if (context == R_2D) { 196 | let m = last(p._renderer.matrixStack); 197 | // probably not needed: 198 | // let mInv = (new p5.Matrix()).invert(m); 199 | 200 | let v = p.createVector(x, y, z); 201 | let vCanvas = multMatrixVector(m, v); 202 | // console.log(vCanvas); 203 | return vCanvas; 204 | 205 | } else { 206 | let v = p.createVector(x, y, z); 207 | 208 | // Calculate the ModelViewProjection Matrix. 209 | let mvp = (p._renderer.uMVMatrix.copy()).mult(p._renderer.uPMatrix); 210 | 211 | // Transform the vector to Normalized Device Coordinate. 212 | let vNDC = multMatrixVector(mvp, v); 213 | 214 | // Transform vector from NDC to Canvas coordinates. 215 | let vCanvas = p.createVector(); 216 | vCanvas.x = 0.5 * vNDC.x * p.width; 217 | vCanvas.y = 0.5 * -vNDC.y * p.height; 218 | vCanvas.z = 0; 219 | 220 | return vCanvas; 221 | } 222 | 223 | } 224 | 225 | 226 | // helper functions --------------------------- 227 | 228 | function last(arr) { 229 | return arr[arr.length - 1]; 230 | } 231 | 232 | function getObjectName(obj) { 233 | var funcNameRegex = /function (.{1,})\(/; 234 | var results = (funcNameRegex).exec((obj).constructor.toString()); 235 | return (results && results.length > 1) ? results[1] : ""; 236 | }; 237 | 238 | 239 | /* Multiply a 4x4 homogeneous matrix by a Vector4 considered as point 240 | * (ie, subject to translation). */ 241 | function multMatrixVector(m, v) { 242 | if (!(m instanceof p5.Matrix) || !(v instanceof p5.Vector)) { 243 | print('multMatrixVector : Invalid arguments'); 244 | return; 245 | } 246 | 247 | var _dest = p.createVector(); 248 | var mat = m.mat4; 249 | 250 | // Multiply in column major order. 251 | _dest.x = mat[0] * v.x + mat[4] * v.y + mat[8] * v.z + mat[12]; 252 | _dest.y = mat[1] * v.x + mat[5] * v.y + mat[9] * v.z + mat[13]; 253 | _dest.z = mat[2] * v.x + mat[6] * v.y + mat[10] * v.z + mat[14]; 254 | var w = mat[3] * v.x + mat[7] * v.y + mat[11] * v.z + mat[15]; 255 | 256 | if (Math.abs(w) > Number.EPSILON) { 257 | _dest.mult(1.0 / w); 258 | } 259 | 260 | return _dest; 261 | } 262 | 263 | } -------------------------------------------------------------------------------- /examples/01-basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |