Skip to content

Commit

Permalink
Merge pull request #7113 from Garima3110/worldToScreen
Browse files Browse the repository at this point in the history
Solves issue #7059
  • Loading branch information
davepagurek authored Nov 2, 2024
2 parents 82ac2af + 23accfe commit 68ae795
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 1 deletion.
148 changes: 148 additions & 0 deletions src/core/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,154 @@ function exitFullscreen() {
}
}


/**
* Converts 3D world coordinates to 2D screen coordinates.
*
* This function takes a 3D vector and converts its coordinates
* from the world space to screen space. This can be useful for placing
* 2D elements in a 3D scene or for determining the screen position
* of 3D objects.
*
* @method worldToScreen
* @param {p5.Vector} worldPosition The 3D coordinates in the world space.
* @return {p5.Vector} A vector containing the 2D screen coordinates.
* @example
* <div>
* <code>
*
* function setup() {
* createCanvas(150, 150);
* let vertices = [
* createVector(-20, -20),
* createVector(20, -20),
* createVector(20, 20),
* createVector(-20, 20)
* ];
*
* push();
* translate(75, 55);
* rotate(PI / 4);
*
* // Convert world coordinates to screen coordinates
* let screenPos = vertices.map(v => worldToScreen(v));
* pop();
*
* background(200);
*
* stroke(0);
* fill(100, 150, 255, 100);
* beginShape();
* screenPos.forEach(pos => vertex(pos.x, pos.y));
* endShape(CLOSE);
*
* screenPos.forEach((pos, i) => {
* fill(0);
* textSize(10);
* if (i === 0) {
* text(i + 1, pos.x + 3, pos.y - 7);
* } else if (i === 1) {
* text(i + 1, pos.x + 7, pos.y + 2);
* } else if (i === 2) {
* text(i + 1, pos.x - 2, pos.y + 12);
* } else if (i === 3) {
* text(i + 1, pos.x - 12, pos.y - 2);
* }
* });
*
* fill(0);
* noStroke();
* textSize(10);
* let legendY = height - 35;
* screenPos.forEach((pos, i) => {
* text(`Vertex ${i + 1}: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)})`, 5, legendY + i * 10);
* });
*
* describe('A rotating square is transformed and drawn using screen coordinates.');
*
* }
* </code>
* </div>
*
* @example
* <div>
* <code>
* let vertices;
*
* function setup() {
* createCanvas(100, 100, WEBGL);
* vertices = [
* createVector(-25, -25, -25),
* createVector(25, -25, -25),
* createVector(25, 25, -25),
* createVector(-25, 25, -25),
* createVector(-25, -25, 25),
* createVector(25, -25, 25),
* createVector(25, 25, 25),
* createVector(-25, 25, 25)
* ];
*
* describe('A rotating cube with points mapped to 2D screen space and displayed as ellipses.');
*
* }
*
* function draw() {
* background(200);
*
* // Animate rotation
* let rotationX = millis() / 1000;
* let rotationY = millis() / 1200;
*
* push();
*
* rotateX(rotationX);
* rotateY(rotationY);
*
* // Convert world coordinates to screen coordinates
* let screenPos = vertices.map(v => worldToScreen(v));
*
* pop();
*
* screenPos.forEach((pos, i) => {
*
* let screenX = pos.x - width / 2;
* let screenY = pos.y - height / 2;
* fill(0);
* noStroke();
* ellipse(screenX, screenY, 3, 3);
* });
* }
* </code>
* </div>
*
*/

p5.prototype.worldToScreen = function(worldPosition) {
const renderer = this._renderer;
if (renderer.drawingContext instanceof CanvasRenderingContext2D) {
// Handle 2D context
const transformMatrix = new DOMMatrix()
.scale(1 / renderer._pInst.pixelDensity())
.multiply(renderer.drawingContext.getTransform());
const screenCoordinates = transformMatrix.transformPoint(
new DOMPoint(worldPosition.x, worldPosition.y)
);
return new p5.Vector(screenCoordinates.x, screenCoordinates.y);
} else {
// Handle WebGL context (3D)
const modelViewMatrix = renderer.calculateCombinedMatrix();
const cameraCoordinates = modelViewMatrix.multiplyPoint(worldPosition);
const normalizedDeviceCoordinates =
renderer.states.uPMatrix.multiplyAndNormalizePoint(cameraCoordinates);
const screenX = (0.5 + 0.5 * normalizedDeviceCoordinates.x) * this.width;
const screenY = (0.5 - 0.5 * normalizedDeviceCoordinates.y) * this.height;
const screenZ = 0.5 + 0.5 * normalizedDeviceCoordinates.z;
return new p5.Vector(screenX, screenY, screenZ);
}
};



