How to change the zoom centerpoint in an ILNumerics scene viewed with a camera - ilnumerics

I would like to be able to zoom into an ILNumerics scene viewed by a camera (as in scene.Camera) with the center point of the zoom determined by where the mouse pointer is located when I start spinning the mouse scroll wheel. The default zoom behavior is for the zoom center to be at the scene.Camera.LookAt point. So I guess this would require the mouse to be tracked in (X,Y) continuously and for that point to be used as the new LookAt point? This seems to be like this post on getting the 3D coordinates from a mouse click, but in my case there's no click to indicate the location of the mouse.
Tips would be greatly appreciated!
BTW, this kind of zoom method is standard operating procedure in CAD software to zoom in and out on an assembly of parts. It's super convenient for the user.

One approach is to overload the MouseWheel event handler. The current coordinates of the mouse are available here, too.
Use the mouse screen coordinates to acquire (to "pick") the world
coordinate corresponding to the primitive under the mouse.
Adjust the Camera.Position and Camera.ZoomFactor to 'move' the camera closer to the point under the mouse and to achieve the required 'directional zoom' effect.
Here is a complete example from the ILNumerics website:
using System;
using System.Windows.Forms;
using ILNumerics;
using ILNumerics.Drawing;
using ILNumerics.Drawing.Plotting;
using static ILNumerics.Globals;
using static ILNumerics.ILMath;
namespace ILNumerics.Examples.DirectionalZoom {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void panel2_Load(object sender, EventArgs e) {
Array<float> X = 0, Y = 0, Z = CreateData(X, Y);
var surface = new Surface(Z, X, Y, colormap: Colormaps.Winter);
surface.UseLighting = true;
surface.Wireframe.Visible = false;
panel2.Scene.Camera.Add(surface);
// setup mouse handlers
panel2.Scene.Camera.Projection = Projection.Orthographic;
panel2.Scene.Camera.MouseDoubleClick += Camera_MouseDoubleClick;
panel2.Scene.Camera.MouseWheel += Camera_MouseWheel;
// initial zoom all
ShowAll(panel2.Scene.Camera);
}
private void Camera_MouseWheel(object sender, Drawing.MouseEventArgs e) {
// Update: added comments.
// the next conditionals help to sort out some calls not needed. Helpful for performance.
if (!e.DirectionUp) return;
if (!(e.Target is Triangles)) return;
// make sure to start with the SceneSyncRoot - the copy of the scene which receives
// user interaction and is eventually used for rendering. See: https://ilnumerics.net/scene-management.html
var cam = panel2.SceneSyncRoot.First<Camera>();
if (Equals(cam, null)) return; // TODO: error handling. (Should not happen in regular setup, though.)
// in case the user has configured limited interaction
if (!cam.AllowZoom) return;
if (!cam.AllowPan) return; // this kind of directional zoom "comprises" a pan operation, to some extent.
// find mouse coordinates. Works only if mouse is over a Triangles shape (surfaces, but not wireframes):
using (var pick = panel2.PickPrimitiveAt(e.Target as Drawable, e.Location)) {
if (pick.NextVertex.IsEmpty) return;
// acquire the target vertex coordinates (world coordinates) of the mouse
Array<float> vert = pick.VerticesWorld[pick.NextVertex[0], r(0, 2), 0];
// and transform them into a Vector3 for easier computations
var vertVec = new Vector3(vert.GetValue(0), vert.GetValue(1), vert.GetValue(2));
// perform zoom: we move the camera closer to the target
float scale = Math.Sign(e.Delta) * (e.ShiftPressed ? 0.01f : 0.2f); // adjust for faster / slower zoom
var offs = (cam.Position - vertVec) * scale; // direction on the line cam.Position -> target vertex
cam.Position += offs; // move the camera on that line
cam.LookAt += offs; // keep the camera orientation
cam.ZoomFactor *= (1 + scale);
// TODO: consider adding: the lookat point now moved away from the center / the surface due to our zoom.
// In order for better rotations it makes sense to place the lookat point back to the surface,
// by adjusting cam.LookAt appropriately. Otherwise, one could use cam.RotationCenter.
e.Cancel = true; // don't execute common mouse wheel handlers
e.Refresh = true; // immediate redraw at the end of event handling
}
}
private void Camera_MouseDoubleClick(object sender, Drawing.MouseEventArgs e) {
var cam = panel2.Scene.Camera;
ShowAll(cam);
e.Cancel = true;
e.Refresh = true;
}
// Some sample data. Replace this with your own data!
private static RetArray<float> CreateData(OutArray<float> Xout, OutArray<float> Yout) {
using (Scope.Enter()) {
Array<float> x_ = linspace<float>(0, 20, 100);
Array<float> y_ = linspace<float>(0, 18, 80);
Array<float> Y = 1, X = meshgrid(x_, y_, Y);
Array<float> Z = abs(sin(sin(X) + cos(Y))) + .01f * abs(sin(X * Y));
if (!isnull(Xout)) {
Xout.a = X;
}
if (!isnull(Yout)) {
Yout.a = Y;
}
return -Z;
}
}
// See: https://ilnumerics.net/examples.php?exid=7b0b4173d8f0125186aaa19ee8e09d2d
public static double ShowAll(Camera cam) {
// Update: adjusts the camera Position too.
// this example works only with orthographic projection. You will need to take the view frustum
// into account, if you want to make this method work with perspective projection also. however,
// the general functioning would be similar....
if (cam.Projection != Projection.Orthographic) {
throw new NotImplementedException();
}
// get the overall extend of the cameras scene content
var limits = cam.GetLimits();
// take the maximum of width/ height
var maxExt = limits.HeightF > limits.WidthF ? limits.HeightF : limits.WidthF;
// make sure the camera looks at the unrotated bounding box
cam.Reset();
// center the camera view
cam.LookAt = limits.CenterF;
cam.Position = cam.LookAt + Vector3.UnitZ * 10;
// apply the zoom factor: the zoom factor will scale the 'left', 'top', 'bottom', 'right' limits
// of the view. In order to fit exactly, we must take the "radius"
cam.ZoomFactor = maxExt * .50;
return cam.ZoomFactor;
}
}
}
Note, that the new handler performs the directional zoom only when the mouse is located over an object hold by this Camera! If, instead, the mouse is placed on the background of the scene or over some other Camera / plot cube object no effect will be visible and the common zoom feature is performed (zooming in/out to the look-at point).

