Autodesk Forge: programmatically zoom out after focus on object by fitToView() - autodesk-forge

After select and focusing on an object with fitToView(objectIds, model),
I want to zoom out the camera programatically instead of via mouse events.
I tried the following code and it was able to zoom out, but the 3D space was distorted.
If you know how to correctly zoom out, please let me know.
// select and isolate target object
let dbid = [1137]
viewer.impl.visibilityManager.isolate(dbid, viewer.model)
viewer.select(dbid)
//focus on target object
viewer.getAggregateSelection((data) => {
let rootId = data.selector.getInstanceTree().nodeAccess.rootId
viewer.fitToView(rootId, viewer.model)
})
//zoom out camera
let cam = viewer.getCamera().perspectiveCamera
cam.zoom *= 0.05
viewer.impl.syncCamera();

It's not the zoom property that you need to change but the camera position:
var nav = viewer.navigation
var pos = nav.getPosition()
var target = nav.getTarget()
var viewdir = new THREE.Vector3()
viewdir.subVectors (pos, target).normalize()
// zooms out by 100 along the view direction
viewdir.multiplyScalar (100)
pos.add(viewdir)
nav.setPosition(pos)

It's not the zoom property that you need to change but set the camera position:
let camera = this.viewer3D.getCamera();
viewer.fitToView(dbIds, null, true); //provide your device id to locate
var direction = new THREE.Vector3();
camera.getWorldDirection( direction );
//set distance move the camera forward to your needs (gives value in minus for zoomout)
camera.position.add( direction.multiplyScalar(-15));
let fov = viewer.navigation.getVerticalFov(); //or horizontalFov
viewer.navigation.setRequestTransition(true, camera.position,viewer.navigation.getTarget(), fov);
//if not work then try fov + 20,30,40 etc..

Related

How to draw shapes in correct position in godot engine?

I am trying to make a selection tool. The collision shape is drawn correctly, as you can see in form of the filled box. While the mouse is dragged, I want to draw a green rectangle. However, the box is drawn in the wrong place. What am I missing?
extends Area2D
onready var shape: Shape2D = $CollisionShape2D.shape
onready var collision: CollisionShape2D = $CollisionShape2D
export (Color, RGBA) var color = Color.green
export var stroke_width: float = 1.0
export (bool) var isFilled = true
var startOfDrag: Vector2
var endOfDrag: Vector2
var width: int = 1
var height: int = 1
var isMouseDragged: bool = false
var points: PoolVector2Array
func _process(delta: float) -> void:
if isMouseDragged:
update()
func _draw():
draw_polyline(points , color, stroke_width, isFilled)
func _input(event: InputEvent) -> void:
if Input.is_action_just_pressed("left_mouse_down") and not isMouseDragged:
startOfDrag = get_global_mouse_position()
position = startOfDrag
shape.set_extents(Vector2(1, 1))
isMouseDragged = true
if Input.is_action_just_released("left_mouse_down"):
isMouseDragged = false
handleSelection()
endOfDrag = get_global_mouse_position()
width = abs(startOfDrag.x - endOfDrag.x)
height = abs(startOfDrag.y - endOfDrag.y)
change_rectangle()
func handleSelection() -> void:
shape.set_extents(Vector2(width/2, height/2))
collision.position = Vector2(width/2, height/2)
func change_rectangle() -> void:
points = []
points.append(Vector2(startOfDrag.x, startOfDrag.y))
points.append(Vector2(startOfDrag.x + width, startOfDrag.y))
points.append(Vector2(startOfDrag.x + width, startOfDrag.y + height))
points.append(Vector2(startOfDrag.x, startOfDrag.y + height))
points.append(Vector2(startOfDrag.x, startOfDrag.y))
This is what I see:
startOfDrag = get_global_mouse_position()
position = startOfDrag
The mouse position is in global coordinates. While the position of your object is in local coordinates (you can think of it as coordinates relative to the parent).
Set the global_position instead:
startOfDrag = get_global_mouse_position()
global_position = startOfDrag
By the way, the method to_local converts global coordinates to local coordinates of the node on which you call it. Similarly, there is a to_global method that will convert local coordinates of the node on which you call it to global coordinates.
Keep track of which vectors are in global coordinates and which are in local coordinates.
If there is scaling applied your width and height might also not match. If you run into that, you should be able to fix it with to_local of the CollisionShape2D.

crop particular room/area from the forge viewer based on Revit coordinates(min and max X, Y, Z)