/**
* Returns the sketch's current
* <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL" target="_blank">URL</a>
Expand Down
9 changes: 9 additions & 0 deletions src/webgl/p5.RendererGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,15 @@ p5.RendererGL = class RendererGL extends Renderer {
this.clear(_r, _g, _b, _a);
}

// Combines the model and view matrices to get the uMVMatrix
// This method will be reusable wherever you need to update the combined matrix.
calculateCombinedMatrix() {
const modelMatrix = this.states.uModelMatrix;
const viewMatrix = this.states.uViewMatrix;
return modelMatrix.copy().mult(viewMatrix);
}


//////////////////////////////////////////////
// COLOR
//////////////////////////////////////////////
Expand Down
2 changes: 1 addition & 1 deletion src/webgl/p5.Shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ function shader(p5, fn){
const viewMatrix = this._renderer.states.uViewMatrix;
const projectionMatrix = this._renderer.states.uPMatrix;
const modelViewMatrix = (modelMatrix.copy()).mult(viewMatrix);
this._renderer.states.uMVMatrix = modelViewMatrix;
this._renderer.states.uMVMatrix = this._renderer.calculateCombinedMatrix();

const modelViewProjectionMatrix = modelViewMatrix.copy();
modelViewProjectionMatrix.mult(projectionMatrix);
Expand Down
57 changes: 57 additions & 0 deletions test/unit/core/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,61 @@ suite('Environment', function() {
assert.isNumber(myp5.displayDensity(), pd);
});
});

suite('2D context test', function() {
beforeEach(function() {
myp5.createCanvas(100, 100);
});

test('worldToScreen for 2D context', function() {
let worldPos = myp5.createVector(50, 50);
let screenPos = myp5.worldToScreen(worldPos);
assert.closeTo(screenPos.x, 50, 0.1);
assert.closeTo(screenPos.y, 50, 0.1);
});

test('worldToScreen with rotation in 2D', function() {
myp5.push();
myp5.translate(50, 50);
myp5.rotate(myp5.PI / 2);
let worldPos = myp5.createVector(10, 0);
let screenPos = myp5.worldToScreen(worldPos);
myp5.pop();
assert.closeTo(screenPos.x, 50, 0.1);
assert.closeTo(screenPos.y, 60, 0.1);
});
});

suite('3D context test', function() {
beforeEach(function() {
myp5.createCanvas(100, 100, myp5.WEBGL);
});

test('worldToScreen for 3D context', function() {
let worldPos = myp5.createVector(0, 0, 0);
let screenPos = myp5.worldToScreen(worldPos);
assert.closeTo(screenPos.x, 50, 0.1);
assert.closeTo(screenPos.y, 50, 0.1);
});

test('worldToScreen with rotation in 3D around Y-axis', function() {
myp5.push();
myp5.rotateY(myp5.PI / 2);
let worldPos = myp5.createVector(50, 0, 0);
let screenPos = myp5.worldToScreen(worldPos);
myp5.pop();
assert.closeTo(screenPos.x, 50, 0.1);
assert.closeTo(screenPos.y, 50, 0.1);
});

test('worldToScreen with rotation in 3D around Z-axis', function() {
myp5.push();
myp5.rotateZ(myp5.PI / 2);
let worldPos = myp5.createVector(10, 0, 0);
let screenPos = myp5.worldToScreen(worldPos);
myp5.pop();
assert.closeTo(screenPos.x, 50, 0.1);
assert.closeTo(screenPos.y, 60, 0.1);
});
});
});

0 comments on commit 68ae795

Please sign in to comment.