Related

Unity3D - Move camera perpendicular to where it's facing

I'm adding the option for players to move the camera to the sides. I also want to limit how far they can move the camera to the sides.
If the camera was aligned with the axis, I could simply move around X/Z axis and set a limit on each axis as to how far it can go. But my problem is that the camera is rotated, so I'm stuck figuring out how to move it and set a limit. How could I implement this?
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class CameraController : MonoBehaviour
{
Camera cam;
Vector3 dragOrigin;
bool drag = false;
void Awake()
{
cam = GetComponent<Camera>();
}
void LateUpdate()
{
// Camera movement with mouse
Vector3 diff = (cam.ScreenToWorldPoint(Input.mousePosition)) - cam.transform.position;
if (Input.GetMouseButton(0))
{
if (drag == false)
{
drag = true;
dragOrigin = cam.ScreenToWorldPoint(Input.mousePosition);
}
}
else
{
drag = false;
}
if (drag)
{
// Here I want to set a constraint in a rectangular plane perpendicular to camera view
transform.position = dragOrigin - diff;
}
}
}
Transform in Unity comes with a handy Transform.right property, which regards the object's rotation. To move your camera sideways you could further utilize Lerp to make the movement smooth.
transform.position += transform.right * factor
moves an object to the right.
Use factor to adjust the desired distance and by doing so you can also set limits. Negative factor would mean moving left by the way:) Hope that helps!
It can be tricky to deal with constraints on rotated objects. The math behind this includes some vector/rotation math to figure out the correct limits relative to the object's orientation, and whether you've exceeded them.
Luckily though, Unity gives you some shortcuts to skip this math: Transform.InverseTransformPoint() and Transform.TransformPoint()! These two methods allow you to transform a point in world space into a point in local space, and vice versa.
That means that no matter how your camera is oriented, you can interpret a position from the orientation of the camera - and with just a couple extra steps, your X/Z constraints are usable because you can calculate X/Z from the camera's point of view.
Let's try to adapt your current script to use this:
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class CameraController : MonoBehaviour
{
// Set the X and Z values in the editor to define the rectangle within
// which your camera can move
public Vector3 maxConstraints;
public Vector3 minConstraints;
Camera cam;
Vector3 dragOrigin;
bool drag = false;
Vector3 cameraStart;
void Awake()
{
cam = GetComponent<Camera>();
// Here, we record the start since we'll need a reference to determine
// how far the camera has moved within the allowed rectangle
cameraStart = transform.position;
}
void LateUpdate()
{
// Camera movement with mouse
Vector3 diff = (cam.ScreenToWorldPoint(Input.mousePosition)) - cam.transform.position;
if (Input.GetMouseButton(0))
{
if (drag == false)
{
drag = true;
dragOrigin = cam.ScreenToWorldPoint(Input.mousePosition);
}
}
else
{
drag = false;
}
if (drag)
{
// Now, rather than setting the position directly, let's make sure it's
// within the valid rectangle first
Vector3 newPosition = dragOrigin - diff;
// First, we get into the local space of the camera and determine the delta
// between the start and possible new position
Vector3 localStart = transform.InverseTransformPoint(cameraStart);
Vector3 localNewPosition = transform.InverseTransformPoint(newPosition);
Vector3 localDelta = localNewPosition - localStart;
// Now, we calculate constrained values for the X and Z coordinates
float clampedDeltaX = Mathf.Clamp(localDelta.x, minConstraint.x, maxConstraint.x);
float clampedDeltaZ = Mathf.Clamp(localDelta.z, minConstraint.z, maxConstraint.z);
// Then, we can use the constrained values to determine the constrained position
// within local space
Vector3 localClampedPosition = new Vector3(clampedDeltaX, localDelta.y, clampedDeltaZ)
+ localStart;
// Finally, we can convert the local position back to world space and use it
transform.position = transform.TransformPoint(localConstrainedPosition);
}
}
}
Note that I'm somewhat assuming dragOrigin - diff moves your camera correctly in its present state. If it doesn't do what you want, please include details on the unwanted behaviour and we can sort that out too.