We are using forge viewer(v7) in our web application.
Our requirement is to crop particular room/area from the forge viewer. For example, if we have shown a house model in the forge viewer then if a user select a kitchen(from menu or navbar) then the viewer should show only kitchen area (including all its objects like cabinets, burner, fridge, sink etc.) and all other objects/sections should be hidden. Similarly for bedrooms, baths etc. It will be just for viewing purpose at run time and not for any automation.
We are getting room coordinates(min and max X, Y, Z) with the help of following using forge API(with Revit engine).
GeometryElement geoElement = room.ClosedShell;
BoundingBoxXYZ boundingBox = geoElement.GetBoundingBox();
XYZ min = boundingBox.Min;
XYZ max = boundingBox.Max;
We are using viewer.setCutPlanes function to draw cutplanes in viewer.
var minPt = new THREE.Vector3(x,y,z); //!<<< put your point here
var maxPt = new THREE.Vector3(x,y,z); //!<<< put your point here
const normals = [
new THREE.Vector3(1, 0, 0),
new THREE.Vector3(0, 1, 0),
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(-1, 0, 0),
new THREE.Vector3(0, -1, 0),
new THREE.Vector3(0, 0, -1)
];
const bbox = new THREE.Box3(minPt, maxPt);
const cutPlanes = [];
for (let i = 0; i < normals.length; i++) {
const plane = new THREE.Plane(normals[i], -1 * maxPt.dot(normals[i]));
// offset plane with negative normal to form an octant
if (i > 2) {
const ptMax = plane.orthoPoint(bbox.max);
const ptMin = plane.orthoPoint(bbox.min);
const size = new THREE.Vector3().subVectors(ptMax, ptMin);
plane.constant -= size.length();
}
const n = new THREE.Vector4(plane.normal.x, plane.normal.y, plane.normal.z, plane.constant);
cutPlanes.push(n);
}
viewer.setCutPlanes(cutPlanes);
But when we are passing these coordinates (obtained for API) to this front end JS code the cutPlanes are getting created at incorrect position/points. For example when we are passing coordinates of kitchen its cropping the small portion of roof and same with all other room.
The possible reason is that the Revit & forge viewer coordinates are not same.
Does anyone have an idea that how can we map these Revit coordinates with forge viewer and draw cutplanes?
If you're following the Forge Viewer tutorial to load the model, then you need to subtract global offsets from endpoints of the room bounding box like below:
var minPt = new THREE.Vector3(x,y,z); //!<<< put your point here
var maxPt = new THREE.Vector3(x,y,z); //!<<< put your point here
var offsetMatrix = viewer.model.getData().placementWithOffset;
var offsetMinPt = minPt.applyMatrix4(offsetMatrix);
var offsetMaxPt = maxPt.applyMatrix4(offsetMatrix);
i have another solution. modify model by hand like cut plane, hide, isolate element to retrieve view you want to show. Then use method var data = viewer.getState() and store that data to your database. then use viewer.restoreState(data) to recall your view.

Controlling camera in Forge viewer

I'm trying to control the camera in the Autodesk Forge Viewer. Setting target and position seems to work fine, but if I try to set rotation or quaternion it do not have any effect.
To get the camera I use the getCamera function and then applyCamera after I have tried to set the parameters.
What I'm trying to achieve is to use the device orientation on a handheld device to rotate the model. Just using alpha and beta to set target is not a smooth experience.
// get camera
var cam = _viewer.getCamera();
// get position
var vecPos = cam.position;
// get view vector
var vecViewDir = new THREE.Vector3();
vecViewDir.subVectors(cam.target,cam.position);
// get length of view vector
var length = vecViewDir.length();
// rotate alpha
var vec = new THREE.Vector3();
vec.y = length;
var zAxis = new THREE.Vector3(0,0,1);
vec.applyAxisAngle(zAxis,THREE.Math.degToRad(alpha));
// rotate beta
var vec2 = new THREE.Vector3(vec.x,vec.y,vec.z);
vec2.normalize();
vec2.negate();
vec2.cross(zAxis);
vec.applyAxisAngle(vec2,THREE.Math.degToRad(beta) + Math.PI / 2);
// add to camera
cam.target.addVectors(vecPos,vec);
_viewer.applyCamera(cam,false);
You need to use the setView() method
_viewer.navigation.setView (pos, target) ;
and may also need to set the up vector to make sure you orient the camera the way you want.
_viewer.navigation.setCameraUpVector (upVector) ;

