How to rotate a 3d mesh around local origin in Bevy - bevy

So it seems in Bevy when I use the rotate function 3d objects rotate around the global origin. My current solution is the following where I first set the objects position to the global origin, rotate it, then move it back to it's original position:
fn rotator_system(time: Res<Time>, mut query: Query<(&Rotator, &mut Transform)>) {
for (_rotator, mut transform) in &mut query.iter() {
let position = transform.translation().clone();
transform.set_translation(Vec3::new(0.0, 0.0, 0.0));
transform.rotate(Quat::from_rotation_y(time.delta_seconds));
transform.translate(position);
}
}
Is this right solution or is there a better way?
This appears to be a related pull request: https://github.com/bevyengine/bevy/pull/564

Related

Bevy - Have multiple Sprites Sheets per Entity

I have a Entity containing a Srite Sheet and class instance
let texture_handle = asset_server.load("turret_idle.png");
let texture_atlas: TextureAtlas = TextureAtlas::from_grid(texture_handle, ...);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
let mut turret = Turret::create(...);
commands.spawn_bundle(SpriteSheetBundle {
texture_atlas: texture_atlas_handle,
transform: Transform::from_xyz(pos),
..default()
})
.insert(turret)
.insert(AnimationTimer(Timer::from_seconds(0.04, true)));
The AnimationTimer will then be used in a query together with the Texture Atlas Handle to render the next sprite
fn animate_turret(
time: Res<Time>,
texture_atlases: Res<Assets<TextureAtlas>>,
mut query: Query<(
&mut AnimationTimer,
&mut TextureAtlasSprite,
&Handle<TextureAtlas>,
)>,
) {
for (mut timer, mut sprite, texture_atlas_handle) in &mut query {
timer.tick(time.delta());
if timer.just_finished() {
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
}
}
}
This works perfectly fine as long as the tower is idle thus plays the idle animation. As soon as a target is found and attacked, I want to display another sprite sheet instead.
let texture_handle_attack = asset_server.load("turret_attack.png");
Unfortunately, It seems that I cannot add multiple TextureAtlas Handles to a Sprite Sheet Bundle and decide later which one to render. How do I solve this? I thought about merging all animations into one Sprite Sheet already but this is very messy as they have different frames.
Maybe create a struct with all the different handles you need and add it as a resource? Then you need a component for the enum states "idle", "attacked" etc.. and a system that handles setting the correct handle in texture_atlas from your resource handles.

Unity get all elements in radius 2D

I'm using 2D.
I wanna get all Objects on Layer X in a specific radius.
transform.position -> (0.0, 0.0, 0.0)
viewRadius -> Distance, which the player can see. This is working fine.
targetMask -> The Layer X. All GameObjects that I want, are on this layer.
Collider2D[] targetInViewRadius = Physics2D.OverlapCircleAll(transform.position, viewRadius, targetMask);
If you just want the objects you can do
foreach (var item in targetInViewRadius)
{
var objects = item.gameObject;
}

How to get a SCNNode to face the current camera position?

I'm currently programming an AR-App, which starts with placing a SCNNode (table). First the app searches for a horizontal plane and whenever a surface was found, the object is shown, but not placed yet. While it is not placed, I want the object to face the camera at all times, but I'm having trouble to find the current position of the camera and also updating the object every frame.
Currently, the object is facing the world coordinates. So when I start the app and a horizontal plane was found - the object appears and faces to the starting point of the world coordinates (wherever I started the app)
Can anybody help me, how to get the vector from the camera position and make the object updates it's direction every frame?
var tableNode : SCNNode! // Node of the actual table
var trackingPosition = SCNVector3Make(0.0, 0.0, 0.0)
let trans = SCNMatrix4(hitTest.worldTransform)
self.trackingPosition = SCNVector3Make(trans.m41, trans.m42, trans.m43)
self.tableNode.position = self.trackingPosition
The functionality you are trying to implement is called billboarding. To achieve this effect you need to set the SCNLookAtConstraint using the scene's .pointOfView as the target.
you can remove
self.trackingPosition = SCNVector3Make(trans.m41, trans.m42, trans.m43)
self.tableNode.position = self.trackingPosition
and use:
// set constraint to force the node to always face camera
let constraint = SCNLookAtConstraint(target:sceneView.pointOfView)
constraint.isGimbalLockEnabled = true
self.tableNode.constraints = [constraint] // constraint can cause node appear flipped over X axis
You should implement ARSCNViewDelegate and use the renderer method renderer(_:updateAtTime:) where you every frame get the camera position from sceneView.pointOfView. This allows you to then use the SCNNode method look(at:) to transform the nodes coordinate system towards the camera.
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
if let camera = sceneView.pointOfView {
tableNode.look(at: camera.worldPosition)
}
}

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

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).

shoot ball from middle of the screen Unity

Hi im trying to create a sphere and launch it in the direction im looking from the middle of the screen.
however im currently launching from the middle but always in the same direction and height.
tanx for any help.
#pragma strict
private var globe:GameObject;
var globeMaterial:Material;
private var shootIndex:boolean;
function Start () {
}
function Update () {
if(Input.GetMouseButtonDown(0))
{
if (shootIndex==false){
globe=GameObject.CreatePrimitive(PrimitiveType.Sphere);
globe.renderer.material=globeMaterial;
globe.AddComponent(Light);
globe.light.color=Color.blue;
globe.AddComponent(Rigidbody);
//globe.transform.position=Camera.main.ScreenToViewportPoint(Input.mousePosition);
globe.transform.position=Camera.main.transform.position;
globe.transform.localScale=Vector3(0.5,0.5,0.5);
globe.rigidbody.AddRelativeForce(Vector3.forward*1000);
shootIndex=true;
}
else if (shootIndex==true){
Destroy(globe);
shootIndex=false;
}
}
}
Your Problem is that Vector3.forward is always the Vector (0,0,1) as in one step towards the z coordinate(in this analogy y is upwards and x is right) as can be seen here:
http://docs.unity3d.com/Documentation/ScriptReference/Vector3-forward.html
Two solutions to this are:
1.) Add the Force in the direction your camera is pointing:
globe.rigidbody.AddForce(Camera.main.transform.forward * 1000);
You dont need "AddRelativeForce" here as your Globe isnt rotated in any way. so "AddForce" and "AddRelativeForce" have the same effect.
2.) Or change the rotation of the sphere and then send the sphere forwards (relative to its own rotation):
globe.transform.rotation = Camera.main.transform.rotation;
globe.rigidbody.AddRelativeForce(Vector3.forward * 1000);