Unity 3D fps view - objects placing at center point

I've searched around and can't seem to find a write up on the calculations I might need for placing an object onto another one without them placing halfway inside of eachother.
private void MovePlaceableObject()
{
Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);
Debug.DrawRay(ray.origin, ray.direction * 20f);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, 15f))
{
Vector3 newPosition = hit.point;
currentPlaceableObject.transform.position = newPosition; //move object to where we have the mouse
currentPlaceableObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal); //rotation equal to the current rotation of our hitinfo, then up
}
}
private void HandeNewObjectHotkey()
{
if (Input.GetKeyDown(newObjectHotkey))
{
if(currentPlaceableObject == null)
{
currentPlaceableObject = Instantiate(placeableObjectPrefab);
//currentPlaceableObject.GetComponent<BoxCollider>().enabled = false;
}
else
{
Destroy(currentPlaceableObject); //pressing the button again will destroy current object
}
}
}
I've tried storing the object I'm looking at's size to floats for x,y,z and a vector3 for it's position, and tried fiddling around with adding those to the newPosition with the hit.point to no avail.
I've wracked my brains for about a day now, i'm terrible at math, however I know that you must need to get the current object your looking ats position or size, then factor that into your placement right?
Get the size of the object that you are looking at by accessing its collider component. You can use the bounds property of the collider to get its size.
Bounds objectBounds = hit.collider.bounds;
Calculate the center point of the object by adding half of its size to its position.
Vector3 objectCenter = hit.collider.transform.position + objectBounds.extents;
Calculate the position of the new object by adding the size of the new object to the center point of the object that you are looking at. This will place the new object on top of the other object.
Bounds newObjectBounds = currentPlaceableObject.GetComponent<Collider>().bounds;
Vector3 newObjectPosition = objectCenter + new Vector3(0, newObjectBounds.extents.y, 0);
Set the position of the new object to the calculated position.
currentPlaceableObject.transform.position = newObjectPosition;
You can also set the rotation of the new object to match the normal of the surface that it is being placed on, as you are already doing in your code.
currentPlaceableObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal);

Unity 2D - Keep Player Object in Boundary of it's Parent Panel

