1. A concise explanation of the problem you’re experiencing.
I’m trying to draw a rectangle based on the heading of the camera. The default rectangle provided by Cesium works perfectly, but only when you’re facing exact North. If the camera is facing a different direction, the rectangle doesn’t highlight the correct area.
I tried to use the rotation attribute of the rectangle, which made it highlight in the correct direction, but this meant that the points selected for the rectangle weren’t necessarily included in the rectangle that was drawn.
The second implementation I tried is found below. It uses a polygon in the back end, but the user won’t be able to tell that it isn’t a rectangle. It uses a callback function that updates the rectangle as the mouse moves. It then translates the first selected point and the mousePoint onto a 2D plane using sinusoidal projection. Then I use some simple 2D maths to calculate the other two corners based on the camera heading and the two 2D points. It then translates the 2D points back into Cesium Cartographic points and plot the polygon using these points.
The problem is that this approach seems to have a few issues. The main issue is that the “rectangle” appears as a parallelogram, even though the 2D points seem to be in a correct rectangle. The second issue seems to be with vertex pathing. In certain directions, the parallelogram appears to be solid, but in other cases it looks like two overlapping triangles.
I’m also drawing points in the callback function to better visualise the vertices.
The code below is compatible with Cesium Sandcastle.
2. A minimal code example. If you’ve found a bug, this helps us reproduce and repair it.
var viewer = new Cesium.Viewer(‘cesiumContainer’);
viewer.terrainProvider = Cesium.createWorldTerrain();
var rectanglePoints = ;
var mousePoint = null;
var rectangleEntity = null;
var currentColor = Cesium.Color.WHITE.withAlpha(0.5);
// function accepts and returns cesium cartographic objects or array of [x,y] points
function sinusoidalProjection(points, reverse) {
let earth_radius = 6371009;
let projectedPoints = ;
if (reverse) {
points.forEach((point) => {
let latitude = point[1]/earth_radius;
let longitude = point[0]/(earth_radius*Math.cos(latitude));
let projectedPoint = Cesium.Cartographic.fromRadians(longitude, latitude);
projectedPoints.push(projectedPoint);
});
} else {
points.forEach((point) => {
let pointX = point.longitude * earth_radius * Math.cos(point.latitude);
let pointY = point.latitude * earth_radius;
let projectedPoint = [pointX, pointY];
projectedPoints.push(projectedPoint);
});
}
return projectedPoints;
}
function addEnt() {
let displayCondition = new Cesium.DistanceDisplayCondition(0, 1200);
// This is the first point
var height = Cesium.Cartographic.fromCartesian(rectanglePoints[0]).height + 0.1;
let heading = (viewer.camera.heading);
let theta = 360-(heading * 180/Math.PI);
testEnt = viewer.entities.add({
polygon: {
hierarchy: new Cesium.CallbackProperty(function() {
let firstCarto = Cesium.Cartographic.fromCartesian(rectanglePoints[0]);
let mouseCarto = Cesium.Cartographic.fromCartesian(mousePoint);
let cartesianPoints = sinusoidalProjection([firstCarto, mouseCarto]);
let c1 = cartesianPoints[0][1]-(Math.tan(theta)*cartesianPoints[0][0]); // c1=y-tan(theta) * x
let c2 = cartesianPoints[1][1]+(cartesianPoints[1][0]/Math.tan(theta)); // c2=y+(x/tan(theta))
let x = (c2-c1)/(Math.tan(theta)+(1/Math.tan(theta)));
let y = Math.tan(theta)*x + c1;
let firstPoint = [x,y];
let c3 = cartesianPoints[0][1]+(cartesianPoints[0][0]/Math.tan(theta)); // c3=y+(x/tan(theta))
let c4 = cartesianPoints[1][1]-(Math.tan(theta)*cartesianPoints[1][0]); // c4=y-tan(theta) * x
x = (c3-c4)/(Math.tan(theta)+(1/Math.tan(theta)));
y = Math.tan(theta)*x+c4;
let secondPoint = [x,y];
console.log(“LOGS”);
console.log("First point " + cartesianPoints[0]);
console.log("Mouse point " + cartesianPoints[1]);
console.log("First found corner " + firstPoint);
console.log("Second found corner " + secondPoint);
let finalCartoPoints = sinusoidalProjection([cartesianPoints[0], cartesianPoints[1], firstPoint, secondPoint], true);
let finalCartesians = ;
finalCartoPoints.forEach((point) => {
let cartesian = Cesium.Cartesian3.fromRadians(point.longitude, point.latitude, 10);
finalCartesians.push(cartesian);
});
finalCartesians.forEach(point => {
viewer.entities.add({
position: point,
point: {
pixelSize: 5,
material: currentColor,
outlineWidth: 2,
},
});
});
console.log(finalCartesians);
return […finalCartesians, mousePoint];
}, false),
material: currentColor,
height: height
}
});
}
new Cesium.ScreenSpaceEventHandler(viewer.canvas).setInputAction(function (event) {
var cartesianPosition = viewer.scene.pickPosition(event.position);
if (rectanglePoints.length === 0) {
rectanglePoints.push(cartesianPosition);
addEnt();
} else {
viewer.entities.remove(rectangleEntity);
rectanglePoints.push(cartesianPosition);
addEnt();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
new Cesium.ScreenSpaceEventHandler(viewer.canvas).setInputAction(function (event) {
mousePoint = viewer.scene.pickPosition(event.endPosition);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
``
3. Context. Why do you need to do this? We might know a better way to accomplish your goal.
I’m trying to draw a rectangle based on the heading of the camera.
4. The Cesium version you’re using, your operating system and browser.
Cesium: 1.5
OS: Ubuntu
Browser: Chrome