Skip to content

Commit 68ae795

Browse files
authored
Merge pull request #7113 from Garima3110/worldToScreen
Solves issue #7059
2 parents 82ac2af + 23accfe commit 68ae795

File tree

4 files changed

+215
-1
lines changed

4 files changed

+215
-1
lines changed

src/core/environment.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,154 @@ function exitFullscreen() {
11461146
}
11471147
}
11481148

1149+
1150+
/**
1151+
* Converts 3D world coordinates to 2D screen coordinates.
1152+
*
1153+
* This function takes a 3D vector and converts its coordinates
1154+
* from the world space to screen space. This can be useful for placing
1155+
* 2D elements in a 3D scene or for determining the screen position
1156+
* of 3D objects.
1157+
*
1158+
* @method worldToScreen
1159+
* @param {p5.Vector} worldPosition The 3D coordinates in the world space.
1160+
* @return {p5.Vector} A vector containing the 2D screen coordinates.
1161+
* @example
1162+
* <div>
1163+
* <code>
1164+
*
1165+
* function setup() {
1166+
* createCanvas(150, 150);
1167+
* let vertices = [
1168+
* createVector(-20, -20),
1169+
* createVector(20, -20),
1170+
* createVector(20, 20),
1171+
* createVector(-20, 20)
1172+
* ];
1173+
*
1174+
* push();
1175+
* translate(75, 55);
1176+
* rotate(PI / 4);
1177+
*
1178+
* // Convert world coordinates to screen coordinates
1179+
* let screenPos = vertices.map(v => worldToScreen(v));
1180+
* pop();
1181+
*
1182+
* background(200);
1183+
*
1184+
* stroke(0);
1185+
* fill(100, 150, 255, 100);
1186+
* beginShape();
1187+
* screenPos.forEach(pos => vertex(pos.x, pos.y));
1188+
* endShape(CLOSE);
1189+
*
1190+
* screenPos.forEach((pos, i) => {
1191+
* fill(0);
1192+
* textSize(10);
1193+
* if (i === 0) {
1194+
* text(i + 1, pos.x + 3, pos.y - 7);
1195+
* } else if (i === 1) {
1196+
* text(i + 1, pos.x + 7, pos.y + 2);
1197+
* } else if (i === 2) {
1198+
* text(i + 1, pos.x - 2, pos.y + 12);
1199+
* } else if (i === 3) {
1200+
* text(i + 1, pos.x - 12, pos.y - 2);
1201+
* }
1202+
* });
1203+
*
1204+
* fill(0);
1205+
* noStroke();
1206+
* textSize(10);
1207+
* let legendY = height - 35;
1208+
* screenPos.forEach((pos, i) => {
1209+
* text(`Vertex ${i + 1}: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)})`, 5, legendY + i * 10);
1210+
* });
1211+
*
1212+
* describe('A rotating square is transformed and drawn using screen coordinates.');
1213+
*
1214+
* }
1215+
* </code>
1216+
* </div>
1217+
*
1218+
* @example
1219+
* <div>
1220+
* <code>
1221+
* let vertices;
1222+
*
1223+
* function setup() {
1224+
* createCanvas(100, 100, WEBGL);
1225+
* vertices = [
1226+
* createVector(-25, -25, -25),
1227+
* createVector(25, -25, -25),
1228+
* createVector(25, 25, -25),
1229+
* createVector(-25, 25, -25),
1230+
* createVector(-25, -25, 25),
1231+
* createVector(25, -25, 25),
1232+
* createVector(25, 25, 25),
1233+
* createVector(-25, 25, 25)
1234+
* ];
1235+
*
1236+
* describe('A rotating cube with points mapped to 2D screen space and displayed as ellipses.');
1237+
*
1238+
* }
1239+
*
1240+
* function draw() {
1241+
* background(200);
1242+
*
1243+
* // Animate rotation
1244+
* let rotationX = millis() / 1000;
1245+
* let rotationY = millis() / 1200;
1246+
*
1247+
* push();
1248+
*
1249+
* rotateX(rotationX);
1250+
* rotateY(rotationY);
1251+
*
1252+
* // Convert world coordinates to screen coordinates
1253+
* let screenPos = vertices.map(v => worldToScreen(v));
1254+
*
1255+
* pop();
1256+
*
1257+
* screenPos.forEach((pos, i) => {
1258+
*
1259+
* let screenX = pos.x - width / 2;
1260+
* let screenY = pos.y - height / 2;
1261+
* fill(0);
1262+
* noStroke();
1263+
* ellipse(screenX, screenY, 3, 3);
1264+
* });
1265+
* }
1266+
* </code>
1267+
* </div>
1268+
*
1269+
*/
1270+
1271+
p5.prototype.worldToScreen = function(worldPosition) {
1272+
const renderer = this._renderer;
1273+
if (renderer.drawingContext instanceof CanvasRenderingContext2D) {
1274+
// Handle 2D context
1275+
const transformMatrix = new DOMMatrix()
1276+
.scale(1 / renderer._pInst.pixelDensity())
1277+
.multiply(renderer.drawingContext.getTransform());
1278+
const screenCoordinates = transformMatrix.transformPoint(
1279+
new DOMPoint(worldPosition.x, worldPosition.y)
1280+
);
1281+
return new p5.Vector(screenCoordinates.x, screenCoordinates.y);
1282+
} else {
1283+
// Handle WebGL context (3D)
1284+
const modelViewMatrix = renderer.calculateCombinedMatrix();
1285+
const cameraCoordinates = modelViewMatrix.multiplyPoint(worldPosition);
1286+
const normalizedDeviceCoordinates =
1287+
renderer.states.uPMatrix.multiplyAndNormalizePoint(cameraCoordinates);
1288+
const screenX = (0.5 + 0.5 * normalizedDeviceCoordinates.x) * this.width;
1289+
const screenY = (0.5 - 0.5 * normalizedDeviceCoordinates.y) * this.height;
1290+
const screenZ = 0.5 + 0.5 * normalizedDeviceCoordinates.z;
1291+
return new p5.Vector(screenX, screenY, screenZ);
1292+
}
1293+
};
1294+
1295+
1296+
11491297
/**
11501298
* Returns the sketch's current
11511299
* <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL" target="_blank">URL</a>

src/webgl/p5.RendererGL.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,15 @@ p5.RendererGL = class RendererGL extends Renderer {
933933
this.clear(_r, _g, _b, _a);
934934
}
935935

936+
// Combines the model and view matrices to get the uMVMatrix
937+
// This method will be reusable wherever you need to update the combined matrix.
938+
calculateCombinedMatrix() {
939+
const modelMatrix = this.states.uModelMatrix;
940+
const viewMatrix = this.states.uViewMatrix;
941+
return modelMatrix.copy().mult(viewMatrix);
942+
}
943+
944+
936945
//////////////////////////////////////////////
937946
// COLOR
938947
//////////////////////////////////////////////

src/webgl/p5.Shader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ function shader(p5, fn){
937937
const viewMatrix = this._renderer.states.uViewMatrix;
938938
const projectionMatrix = this._renderer.states.uPMatrix;
939939
const modelViewMatrix = (modelMatrix.copy()).mult(viewMatrix);
940-
this._renderer.states.uMVMatrix = modelViewMatrix;
940+
this._renderer.states.uMVMatrix = this._renderer.calculateCombinedMatrix();
941941

942942
const modelViewProjectionMatrix = modelViewMatrix.copy();
943943
modelViewProjectionMatrix.mult(projectionMatrix);

test/unit/core/environment.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,61 @@ suite('Environment', function() {
236236
assert.isNumber(myp5.displayDensity(), pd);
237237
});
238238
});
239+
240+
suite('2D context test', function() {
241+
beforeEach(function() {
242+
myp5.createCanvas(100, 100);
243+
});
244+
245+
test('worldToScreen for 2D context', function() {
246+
let worldPos = myp5.createVector(50, 50);
247+
let screenPos = myp5.worldToScreen(worldPos);
248+
assert.closeTo(screenPos.x, 50, 0.1);
249+
assert.closeTo(screenPos.y, 50, 0.1);
250+
});
251+
252+
test('worldToScreen with rotation in 2D', function() {
253+
myp5.push();
254+
myp5.translate(50, 50);
255+
myp5.rotate(myp5.PI / 2);
256+
let worldPos = myp5.createVector(10, 0);
257+
let screenPos = myp5.worldToScreen(worldPos);
258+
myp5.pop();
259+
assert.closeTo(screenPos.x, 50, 0.1);
260+
assert.closeTo(screenPos.y, 60, 0.1);
261+
});
262+
});
263+
264+
suite('3D context test', function() {
265+
beforeEach(function() {
266+
myp5.createCanvas(100, 100, myp5.WEBGL);
267+
});
268+
269+
test('worldToScreen for 3D context', function() {
270+
let worldPos = myp5.createVector(0, 0, 0);
271+
let screenPos = myp5.worldToScreen(worldPos);
272+
assert.closeTo(screenPos.x, 50, 0.1);
273+
assert.closeTo(screenPos.y, 50, 0.1);
274+
});
275+
276+
test('worldToScreen with rotation in 3D around Y-axis', function() {
277+
myp5.push();
278+
myp5.rotateY(myp5.PI / 2);
279+
let worldPos = myp5.createVector(50, 0, 0);
280+
let screenPos = myp5.worldToScreen(worldPos);
281+
myp5.pop();
282+
assert.closeTo(screenPos.x, 50, 0.1);
283+
assert.closeTo(screenPos.y, 50, 0.1);
284+
});
285+
286+
test('worldToScreen with rotation in 3D around Z-axis', function() {
287+
myp5.push();
288+
myp5.rotateZ(myp5.PI / 2);
289+
let worldPos = myp5.createVector(10, 0, 0);
290+
let screenPos = myp5.worldToScreen(worldPos);
291+
myp5.pop();
292+
assert.closeTo(screenPos.x, 50, 0.1);
293+
assert.closeTo(screenPos.y, 60, 0.1);
294+
});
295+
});
239296
});

0 commit comments

Comments
 (0)