In Unity i have a UI Panel which has a player object (an UI Image object).
I moving player object into planel with user inputs (keyboard or touch)
I can't keep player object in it's parent panel,
Please check below image, I want to keep player inside of Red Panel
Here is my Tried Code
public Camera MainCamera; //be sure to assign this in the inspector to your main camera
private Vector2 screenBounds;
private float objectWidth;
private float objectHeight;
private RectTransform pnlBackgroundTransform;
private void Start()
{
pnlBackgroundTransform = GameObject.Find("PnlBackground").GetComponent<RectTransform>();
screenBounds = MainCamera.ScreenToWorldPoint(new Vector3(pnlBackgroundTransform.rect.width , pnlBackgroundTransform.rect.height , MainCamera.transform.position.z));
objectWidth = transform.GetComponent<SpriteRenderer>().bounds.extents.x; //extents = size of width / 2
objectHeight = transform.GetComponent<SpriteRenderer>().bounds.extents.y; //extents = size of height / 2
}
void LateUpdate()
{
Vector3 viewPos = transform.position;
viewPos.x = Mathf.Clamp(viewPos.x, screenBounds.x * -1 + objectWidth, screenBounds.x - objectWidth);
viewPos.y = Mathf.Clamp(viewPos.y, screenBounds.y * -1 + objectHeight, screenBounds.y - objectHeight);
Debug.Log(screenBounds);
Debug.Log(viewPos);
transform.position = viewPos;
}
I'd say it's not very usual having the player implemented as a UI element, and instead you should be implementing it outside the UI/Canvas system.
The UI/Canvas system uses a set of rules of placing and scaling to deal with responsive design. You have at least 4 values (excluding rotation) to place something on the screen: anchor, pivot, position and scale.
For example: if you want to create a square you can either set it's size in absolute pixel values or relative values (to parent). If you're using absolute values, your UI Scale Mode, defined on the Canvas object, should affect the visual results.
This means the UI/Canvas is for elements that should adapt to the screen, such as buttons, dialogs, labels, etc. Taking advantage of device parameters to improve the UX.
Outside the UI/Canvas system, things are directly based on Linear Algebra: you have a 3D vector space (a "World") where everything exists with an absolute size and position. Then, your Camera stretches and twists the whole world to match what your current perspective. That means your object will always have the same size, regardless of screen size.
Now, assuming you have a very specific reason to implement your game into UI, there are a few ways you can do it. I'll assume you're using absolute values. Please note all the units used here are pixels, and the effect should be different for devices with different resolutions and sensible to the UI Scale Mode parameter. Also, please note I've set both anchors min and max to (0,0), the bottom left corner (default is screen center, (0.5,0.5)), in order to avoid negative coordinates.
The following script is attached to the player's UI Image.
public class UIMovementController : MonoBehaviour
{
public float speed = 5.0f;
new private RectTransform transform;
private Rect canvasRect;
private void Start()
{
transform = GetComponent<RectTransform>();
canvasRect = GetComponentInParent<Canvas>().pixelRect;
}
void Update()
{
// Keyboard Input (Arrows)
Vector2 move = new Vector2(0,0);
if (Input.GetKey(KeyCode.UpArrow)) { move.y += speed; }
if (Input.GetKey(KeyCode.DownArrow)) { move.y -= speed; }
if (Input.GetKey(KeyCode.LeftArrow)) { move.x -= speed; }
if (Input.GetKey(KeyCode.RightArrow)) { move.x += speed; }
transform.anchoredPosition += move;
// Position clamping
Vector2 clamped = transform.anchoredPosition;
clamped.x = Mathf.Clamp(clamped.x, transform.rect.width / 2, canvasRect.width - transform.rect.width / 2);
clamped.y = Mathf.Clamp(clamped.y, transform.rect.height / 2, canvasRect.height - transform.rect.height / 2);
transform.anchoredPosition = clamped;
}
}

Direction Vector around Mesh's Surface

