Google Maps ImageMapTypeOptions.getTileUrl convert point and zoom to LatLng - google-maps-api-3

In google maps v3 api, How do I convert the point and zoom that get passed to ImageMapTypeOptions.getTitleUrl to a LatLng?
Thanks!

This shows how it's done with code you can reimplement in other languages.
https://developers.google.com/maps/documentation/javascript/examples/map-coordinates

const TILE_SIZE = 314;
const tileCoordToWorldCoord = ( tileCoord, zoom ) => {
const scale = Math.pow( 2, zoom );
const shift = Math.floor( TILE_SIZE / 2 );
const calc = tc => ( tc * TILE_SIZE + shift ) / scale;
const x = calc( tileCoord.x );
const y = calc( tileCoord.y );
return new google.maps.Point( x, y );
}
...
getTileUrl: ( coord, zoom ) => {
const pointCoord = tileCoordToWorldCoord( coord, zoom );
const latLng = mapInstance.getProjection().fromPointToLatLng( pointCoord );
}

Related

ThreeJS: Tweaking projection / raycasting

there is a very rough ThreeJS sketch with a cube at the Vector3(0.0, 0.0, 0.0) rotated with one edge to a viewer. Code gets some screen points from left/right edges, transforms them to 3D world coordinates and transpose further for their projections on the cube. By now I have set them by hand, but it could be done with THREE.Raycaster and the result is the same.
let m0 = new THREE.Vector3(0.0, edges.wtl.y, 100.0);
let m1 = new THREE.Vector3(0.0, edges.wtl.y, -100.0);
let ray0 = new THREE.Raycaster();
let dir = m1.clone().sub(m0.clone()).normalize();
ray0.set(m0, dir);
The initial setup looks fine, but if you rotate scene with OrbitControls you would notice that straight white lines don't match with red ones. Despite the fact that the red lines are built correctly based on the camera FOV distortion I need to tweak red dots in a way illustrated below.
Any ideas? Maybe I need to find screen coordinates for cube left/right edges and find its intersections with whose I am using just in the beginning of calculateEdges() and transform them back to world ones? It's a very clumsy solution and could be use as a last resort only.
THREE.OrbitControls = function ( object, domElement ) {
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
// API
this.enabled = true;
this.center = new THREE.Vector3();
this.userZoom = true;
this.userZoomSpeed = 1.0;
this.userRotate = true;
this.userRotateSpeed = 1.0;
this.userPan = true;
this.userPanSpeed = 2.0;
this.autoRotate = false;
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
this.minDistance = 0;
this.maxDistance = Infinity;
// 65 /*A*/, 83 /*S*/, 68 /*D*/
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, ROTATE: 65, ZOOM: 83, PAN: 68 };
// internals
var scope = this;
var EPS = 0.000001;
var PIXELS_PER_ROUND = 1800;
var rotateStart = new THREE.Vector2();
var rotateEnd = new THREE.Vector2();
var rotateDelta = new THREE.Vector2();
var zoomStart = new THREE.Vector2();
var zoomEnd = new THREE.Vector2();
var zoomDelta = new THREE.Vector2();
var phiDelta = 0;
var thetaDelta = 0;
var scale = 1;
var lastPosition = new THREE.Vector3();
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 };
var state = STATE.NONE;
// events
var changeEvent = { type: 'change' };
this.rotateLeft = function ( angle ) {
if ( angle === undefined ) {
angle = getAutoRotationAngle();
}
thetaDelta -= angle;
};
this.rotateRight = function ( angle ) {
if ( angle === undefined ) {
angle = getAutoRotationAngle();
}
thetaDelta += angle;
};
this.rotateUp = function ( angle ) {
if ( angle === undefined ) {
angle = getAutoRotationAngle();
}
phiDelta -= angle;
};
this.rotateDown = function ( angle ) {
if ( angle === undefined ) {
angle = getAutoRotationAngle();
}
phiDelta += angle;
};
this.zoomIn = function ( zoomScale ) {
if ( zoomScale === undefined ) {
zoomScale = getZoomScale();
}
scale /= zoomScale;
};
this.zoomOut = function ( zoomScale ) {
if ( zoomScale === undefined ) {
zoomScale = getZoomScale();
}
scale *= zoomScale;
};
this.pan = function ( distance ) {
distance.transformDirection( this.object.matrix );
distance.multiplyScalar( scope.userPanSpeed );
this.object.position.add( distance );
this.center.add( distance );
};
this.update = function () {
var position = this.object.position;
var offset = position.clone().sub( this.center );
// angle from z-axis around y-axis
var theta = Math.atan2( offset.x, offset.z );
// angle from y-axis
var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
if ( this.autoRotate ) {
this.rotateLeft( getAutoRotationAngle() );
}
theta += thetaDelta;
phi += phiDelta;
// restrict phi to be between desired limits
phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
// restrict phi to be betwee EPS and PI-EPS
phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
var radius = offset.length() * scale;
// restrict radius to be between desired limits
radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
offset.x = radius * Math.sin( phi ) * Math.sin( theta );
offset.y = radius * Math.cos( phi );
offset.z = radius * Math.sin( phi ) * Math.cos( theta );
position.copy( this.center ).add( offset );
this.object.lookAt( this.center );
thetaDelta = 0;
phiDelta = 0;
scale = 1;
if ( lastPosition.distanceTo( this.object.position ) > 0 ) {
this.dispatchEvent( changeEvent );
lastPosition.copy( this.object.position );
}
};
function getAutoRotationAngle() {
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
}
function getZoomScale() {
return Math.pow( 0.95, scope.userZoomSpeed );
}
function onMouseDown( event ) {
if ( scope.enabled === false ) return;
if ( scope.userRotate === false ) return;
event.preventDefault();
if ( state === STATE.NONE )
{
if ( event.button === 0 )
state = STATE.ROTATE;
if ( event.button === 1 )
state = STATE.ZOOM;
if ( event.button === 2 )
state = STATE.PAN;
}
if ( state === STATE.ROTATE ) {
//state = STATE.ROTATE;
rotateStart.set( event.clientX, event.clientY );
} else if ( state === STATE.ZOOM ) {
//state = STATE.ZOOM;
zoomStart.set( event.clientX, event.clientY );
} else if ( state === STATE.PAN ) {
//state = STATE.PAN;
}
document.addEventListener( 'mousemove', onMouseMove, false );
document.addEventListener( 'mouseup', onMouseUp, false );
}
function onMouseMove( event ) {
if ( scope.enabled === false ) return;
event.preventDefault();
if ( state === STATE.ROTATE ) {
rotateEnd.set( event.clientX, event.clientY );
rotateDelta.subVectors( rotateEnd, rotateStart );
scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed );
scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed );
rotateStart.copy( rotateEnd );
} else if ( state === STATE.ZOOM ) {
zoomEnd.set( event.clientX, event.clientY );
zoomDelta.subVectors( zoomEnd, zoomStart );
if ( zoomDelta.y > 0 ) {
scope.zoomIn();
} else {
scope.zoomOut();
}
zoomStart.copy( zoomEnd );
} else if ( state === STATE.PAN ) {
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
scope.pan( new THREE.Vector3( - movementX, movementY, 0 ) );
}
}
function onMouseUp( event ) {
if ( scope.enabled === false ) return;
if ( scope.userRotate === false ) return;
document.removeEventListener( 'mousemove', onMouseMove, false );
document.removeEventListener( 'mouseup', onMouseUp, false );
state = STATE.NONE;
}
function onMouseWheel( event ) {
if ( scope.enabled === false ) return;
if ( scope.userZoom === false ) return;
var delta = 0;
if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta;
} else if ( event.detail ) { // Firefox
delta = - event.detail;
}
if ( delta > 0 ) {
scope.zoomOut();
} else {
scope.zoomIn();
}
}
function onKeyDown( event ) {
if ( scope.enabled === false ) return;
if ( scope.userPan === false ) return;
switch ( event.keyCode ) {
/*case scope.keys.UP:
scope.pan( new THREE.Vector3( 0, 1, 0 ) );
break;
case scope.keys.BOTTOM:
scope.pan( new THREE.Vector3( 0, - 1, 0 ) );
break;
case scope.keys.LEFT:
scope.pan( new THREE.Vector3( - 1, 0, 0 ) );
break;
case scope.keys.RIGHT:
scope.pan( new THREE.Vector3( 1, 0, 0 ) );
break;
*/
case scope.keys.ROTATE:
state = STATE.ROTATE;
break;
case scope.keys.ZOOM:
state = STATE.ZOOM;
break;
case scope.keys.PAN:
state = STATE.PAN;
break;
}
}
function onKeyUp( event ) {
switch ( event.keyCode ) {
case scope.keys.ROTATE:
case scope.keys.ZOOM:
case scope.keys.PAN:
state = STATE.NONE;
break;
}
}
this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
this.domElement.addEventListener( 'mousedown', onMouseDown, false );
this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
window.addEventListener( 'keydown', onKeyDown, false );
window.addEventListener( 'keyup', onKeyUp, false );
};
THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
let camera, scene, renderer, raycaster, controls, edges = {}, line0, line1, plane;
let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;
init();
animate();
function init() {
const container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1024);
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 64;
scene = new THREE.Scene();
edges.tl = new THREE.Vector3(0.0, 0.0, 0.0);
edges.tr = new THREE.Vector3(0.0, 0.0, 0.0);
edges.bl = new THREE.Vector3(0.0, 0.0, 0.0);
edges.br = new THREE.Vector3(0.0, 0.0, 0.0);
edges.wtl = new THREE.Vector3(0.0, 0.0, 0.0);
edges.wtr = new THREE.Vector3(0.0, 0.0, 0.0);
edges.wbl = new THREE.Vector3(0.0, 0.0, 0.0);
edges.wbr = new THREE.Vector3(0.0, 0.0, 0.0);
edges.width = new THREE.Vector3(0.0, 0.0, 0.0);
edges.wwidth = new THREE.Vector3(0.0, 0.0, 0.0);
const ambientLight = new THREE.AmbientLight(0xCCCCCC, 0.4);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xFFFFFF, 0.8);
camera.add(pointLight);
scene.add(camera);
renderer = new THREE.WebGLRenderer();
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minPolarAngle = Math.PI / 2.0 -0.15;
controls.maxPolarAngle = Math.PI / 2.0 + 0.15;
controls.minAzimuthAngle = -0.15;
controls.maxAzimuthAngle = 0.15;
controls.minDistance = 42.0; //.75;
controls.maxDistance = 69.0;
//cube
let geometry = new THREE.BoxGeometry(32, 32, 32);
let material = new THREE.MeshPhongMaterial( {color: 0x00FFFF} );
const cube = new THREE.Mesh(geometry, material);
cube.rotation.set(0.0, -Math.PI / 4.0, 0.0);
cube.name = "cube";
cube.updateMatrixWorld();
scene.add(cube);
//window.addEventListener('resize', onWindowResize);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
controls.update();
calculateEdges()
renderer.render(scene, camera);
}
function calculateEdges(){
let toRemove = ["line0", "line1", "topLine", "bottomLine", "frame", "pointTM", "pointBM", "point00", "point01", "point10", "point11", "point20","point21", "point30", "point31", "point40", "point41"];
toRemove.forEach((name_) => { if(scene.getObjectByName(name_) != undefined) { scene.remove(scene.getObjectByName(name_)); } })
let distance = 0.0, w = 50;
edges.tl.x = -1.0;
edges.tl.y = -((windowHalfY - w) / window.innerHeight) * 2 + 1;
edges.tl.z = 0.0;
edges.width.x = ((2.0 * w) / window.innerWidth) * 2 - 1;
edges.width.y = -((windowHalfY - w) / window.innerHeight) * 2 + 1;
edges.width.z = 0.0;
edges.tr.x = (windowHalfX * 2.0 / window.innerWidth) * 2 - 1;
edges.tr.y = -((windowHalfY - w) / window.innerHeight) * 2 + 1;
edges.tr.z = 0.0;
edges.bl.x = -1.0;
edges.bl.y = -((windowHalfY + w) / window.innerHeight) * 2 + 1;
edges.bl.z = 0.0;
edges.br.x = (windowHalfX * 2.0 / window.innerWidth) * 2 - 1;
edges.br.y = -((windowHalfY + w) / window.innerHeight) * 2 + 1;
edges.br.z = 0.0;
edges.tl.unproject(camera);
edges.tl.sub(camera.position).normalize();
distance = -camera.position.z / edges.tl.z;
edges.wtl = edges.wtl.copy(camera.position).add(edges.tl.multiplyScalar(distance));
edges.width.unproject(camera);
edges.width.sub(camera.position).normalize();
distance = -camera.position.z / edges.width.z;
edges.wwidth = edges.wwidth.copy(camera.position).add(edges.width.multiplyScalar(distance));
edges.tr.unproject(camera);
edges.tr.sub(camera.position).normalize();
distance = -camera.position.z / edges.tr.z;
edges.wtr = edges.wtr.copy(camera.position).add(edges.tr.multiplyScalar(distance));
edges.bl.unproject(camera);
edges.bl.sub(camera.position).normalize();
distance = -camera.position.z / edges.bl.z;
edges.wbl = edges.wbl.copy(camera.position).add(edges.bl.multiplyScalar(distance));
edges.br.unproject(camera);
edges.br.sub(camera.position).normalize();
distance = -camera.position.z / edges.br.z;
edges.wbr = edges.wbr.copy(camera.position).add(edges.br.multiplyScalar(distance));
const material = new THREE.LineBasicMaterial({ color: 0x0FFFFFF });
const points0 = [edges.wtl, edges.wtr];
let geometry = new THREE.BufferGeometry().setFromPoints(points0);
line0 = new THREE.Line(geometry, material);
line0.name = "line0";
scene.add(line0);
const points1 = [edges.wbl, edges.wbr];
geometry = new THREE.BufferGeometry().setFromPoints(points1);
line1 = new THREE.Line(geometry, material);
line1.name = "line1";
scene.add(line1);
const sphereGeometry = new THREE.SphereGeometry(1.0, 8, 8);
const sphereMaterial = new THREE.MeshBasicMaterial( { color: 0xFFFFFF } );
const sphereMaterial2 = new THREE.MeshBasicMaterial( { color: 0xFF0000 } );
let p00 = new THREE.Vector3(edges.wtl.x, edges.wtl.y, 0.0);
let p01 = new THREE.Vector3(edges.wbl.x, edges.wbl.y, 0.0);
let p10 = new THREE.Vector3(-Math.sqrt(2.0) * 16.0, edges.wtl.y, 0.0);
let p11 = new THREE.Vector3(-Math.sqrt(2.0) * 16.0, edges.wbl.y, 0.0);
let p20 = new THREE.Vector3(0.0, edges.wtl.y, Math.sqrt(2.0) * 16.0);
let p21 = new THREE.Vector3(0.0, edges.wbl.y, Math.sqrt(2.0) * 16.0);
let p30 = new THREE.Vector3(Math.sqrt(2.0) * 16.0, edges.wtl.y, 0.0);
let p31 = new THREE.Vector3(Math.sqrt(2.0) * 16.0, edges.wbl.y, 0.0);
let p40 = new THREE.Vector3(edges.wtr.x, edges.wtr.y, 0.0);
let p41 = new THREE.Vector3(edges.wbr.x, edges.wbr.y, 0.0);
let sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(p00.x, p00.y, p00.z);
sphere.name = "point00";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(p01.x, p01.y, p01.z);
sphere.name = "point01";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial2);
sphere.position.set(p10.x, p10.y, p10.z);
sphere.name = "point10";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(p20.x, p20.y, p20.z);
sphere.name = "point20";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial2);
sphere.position.set(p30.x, p30.y, p30.z);
sphere.name = "point30";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial2);
sphere.position.set(p11.x, p11.y, p11.z);
sphere.name = "point11";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(p21.x, p21.y, p21.z);
sphere.name = "point21";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial2);
sphere.position.set(p31.x, p31.y, p31.z);
sphere.name = "point31";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(p40.x, p40.y, p40.z);
sphere.name = "point40";
scene.add(sphere);
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(p41.x, p41.y, p41.z);
sphere.name = "point41";
scene.add(sphere);
const material2 = new THREE.LineBasicMaterial({ color: 0x0FF0000 });
let points = [p00, p10, p20, p30, p40];
geometry = new THREE.BufferGeometry().setFromPoints(points);
let topLine = new THREE.Line(geometry, material2);
topLine.name = "topLine";
scene.add(topLine);
points = [p01, p11, p21, p31, p41];
geometry = new THREE.BufferGeometry().setFromPoints(points);
let bottomLine = new THREE.Line(geometry, material2);
bottomLine.name = "bottomLine";
scene.add(bottomLine);
let pf0 = new THREE.Vector3(edges.wtl.x + edges.wtl.distanceTo(edges.wwidth), p00.y, p00.z);
let pf1 = new THREE.Vector3(edges.wbl.x + edges.wtl.distanceTo(edges.wwidth), p01.y, p01.z);
//let pf1 = new THREE.Vector3(edges.wwidth.x * 2, p01.y, p01.z);
points = [p00, pf0, pf1, p01, p00];
geometry = new THREE.BufferGeometry().setFromPoints(points);
let frameLine = new THREE.Line(geometry, material);
frameLine.name = "frame";
scene.add(frameLine);
}
body { margin: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js"></script>
As is in the figure below,
consider a Plane made by camera.position and both end-points(P00 and P40)
and move the points(P10 and P30) to the intersection points of the plane and the edges.
Then, the points would align straight on the screen as expected.
For example, by using ray.intersectPlane(),
let p00 = new THREE.Vector3(edges.wtl.x, edges.wtl.y, 0.0);
let p01 = new THREE.Vector3(edges.wbl.x, edges.wbl.y, 0.0);
//let p10 = new THREE.Vector3(-Math.sqrt(2.0) * 16.0, edges.wtl.y, 0.0);
//let p11 = new THREE.Vector3(-Math.sqrt(2.0) * 16.0, edges.wbl.y, 0.0);
let p10 = new THREE.Vector3(-Math.sqrt(2.0) * 16.0, 16.0, 0.0);
let p11 = new THREE.Vector3(-Math.sqrt(2.0) * 16.0, -16.0, 0.0);
let p20 = new THREE.Vector3(0.0, edges.wtl.y, Math.sqrt(2.0) * 16.0);
let p21 = new THREE.Vector3(0.0, edges.wbl.y, Math.sqrt(2.0) * 16.0);
//let p30 = new THREE.Vector3(Math.sqrt(2.0) * 16.0, edges.wtl.y, 0.0);
//let p31 = new THREE.Vector3(Math.sqrt(2.0) * 16.0, edges.wbl.y, 0.0);
let p30 = new THREE.Vector3(Math.sqrt(2.0) * 16.0, 16.0, 0.0);
let p31 = new THREE.Vector3(Math.sqrt(2.0) * 16.0, -16.0, 0.0);
let p40 = new THREE.Vector3(edges.wtr.x, edges.wtr.y, 0.0);
let p41 = new THREE.Vector3(edges.wbr.x, edges.wbr.y, 0.0);
let nwt = p00.clone().sub(camera.position).cross(p40.clone().sub(camera.position)).normalize();
let nwb = p01.clone().sub(camera.position).cross(p41.clone().sub(camera.position)).normalize();
let planewt = new THREE.Plane(nwt, -nwt.dot(camera.position));
let planewb = new THREE.Plane(nwb, -nwb.dot(camera.position));
let r10 = new THREE.Ray(p10.clone(), p11.clone().sub(p10).normalize());
let r11 = new THREE.Ray(p11.clone(), p10.clone().sub(p11).normalize());
r10.intersectPlane(planewt, p10);
r11.intersectPlane(planewb, p11);
let r30 = new THREE.Ray(p30.clone(), p31.clone().sub(p30).normalize());
let r31 = new THREE.Ray(p31.clone(), p30.clone().sub(p31).normalize());
r30.intersectPlane(planewt, p30);
r31.intersectPlane(planewb, p31);

Evenly distribute a given number of segments along an existing path

without seeing the codepen it is tricky to explain my situation, but here goes. I'm creating some paths by getting pathData using opentype.js. I am then placing random shapes at the position of the path's segment's points. Because of the nature of a font's paths some paths have far more segments than others for example '1' has way fewer segments thant '0'. I would like to average out the number of segments along each path so that when I add the shapes they look a consistent number of segments. Thanks in advance.
Is it possible to evenly distribute a given number of segments along an existing path?
Here is a link to the Codepen
paper.install(window);
const minMax = (min, max) => {
return Math.floor(Math.random() * max + min);
};
window.onload = () => {
paper.setup("canvas");
let pathData;
const font = opentype.load(
"https://assets.codepen.io/1070/pphatton-ultralight-webfont.woff",
(err, font) => {
if (err) {
console.log(err);
} else {
class Doughnut {
constructor(x, y) {
this.x = x;
this.y = y;
this.shape = new paper.Path.RegularPolygon({
position: [this.x, this.y],
sides: minMax(3, 8),
radius: minMax(6, 12),
fillColor: "black"
});
}
// makeShape(){
// this.shape
// }
}
pathData = font.getPath("100", 0, 600, 600).toSVG();
// const rect = new paper.Path.Rectangle({
// point: [80, 25],
// size: [300, 200],
// fillColor: "black"
// });
const number = new paper.Path(pathData);
number.selected = true;
// number.flatten(10);
const amount = 50;
const length = number.length
const points = [];
const segments = number.segments;
number.fitBounds(paper.view.bounds);
for(let i = 0; i < amount; i++){
const offset = i / amount * length
const point = number.getPointAt(offset)
new Doughnut(point.x, point.y);
}
segments.forEach((seg) => {
points.push(number.getPointAt(seg));
});
points.forEach((point) => {
console.log(point);
new Doughnut(point.x, point.y);
});
number.reduce();
}
}
);
const shapes = [];
class Doughnut {
constructor(x, y) {
this.x = x;
this.y = y;
this.shape = new paper.Path.RegularPolygon({
position: [this.x, this.y],
sides: minMax(3, 8),
radius: minMax(6, 12),
fillColor: "black"
});
}
// makeShape(){
// this.shape
// }
}
// for (let i = 0; i < 10; i++) {
// shapes.push(new Doughnut(minMax(100, 500), minMax(100, 500)));
// }
// console.log(shapes)
// shapes.makeShape()
};
The path.divideAt() method can help you greatly.
What is tricky in your case is that, in order to preserve the path appearance, you can't move the existing segments. So you'll have to find a way to only add segment where it is needed.
Otherwise, here's a simple sketch demonstrating a possible solution. It should get you on the track to find a solution more specific to your use case.
const circle = new Path.Circle({
center: [0, 0],
radius: 75,
selected: true
});
const rectangle = new Path.Rectangle({
from: [0, 0],
to: [200, 100],
selected: true
});
rectangle.position = circle.position + [circle.bounds.width + rectangle.bounds.width, 0];
const cloneAndAddSegments = (item) => {
const clone = item.clone().translate(0, 200);
const length = clone.length;
const step = 20;
const iterations = Math.floor(length / step);
for (let i = 1; i <= iterations; i++) {
const offset = i * step;
clone.divideAt(offset);
}
return clone;
};
const circleClone = cloneAndAddSegments(circle);
const rectangleClone = cloneAndAddSegments(rectangle);
const showSegments = (item) => {
item.segments.forEach(({ point }) => new Path.Circle({
center: point,
radius: 5,
fillColor: 'orange'
}))
}
showSegments(circle);
showSegments(rectangle);
showSegments(circleClone);
showSegments(rectangleClone);
project.activeLayer.fitBounds(view.bounds.scale(0.8));

How to rotate a google.maps.LatLngBounds?

I would like to verify if a google.maps.LatLngBounds contains a specific point.
I use a custom overlay like:
export class RotateOverlay extends google.maps.OverlayView {
private _bounds$ = new Subject<google.maps.LatLngBounds>();
get bounds$(): Observable<google.maps.LatLngBounds> {
return this._bounds$.asObservable();
}
private div: HTMLDivElement;
constructor(
private bounds: google.maps.LatLngBounds,
private image: string,
private rotation: number,
private map: google.maps.Map,
private dragEnabled: boolean = false
) {}
onAdd() {
const div = document.createElement('div');
div.style.borderStyle = 'none';
div.style.borderWidth = '0px';
div.style.position = 'absolute';
// Create the img element and attach it to the div.
const img = document.createElement('img');
img.src = this.image;
img.style.width = '100%';
img.style.height = '100%';
img.style.position = 'absolute';
div.appendChild(img);
this.div = div;
// Add the element to the "overlayLayer" pane.
const panes = this.getPanes();
const sw = this.getProjection().fromLatLngToDivPixel(
this.bounds.getSouthWest()
);
const ne = this.getProjection().fromLatLngToDivPixel(
this.bounds.getNorthEast()
);
const bounds = new google.maps.LatLngBounds(
this.getProjection().fromDivPixelToLatLng(
new google.maps.Point(sw.x, sw.y)
),
this.getProjection().fromDivPixelToLatLng(
new google.maps.Point(ne.x, ne.y)
)
);
this._bounds$.next(bounds);
panes.overlayLayer.appendChild(div);
}
draw() {
// We use the south-west and north-east
// coordinates of the overlay to peg it to the correct position and size.
// To do this, we need to retrieve the projection from the overlay.
const overlayProjection = this.getProjection();
// Retrieve the south-west and north-east coordinates of this overlay
// in LatLngs and convert them to pixel coordinates.
// We'll use these coordinates to resize the div.
const sw = overlayProjection.fromLatLngToDivPixel(
this.bounds.getSouthWest()
);
const ne = overlayProjection.fromLatLngToDivPixel(
this.bounds.getNorthEast()
);
// Resize the image's div to fit the indicated dimensions.
const div = this.div;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = ne.x - sw.x + 'px';
div.style.height = sw.y - ne.y + 'px';
div.style.transform = 'rotate(' + this.rotation + 'deg)';
}
}
And in the "parent", I use this to verify if my position is in the bounds:
this.rotateOverlay.bounds$.subscribe((bounds) => {
bounds.contains(this.position);
});
The image is correctly display. but the bounds$ receive the position of the point without the rotation included.
How could I get the correct LatLngBounds including the rotation inside?
Thanks
EDIT 1:
following the advices in comment, I created a polygon which is display uppon my custom overlay. But the rotation is not applied, here is the code:
const sw = this.bounds.getSouthWest();
const ne = this.bounds.getNorthEast();
const coordinates = [
ne,
{ lat: ne.lat(), lng: sw.lng() },
sw,
{ lat: sw.lat(), lng: ne.lng() },
];
//debugger;
var polygon = new google.maps.Polygon({
paths: coordinates,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35
});
polygon.setMap(this.map);
const rotatePolygon = (polygon, angle) => {
var map = polygon.getMap();
var prj = map.getProjection();
var origin = this.bounds.getCenter(); //rotate around first point
var coords = polygon.getPath().getArray().map((latLng) => {
var point = prj.fromLatLngToPoint(latLng);
var rotatedLatLng = prj.fromPointToLatLng(rotatePoint(point, origin, angle));
return { lat: rotatedLatLng.lat(), lng: rotatedLatLng.lng() };
});
polygon.setPath(coords);
return coords;
}
const rotatePoint = (point, origin, angle) => {
var angleRad = angle * Math.PI / 180.0;
return {
x: Math.cos(angleRad) * (point.x - origin.x) - Math.sin(angleRad) * (point.y - origin.y) + origin.x,
y: Math.sin(angleRad) * (point.x - origin.x) + Math.cos(angleRad) * (point.y - origin.y) + origin.y
};
}
debugger;
const coords = rotatePolygon(polygon, this.rotation);
//polygon.setPath(coords);
//polygon.setMap(this.map);
this._polygon$.next(polygon);
panes.overlayLayer.appendChild(div);
and in the parent:
this.rotateOverlay.polygon$.subscribe(polygon => {
this.map.googleMap.addListener("click", (data) => {
const isInsidePolygon = google.maps.geometry.poly.containsLocation(
data.latLng,
polygon
);
[..]
I was really close in my Edit 1, here is the working solution:
before the constructor:
private _polygon$ = new Subject<google.maps.Polygon>();
get polygon$(): Observable<google.maps.Polygon> {
return this._polygon$.asObservable();
}
in onAdd function:
const sw = this.bounds.getSouthWest();
const ne = this.bounds.getNorthEast();
const coordinates = [
ne,
{ lat: ne.lat(), lng: sw.lng() },
sw,
{ lat: sw.lat(), lng: ne.lng() },
];
//debugger;
var polygon = new google.maps.Polygon({
paths: coordinates,
strokeColor: 'transparent', // because I dont want to see it
fillColor: 'transparent', // because I dont want to see it
});
polygon.setMap(this.map);
const rotatePolygon = (polygon, angle) => {
var map = polygon.getMap();
var prj = map.getProjection();
var origin = prj.fromLatLngToPoint(this.bounds.getCenter()); // here was the error in the Edit 1
var coords = polygon.getPath().getArray().map((latLng) => {
var point = prj.fromLatLngToPoint(latLng);
var rotatedLatLng = prj.fromPointToLatLng(rotatePoint(point, origin, angle));
return { lat: rotatedLatLng.lat(), lng: rotatedLatLng.lng() };
});
polygon.setPath(coords);
}
const rotatePoint = (point, origin, angle) => {
var angleRad = angle * Math.PI / 180.0;
return {
x: Math.cos(angleRad) * (point.x - origin.x) - Math.sin(angleRad) * (point.y - origin.y) + origin.x,
y: Math.sin(angleRad) * (point.x - origin.x) + Math.cos(angleRad) * (point.y - origin.y) + origin.y
};
}
rotatePolygon(polygon, this.rotation);
this._polygon$.next(polygon);
and in the parent, to handle the click on the map and in the polygon:
this.rotateOverlay.polygon$.subscribe(polygon => {
if (this.positionDevice) {
polygon.addListener("click", (data) => {
// inside
});
this.map.googleMap.addListener("click", (data) => {
// outside
});
}
});

how to dragging Threejs point

I'm trying to huge graph visualization with threejs r86(latest master version), for showing 600,000 nodes I found a way to draw them faster than using mesh with THREE.points but know I need to make them draggable, after many searches I found raycast to found closest object to mouse point but I have a problem becouse all of taht points are just an object and can not be changed seperately.
function Graph3(Nodes, Edges) {
this.renderer = new THREE.WebGLRenderer({ alpha: true});
var width = window.innerWidth , height = window.innerHeight;
this.renderer.setSize(width, height, false);
document.body.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene(),
this.camera = new THREE.PerspectiveCamera(100, width / height, 0.1, 3000),
this.controls = new THREE.OrbitControls(this.camera);
this.controls.enableKeys = true;
this.controls.enableRotate = false;
var material, geometry;
self = this;
material = new THREE.LineBasicMaterial({color: '#ccc'});
geometry = new THREE.Geometry();
geometry.vertices = Nodes.map(function(item){return new THREE.Vector3(item.pos.x,item.pos.y,item.pos.z);});
// this.vertices = geometry.vertices;
this.line = new THREE.LineSegments(geometry, material);
this.scene.add(this.line);
var Node = new THREE.Group();
material = new THREE.PointsMaterial( { color:0x000060 ,size:1 } );
this.particles = new THREE.Mesh(geometry,material)
this.particles = new THREE.Points( geometry, material);
this.scene.add( this.particles );
dragControls = new THREE.DragControls([this.particles], this.camera/*,this.scene*/, this.renderer.domElement);
this.camera.position.z = 200;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
document.addEventListener( 'click', function ( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
console.log(mouse);
}, false );
stats = new Stats();
document.body.appendChild(stats.dom);
this.animate = function()
{
raycaster.setFromCamera( mouse, self.camera );
var intersections = raycaster.intersectObject( self.particles );
intersection = ( intersections.length ) > 0 ? intersections[ 0 ] : null;
if ( intersection !== null) {
console.log(intersection);
}
requestAnimationFrame( self.animate );
stats.update();
self.renderer.render(self.scene, self.camera);
}
this.animate();}
I had able to change all the points with dragControls but can't move them seperatly
I had found EventsControls.js file which help us to handle events but I couldn't use it
Here you can check how to target individual parts of a buffer geometry with a raycaster:
https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_buffergeometry.html
As for moving them, refer to this question and answer:
How to quickly update a large BufferGeometry?
Thanks for helping me in previous question.
I am making my points in 2d plane (z = 0) and I could making them with bufferGeometry and RawShaderMaterial but now I have another problem in dragging them, how raycaster do? it need vec3 positions but I have changed it for performance purpose.
var Geo = new THREE.BufferGeometry();
var position = new Float32Array( NodeCount * 2 );
var colors = new Float32Array( NodeCount * 3 );
var sizes = new Float32Array( NodeCount );
for ( var i = 0; i < NodeCount; i++ ) {
position[ 2*i ] = (Math.random() - 0.5) * 10;
position[ 2*i + 1 ] = (Math.random() - 0.5) * 10;
colors[ 3*i ] = Math.random();
colors[3*i+1] = Math.random();
colors[3*i+2] = Math.random();
// sizes
sizes[i] = Math.random() * 5 ;
}
Geo.addAttribute( 'position', new THREE.BufferAttribute( position, 2 ) );
Geo.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
Geo.addAttribute( 'size', new THREE.BufferAttribute( sizes, 1 ) );
points = new THREE.Points( Geo, new THREE.RawShaderMaterial({
vertexShader:`
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 cameraPosition;
attribute vec2 position; /// reason of problem
varying vec3 vColor;
attribute vec3 color;
attribute float size;
void main() {
vColor = color;
gl_PointSize = size;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position , 0, 1 );
}`,
fragmentShader:`
precision highp float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4( vColor, 1.0 ) ;
}`
}) );
scene.add( points );
and my using of raycaster:
function mouseDown(e) {
e.preventDefault();
var mouse = new THREE.Vector2();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// mouse.z = 0;
raycaster.setFromCamera(mouse, camera);
raycaster.far = camera.position.z + 3;
const intersect = raycaster.intersectObject(points);
console.log(intersect);
if (intersect.length > 0) {
controls.enabled = false;
console.log(intersect);
selection = intersect[0].index;
}
}
function mouseUp(e) {
controls.enabled = true;
var vector = new THREE.Vector3();
vector.x = (( event.clientX / window.innerWidth ) * 2 - 1);
vector.y = (- ( event.clientY / window.innerHeight ) * 2 + 1);
vector.z = 1.0;
console.log(camera.position.z);
vector.unproject( camera );
var dir = vector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var temp = camera.position.clone().add( dir.multiplyScalar( distance ) );
var pos = points.geometry.attributes.position;
pos.setXY(selection, temp.x, temp.y);
pos.updateRange.offset = selection; // where to start updating
pos.updateRange.count = 1; // how many vertices to update
pos.needsUpdate = true;
selection = undefined;
}

three.js and confetti

I'm trying to make a confetti explosion and I'm having issues with projecting the confetti out. My idea is to have a fast explosion outwards in all directions (1 sec) then the confetti floats to the ground. I'm sure my math is wrong because I'm not getting it to expand.
I've taken three.js code and made some mods:
http://givememypatientinfo.com/ParticleBlocksConfetti.html
Any suggestions are welcome. I'm a noob at the three.js... but love the library!
Code:
var container, stats;
var camera, controls, scene, projector, renderer;
var objects = [], plane;
var vel = 1;
var vel2 = 0.01;
var accel = .3;
var accel2 = -.9;
var force = 1;
var frame = 0;
var mouse = new THREE.Vector2(),
offset = new THREE.Vector3(),
INTERSECTED, SELECTED;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
/*//controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;*/
scene = new THREE.Scene();
scene.add( new THREE.AmbientLight( 0x505050 ) );
var light = new THREE.SpotLight( 0xffffff, 1.5 );
light.position.set( 0, 500, 2000 );
light.castShadow = true;
light.shadowCameraNear = 200;
light.shadowCameraFar = camera.far;
light.shadowCameraFov = 50;
light.shadowBias = -0.00022;
light.shadowDarkness = 0.5;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
scene.add( light );
var geometry = new THREE.CubeGeometry( 40, 40, 40 );
//make confetti for particle system
for ( var i = 0; i < 100; i ++ ) {
var object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) );
//object.material.ambient = object.material.color;
/*object.position.x = Math.random() * 500 - 100;
object.position.y = Math.random() * 500 - 100;
object.position.z = 300;*/
object.position.x = Math.random() * 100 - 100;
object.position.y = Math.random() * 100 - 100;
object.position.z = 300;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = .1;
object.scale.y = Math.random() * .8 + .1;
object.scale.z = Math.random() * .5 + .1;
object.castShadow = false;
object.receiveShadow = true;
scene.add( object );
objects.push( object );
}
plane = new THREE.Mesh( new THREE.PlaneGeometry( 2000, 2000, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0.25, transparent: true, wireframe: true } ) );
plane.visible = false;
scene.add( plane );
projector = new THREE.Projector();
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.sortObjects = false;
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMapEnabled = true;
renderer.shadowMapType = THREE.PCFShadowMap;
container.appendChild( renderer.domElement );
var info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = 'three.js webgl - draggable cubes';
container.appendChild( info );
}
function animate_particles(frame) {
//will update each particle
if (frame < 50){
var pCount = objects.length-1;
if (frame < 40){
vel += accel*2;
}else {
vel = vel + accel2;
}
while(pCount > -1) {
if (frame < 30){
objects[pCount].position.y += vel;
}else{
objects[pCount].position.y -= vel;
}
//objects[pCount].rotation.x += Math.random()*.7;
//objects[pCount].rotation.z += Math.random()*.01;
//objects[pCount].rotation.y += Math.random()*.01;
pCount--;
}
}
}
function animate() {
requestAnimationFrame( animate );
animate_particles(frame);
render();
//stats.update();
}
function render() {
renderer.render( scene, camera );
frame++;
}
</script>
This could be what you were trying to archieve. I modified your code a little bit and commented the changes. Basically I just added a random direction vector, normalized it and added a random speed to the particles. In the animate_particles function, I am moving the confetti along the random direction vector at the random speed.
var container, stats;
var camera, controls, scene, projector, renderer;
var objects = [], plane;
var vel = 1;
var vel2 = 0.01;
var accel = .3;
var accel2 = -.9;
var force = 1;
var frame = 0;
var mouse = new THREE.Vector2(),
offset = new THREE.Vector3(),
INTERSECTED, SELECTED;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
/*//controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;*/
scene = new THREE.Scene();
scene.add( new THREE.AmbientLight( 0x505050 ) );
var light = new THREE.SpotLight( 0xffffff, 1.5 );
light.position.set( 0, 500, 2000 );
light.castShadow = true;
light.shadowCameraNear = 200;
light.shadowCameraFar = camera.far;
light.shadowCameraFov = 50;
light.shadowBias = -0.00022;
light.shadowDarkness = 0.5;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
scene.add( light );
var geometry = new THREE.CubeGeometry( 40, 40, 40 );
//make confetti for particle system
for ( var i = 0; i < 100; i ++ ) {
var object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) );
//object.material.ambient = object.material.color;
/*object.position.x = Math.random() * 500 - 100;
object.position.y = Math.random() * 500 - 100;
object.position.z = 300;*/
object.position.x = Math.random() * 100 - 100;
object.position.y = Math.random() * 100 - 100;
object.position.z = 300;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = .1;
object.scale.y = Math.random() * .8 + .1;
object.scale.z = Math.random() * .5 + .1;
// give every "particle" a random expanding direction vector and normalize it to receive a length of 1.
object.directionVector = new THREE.Vector3( Math.random() - .5, Math.random() - .5, Math.random() - .5 )
object.directionVector.normalize();
// and a random expanding Speed
object.expandingSpeed = Math.random() * 100;
object.castShadow = false;
object.receiveShadow = true;
scene.add( object );
objects.push( object );
}
plane = new THREE.Mesh( new THREE.PlaneGeometry( 2000, 2000, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0.25, transparent: true, wireframe: true } ) );
plane.visible = false;
scene.add( plane );
projector = new THREE.Projector();
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.sortObjects = false;
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMapEnabled = true;
renderer.shadowMapType = THREE.PCFShadowMap;
container.appendChild( renderer.domElement );
var info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = 'three.js webgl - draggable cubes';
container.appendChild( info );
}
function animate_particles(frame) {
//will update each particle
if (frame < 50){
var pCount = objects.length-1;
if (frame < 40){
vel += accel*2;
}else {
vel = vel + accel2;
}
while(pCount > -1) {
if (frame < 30){
// commented that out. not sure why you put it there.
//objects[pCount].position.y += vel;
// move objects along random direction vector at the individual random speed.
objects[pCount].position.x += objects[pCount].directionVector.x * objects[pCount].expandingSpeed;
objects[pCount].position.y += objects[pCount].directionVector.y * objects[pCount].expandingSpeed;
objects[pCount].position.z += objects[pCount].directionVector.z * objects[pCount].expandingSpeed;
}else{
objects[pCount].position.y -= vel;
}
//objects[pCount].rotation.x += Math.random()*.7;
//objects[pCount].rotation.z += Math.random()*.01;
//objects[pCount].rotation.y += Math.random()*.01;
pCount--;
}
}
}
function animate() {
requestAnimationFrame( animate );
animate_particles(frame);
render();
//stats.update();
}
function render() {
renderer.render( scene, camera );
frame++;
}
I've just made a plugin for create Confetti Effect with THREE.js: https://github.com/mrgoonie/three.confetti.explosion.js
Things would be easier with:
var confetti = new ExplosionConfetti({
rate: 1, // percent of explosion in every tick - smaller is fewer - be careful, larger than 10 may crash your browser!
amount: 200, // max amount particle of an explosion
radius: 800, // max radius of an explosion
areaWidth: 500, // width of the area
areaHeight: 500, // height of the area
fallingHeight: 500, // start exploding from Y position
fallingSpeed: 1, // max falling speed
colors: [0xffffff, 0xff0000, 0xffff00] // random colors
});
scene.add( confetti.object );
Cheers,

Resources