Dynamic google map with custom tiles prevent repeating pan

I have a dynamic tile set where I do NOT want to allow panning outside of its bounds.
The below code gets me close, but the user can still scroll horizontally outside of strict bounds because it uses the map center for comparison
var strictBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(sw_lat, sw_lon),
new google.maps.LatLng(ne_lat, ne_lon)
);
google.maps.event.addListener(map, 'drag', function()
{
if (strictBounds.contains(map.getCenter())) return;
// We're out of bounds - Move the map back within the bounds
var c = map.getCenter(),
x = c.lng(),
y = c.lat(),
maxX = strictBounds.getNorthEast().lng(),
maxY = strictBounds.getNorthEast().lat(),
minX = strictBounds.getSouthWest().lng(),
minY = strictBounds.getSouthWest().lat();
if (x < minX) x = minX;
if (x > maxX) x = maxX;
if (y < minY) y = minY;
if (y > maxY) y = maxY;
map.setCenter(new google.maps.LatLng(y, x));
});
For quick access, here is the jsfiddle for this solution: http://jsfiddle.net/nYz6k/
Supposed you're happy with the current solution of detecting bounds against the center, all you have to do is to restrict the bounds based on the current map canvas size.
The result that you're getting now is that every corner of the restricting bounds appears on the center of the map when it's full restricting (i.e. both latitude and longitude exceed the bounds corners).
The solution is to actually remove the x and y offset from the bounds, and still be able to check against the center (which is the most efficient solution, compared to other bounds-based checking solutions).
Also, you have to restrict the bounds only when you initialize the map and when the window is resizing, which means that when you pan there is no extra processing overhead besides the check method that you have already provided.
Don't forget to set the maxZoom property for the map (tweak it as needed) because above a determined zoom level the restricting bounds will by themselves fit in the viewport and there's no solution for that.
Important! Use 'center_changed' instead of 'drag' because 'drag' has that sliding behavior that when you finished dragging and set the center, the maps still slides in the direction of the panning.
Before you implement the solution I recommend you to check out how the Maps API coordinate system and projection works, because this solution is heavily based on it, and if you want to tweak it out, it's useful to know this information.
https://developers.google.com/maps/documentation/javascript/maptypes and check out the Custom Map Types -> Map Coordinates section.
Here's what you need to do. First, you need to implement 2 short methods of converting between geographic coordinates (LatLng) and pixel coordinates.
var fromLatLngToPixel = function (latLng) {
var point = map.getProjection().fromLatLngToPoint(latLng);
var zoom = map.getZoom();
return new google.maps.Point(
Math.floor(point.x * Math.pow(2, zoom)),
Math.floor(point.y * Math.pow(2, zoom))
);
}
var fromPixelToLatLng = function (pixel) {
var zoom = map.getZoom();
var point = new google.maps.Point(
pixel.x / Math.pow(2, zoom),
pixel.y / Math.pow(2, zoom)
);
return map.getProjection().fromPointToLatLng(point);
}
Next, implement the method that effectively restricts the bounds. Note that you always have to keep a variable that stores the original bounds, because each time the map canvas is resized, the resulting bounds will change.
For this, let's say originalBounds keeps the bounds you provided with sw_lat, sw_long etc, and shrinkedBounds is modified by this method. You can rename it to strictBounds to still work in your method, but it's up to you. This uses jQuery for getting the width and height of the canvas object.
var shrinkBounds = function () {
zoom = map.getZoom();
// The x and y offset will always be half the current map canvas
// width and height respectively
xoffset = $('#map_canvas').width() / 2;
yoffset = $('#map_canvas').height() / 2;
// Convert the bounds extremities to global pixel coordinates
var pixswOriginal = fromLatLngToPixel(originalBounds.getSouthWest());
var pixneOriginal = fromLatLngToPixel(originalBounds.getNorthEast());
// Shrink the original bounds with the x and y offset
var pixswShrinked = new google.maps.Point(pixswOriginal.x + xoffset, pixswOriginal.y - yoffset);
var pixneShrinked = new google.maps.Point(pixneOriginal.x - xoffset, pixneOriginal.y + yoffset);
// Rebuild the shrinkedBounds object with the modified
shrinkedBounds = new google.maps.LatLngBounds(
fromPixelToLatLng(pixswShrinked),
fromPixelToLatLng(pixneShrinked));
}
Next, all you have to do is to call this method:
once when the map is initialized. Be aware that depending on when you call the method, you may be getting some weird errors because the map might not have yet initialized all it's properties. Best way is to use the projection_changed event.
google.maps.event.addListener(map, 'projection_changed', function (e) {
shrinkBounds();
});
every time the map canvas is resized
google.maps.event.addListener(map, 'resize', function (e) {
shrinkBounds();
});
But the most important part of this is that the 'resize' event is never triggered for programmatic resizes of the map container, so you have to trigger it manually every time you resize the canvas programmatically (if you do, but I doubt you do).
The most common way is to trigger it when the window is resized:
$(window).resize(function () {
google.maps.event.trigger(map, 'resize');
});
After all of this, you can now safely use your method, but as a handler for 'center_changed' and not for 'drag' as I've mentioned earlier.
I set up a jsfiddle with the full working code.
http://jsfiddle.net/nYz6k/
You can see the restricting at the bottom left corner with that little half-marker that shows up, which is positioned in the SW corner of the bounds. There is also one at the NW corner but naturally you can't see it because it's displayed above the position.
Instead of using map.getCenter() as a check, use map.getBounds()
Untested, but a simple way to accomplish this would be to check if the union of both bounds were identical (if not, your map bounds is outside your strict bounds):
var union = strictBounds.union(map.getBounds());
if (strictBounds.equals(union)) { //new map bounds is within strict bounds
Tiborg version above is the best version so far in keeping the bounds...
I noticed a bug that occurs when zoom level and width/height are larger than original bounds
I have modified his code and added a rectangle to see it in action.
var buildBounds = function(sw, ne) {
var swY = sw.lat();
var swX = sw.lng();
var neY = ne.lat();
var neX = ne.lng();
if (swY > neY) {
var cY = (swY + neY) / 2;
swY = cY;
neY = cY;
}
if (swX > neX) {
var cX = (swX + neX) / 2;
swX = cX;
neX = cX;
}
return new google.maps.LatLngBounds(
new google.maps.LatLng(swY, swX),
new google.maps.LatLng(neY, neX));
}
http://jsfiddle.net/p4wgjs6s/

How to set google map custom zoom level dynamically?

I am working with google map. According to requirements i need to set different zoom level that is dependent to my search query.If there are multiple location on the map in country then map should focus the country. Other scenario is , if there are different locations marked in a city then map should be focused to city level.
var geoCoder = new GClientGeocoder();
geoCoder.setViewport(map.getBounds());
geoCoder.getLocations('searchquery', function(latlng) {
if( latlng.Placemark.length > 0 ) {
var box = latlng.Placemark[0].ExtendedData.LatLonBox;
var bounds = new GLatLngBounds(new GLatLng(box.south, box.west), new GLatLng(box.north, box.east));
var center = new GLatLng(box.Placemark[0].Point.coordinates[1], latlng.Placemark[0].Point.coordinates[0]);
var zoom = oMap.getBoundsZoomLevel(bounds);
map.setCenter(center, zoom);
}
});
I think the key part of this for you is
//box is a LatLonBox with the size of your resultquery. You can create this yourself as well
var box = latlng.Placemark[0].ExtendedData.LatLonBox;
//bounds are the bounds of the box
var bounds = new GLatLngBounds(new GLatLng(box.south, box.west), new GLatLng(box.north, box.east));
//center is the center of the box, you want this as the center of your screen
var center = new GLatLng(box.Placemark[0].Point.coordinates[1], latlng.Placemark[0].Point.coordinates[0]);
//zoom is the zoomlevel you need for all this
var zoom = oMap.getBoundsZoomLevel(bounds);
//the actual action
map.setCenter(center, zoom);
After you perform the search, once you have the locations, you can use bounds.extend and map.fitBounds so the map automatically zooms to show all the pins returned by your search, like this:
//places is an array that contains the search result
var bounds = new google.maps.LatLngBounds();
for (var i = 0, place; place = places[i]; i++) {
//pass the location of each place to bounds.extend
bounds.extend(place.geometry.location);
}
//tell your map to sets the viewport to contain all the places.
map.fitBounds(bounds);
On the other hand, if you do a search for a zone like zip code, city or country using the geo coder, you can also use map.fitBounds to set the viewport to show the entire specific zone that was returned by the geo coder, like this:
//response is the geocoder's response
map.fitBounds(response[0].geometry.viewport);
Here's a codepen with the geocoder example https://codepen.io/jaireina/pen/gLjEqY
I found following article very helpful. using code sample code I can able to achieveenter link description here this.
I tried the code in drupal and it is working.