How do I best describe the direction vector to move an object left (or right/up/down) from its current position, factoring the current view perspective into the equation?
For example, imagine the box depicted in the following picture, placed at origin (0,0,0). If I wanted to move the point on top of the box to the left, I'd have to make a step in (-1,0,0) direction (i.e. currentPos = currentPos + Vector3.left).
If I looked at the box from behind, I'd have to make a step in (1,0,0) direction to continue moving in the same direction.
i.e. When I'm looking at this box, placed at origin, and press an input button LEFT, I need for the point to move left and continue moving in that direction for as long as I keep pressing the button. It ultimately wraps around the surface, i.e. if I keep pressing just LEFT the point will wrap around the cube and re-appear so to speak.
To indefinitly trace a decal around a cube:
private Transform Cube;
private Transform Decal;
void Update() {
Vector3 moveLocal = Vector3.zero;
// Set moveLocal from User Input
moveLocal += Vector3.left * Input.GetAxis("Left"); // etc
if (moveLocal.sqrMagnitude > 0) {
var decalDirection = (Decal.position - Cube.position).normalized;
var angle = Vector3.SignedAngle(Cube.forward, decalDirection, Cube.up);
// Align decal to correct cube face
if (angle < -135 || angle > 135) { // Rear face
Decal.forward = -Cube.forward;
}
else if (angle < -45) { // Left face
Decal.forward = -Cube.right;
}
else if (angle > 45) { // Right face
Decal.forward = Cube.right;
}
else { // Front Face
Decal.forward = Cube.forward;
}
// Now move decal in it's local space:
Decal.Translate(moveLocal, Space.Self);
}
}
Admittedly, this only rotates Left/Right, but I hope you get the idea =)
As i mentioned you in my opinion you can achieve this with surface normals. I cracked some code which is not an exact solution because i used collision normals and on corners that gives different results. In any case i share it so that it gives you the idea. You can go left and right with this code relative to camera(even though it does not use anything related to camera) because it depends on surface normals.
private Vector3 normal;
private Vector3 contact;
private Vector3 direction;
private GameObject obj;
void Start () {
}
void Update () {
if (obj == null)
return;
if( Input.GetKey(KeyCode.LeftArrow))
{
obj.transform.position += direction * Time.deltaTime;
}
if (Input.GetKey(KeyCode.RightArrow))
{
obj.transform.position -= direction * Time.deltaTime;
}
}
void OnCollisionEnter(Collision collision)
{
Debug.Log("Collided");
Debug.Log("If works");
obj = collision.gameObject;
normal = collision.contacts[0].normal;
contact = collision.contacts[0].point;
//Cross product of normal and up gives direction Right.
//This up can be cube's up as well in cases it is tilted
direction = (-Vector3.Cross(Vector3.up, normal));
}
You can also see how it works from images below:

why does the orientation of my object change in unity?

I have a model in Unity which i want to rotate using a Quaternion given by an IMU. To change the orientation i need to set the object's initial orientation Quaternion(=Qreset) to the orientation of the IMU (=Qimu) and from then on calculate the difference between Qreset and Qimu to get the new orientation. It works, however whenever i set the initial orientation (Qreset) the orientation of the object in Unity always changes (to X: -180.4, Y: 21.08, Z: 21.08). I don't know why this happens. How do i change this so object orientation at reset is always 0,0,0. This is my code:
void Update() {
if (Input.GetKeyDown(KeyCode.Space))
{
Qreset = Qimu;
}
transform.rotation = Qreset * Quaternion.inverse(Qimu);
}
void Splitstring(string msg)
{
string[] values = msg.Split('+');
Qimu.x = float.Parse(values[1]);
Qimu.y = float.Parse(values[2]);
Qimu.z = float.Parse(values[3]);
Qimu.w = float.Parse(values[0]);
}
There is a difference between rotation
The rotation of the transform in world space stored as a Quaternion.
and localRotation
The rotation of the transform relative to the transform rotation of the parent.
And note that in the Transform Inspector
The position, rotation and scale values of a Transform are measured relative to the Transform’s parent. If the Transform has no parent, the properties are measured in world space.
=> what you see in the Inspector is usually the localRotation
So if your object is nested under the Qimu you want to set
transform.localRotation = Qreset * Quaternion.inverse(Qimu);
Also note the Draco18s' comment about retrieving the Quaternion. You rather should do
void Splitstring(string msg)
{
string[] values = msg.Split('+');
var x = float.Parse(values[1]);
var y = float.Parse(values[2]);
var z = float.Parse(values[3]);
var w = float.Parse(values[0]);
Qimu = new Quaternion(x, y, z, w);
}