I have a scene with a skybox and I would like to get the point the user clicked projected onto the skybox.
I'm using HelixViewport3D.FindNearestPoint(Point pt) to get the point, which works very well, except when there's anything between the click and the skybox. In this situation it returns the point projected onto the object in front of the skybok.
Is there any way to flag an element so it would be ignored in HitTests?
You can catch point on any Visual3D or Geometry3D
Give names to your Visual3D objects.
ModelVisual3D modelVisual3D = new ModelVisual3D();
modelVisual3D.SetName("ModelName");
You can use FindHits method with your HelixViewPort3D
Point3D point3D;
var hitList = yourHelixViewPort.ViewPort.FindHits(Point point);
foreach (var hit in hitList)
{
if (hit.Visual != null)
{
if (hit.Visual.GetName() == "ModelName")
{
point3D = hit.Position;
// You can use also hit.Mesh
// also hit.Model
// also hit.Visual
// also hit.Normal
}
}
}
Related
So the title might not be perfect, but it's essentially what I want to do.
My problem
I'm creating an app using Unity 2017.3.0f3 and Google's ARCore where you can select a model to place in the world and change the texture on the material as desired. The problem I'm having is that I can place a model with 1 texture on it, but when I change the texture on the Ghost model (in this case, the Ghost model floats around in the world on the ARCore Plane where a Raycast from the center of the screen collides with the Plane) it changes the texture on the already-placed model as well. See the screenshots below, taken seconds apart from screenshots on my phone:
I have placed a model (left/back) with the 'Lit' firepit texture, and the Ghost model is still 'Lit' (right/front):
I have clicked the 'Unlit' firepit texture in the list, and now both models have changed texture:
This is the code I wrote to change the Ghost to the selected model (it works, I've tested it with multiple models, but the demo currently only has 1), which is called when clicking on the model thumbnail in the bottom pane:
public void SetPlaceableModel(GameObject newModel)
{
PlaceableModel = newModel;
var pos = (Ghost != null) ? Ghost.transform.position : FirstPersonCamera.ScreenToWorldPoint(new Vector3(Screen.width / 2f, Screen.height / 2f, -100f));
var rot = (Ghost != null) ? Ghost.transform.rotation : Quaternion.identity;
Destroy(Ghost);
Ghost = Instantiate(PlaceableModel, pos, rot);
}
This is the code I wrote to change the texture on the Ghost model, which is called when clicking on the texture thumbnail in the top pane:
public void ChangeGhostTextures(TextureSet textureSet)
{
SelectedTextureSet = textureSet;
var materials = Ghost.GetComponentsInChildren<Renderer>().SelectMany(x => x.materials);
foreach (var texture in textureSet.Textures.Where(x => !x.name.ToLower().Contains("normal")))
{
var normalTexture = textureSet.Textures.FirstOrDefault(x => x.name == string.Format("{0}_Normal", texture.name));
foreach (var material in materials.Where(x => x.name.Replace(" (Instance)", string.Empty) == texture.name))
{
material.SetTexture("_MainTex", texture);
if (normalTexture != null)
{
material.SetTexture("_BumpMap", normalTexture);
}
}
}
}
This is the definition of TextureSet:
public class TextureSet : MonoBehaviour {
public string SetName;
public Texture Thumbnail;
public List<Texture> Textures = new List<Texture>();
}
And this is the code I'm using to Instantiate the Ghost model into a new object and placing it on the ARCore Plane in the Update() method:
var productModel = Instantiate(Ghost, Ghost.transform.position, Ghost.transform.rotation);
productModel.GetComponent<PlaneAttachment>().Attach(hit.Plane);
As far as I can tell, and I don't know why this is, when I Instantiate the Ghost onto the Plane, the materials seem to be maintaining reference to the Ghost materials. I've tried changing the ChangeGhsotTexture to do this:
var _tex = Instantiate(texture) as Texture; //also tried 'as Texture2D'
material.SetTexture("_MainTex", _tex);
But then this happens when I change textures:
I've also tried to Instantiate(material) as Material and then set it that way, but the same thing happens (as the original issue, not the black texture):
material.SetTexture("_MainTex", texture);
if (normalTexture != null)
{
material.SetTexture("_BumpMap", normalTexture);
}
var _mat = Instantiate(material) as Material;
//can't assign directly to `material` because it's the enumeration result
var assignableMaterial = materials.First(x => x.Equals(material));
assignableMaterial = _mat;
Now here's the kicker
After placing a model down, if I click on the model thumbnail (bottom pane, which calls SetPlaceableModel), even when it's the same model that's already selected because it still calls SetPlaceableModel, it works**! I've tried calling SetPlaceableModel after placing the model in the Update() method, but it doesn't fix the issue (to add information, when clicking on the model thumbnail it calls SetPlaceableModel in the Update() from a different script). Also, it now seems to be working properly only for the first model that is is placed, then it behaves with the problem I'm having (but I don't know why... I haven't changed anything).
I've tried...
As I mentioned, I've tried calling SetPlaceableModel again after placing a model, I've tried doing Instantiate on the Texture and Material as is suggested here (which seems to be my exact issue... so I must be missing something). **
I am super confused... please help. If you need any more information let me know.
**
EDIT: on further testing (I added a new model and 2 TextureSet objects to go with it, so I had 2 models each with 2 different texture sets), this 'works' because the issue only arises after the first time I change textures after selecting a model. The issue goes away when I change models and use the material/texture that are default on the model (each model has material(s)/texture(s) on them, set in the Editor, before running the project).
EDIT 2: as #Programmer suggested, I changed the Instantiate(material) as Material to his suggested new Material(material), but it did not work. This is how I implemented it:
public void ChangeGhostTextures(TextureSet textureSet)
{
var materials = Ghost.GetComponentsInChildren<Renderer>().SelectMany(x => x.materials);
foreach (var texture in textureSet.Textures.Where(x => !x.name.ToLower().Contains("normal")))
{
var normalTexture = textureSet.Textures.FirstOrDefault(x => x.name == string.Format("{0}_Normal", texture.name));
foreach (var material in materials.Where(x => x.name.Replace(" (Instance)", string.Empty) == texture.name))
{
material.SetTexture("_MainTex", texture);
if (normalTexture != null)
{
material.SetTexture("_BumpMap", normalTexture);
}
var _material = new Material(material);
var assignableMaterial = materials.First(x => x.Equals(material));
assignableMaterial = _material;
}
}
}
did I do something wrong? I tried this implementation, too, but it doesn't change textures at all now (whereas the other implementation does change textures, but maintains the issue):
var _material = new Material(material);
_material.SetTexture("_MainTex", texture);
if (normalTexture != null)
{
_material.SetTexture("_BumpMap", normalTexture);
}
var assignableMaterial = materials.First(x => x.Equals(material));
assignableMaterial = _material;
when I Instantiate the Ghost onto the Plane, the materials seem to be
maintaining reference to the Ghost materials.
There are three constructor overloads for the Material class:
public Material(string contents);
public Material(Shader shader);
public Material(Material source);
The last is what you need. Use it to make a copy of your original material then assign that copy to your object. This should solve the material reference issues.
public Material objMat;
void Start()
{
Material newMat = new Material(objMat);
//Use the newMat on your object
}
Since I couldn't get this to work with #Programmer's method (though it may work for others, mine was still changing the material, no matter where/how I implemented var _mat = new Material(mat); or assigned textures, I opted to just define multiple materials as Assets and switch between them as desired:
public void ChangeGhostMaterials(MaterialSet materialSet)
{
foreach (var renderer in Ghost.GetComponentsInChildren<Renderer>())
{
var material = materialSet.Materials.FirstOrDefault(x => x.name.Equals(renderer.material.name.Replace(" (Instance)", string.Empty)));
renderer.material = material ?? renderer.material;
}
}
If someone else comes along this and provides a working Answer, I will gladly change the selected Answer, but as it stands this is my solution to my issue.
I have been looking on the code for ARCore Unity for a while and I want to do one simple task that is, to have a toggle button so user can place an object in the scene while knowing where to place it while the tracked planes are visible and once the user places the object, he is given the option of just visually disabling the tracked planes so it looks more realistic. I was able to do this in Android Studio by doing something like this in the main HelloArActivity.java:
if (planeToggle) {
mPlaneRenderer.drawPlanes(mSession.getAllPlanes(), frame.getPose(), projmtx);
}
this was really simple. I made a bool named planeToggle and just placed the mPlaneRenderer.drawPlanes function in an if condition. When the bool is true it displays the planes and when its false, it does not...
However, with Unity I am confused. I did something like this in the HelloARController.cs :
I made a button to togglePlanes.
Set an event listener to it to toggle a boolean variable and did something like this :
for (int i = 0; i < m_newPlanes.Count; i++)
{
// Instantiate a plane visualization prefab and set it to track the new plane. The transform is set to
// the origin with an identity rotation since the mesh for our prefab is updated in Unity World
// coordinates.
GameObject planeObject = Instantiate(m_trackedPlanePrefab, Vector3.zero, Quaternion.identity,
transform);
planeObject.GetComponent<TrackedPlaneVisualizer>().SetTrackedPlane(m_newPlanes[i]);
m_planeColors[0].a = 0;
// Apply a random color and grid rotation.
planeObject.GetComponent<Renderer>().material.SetColor("_GridColor", m_planeColors[0]);
planeObject.GetComponent<Renderer>().material.SetFloat("_UvRotation", Random.Range(0.0f, 360.0f));
if (togglePlanes == false){ // my code
planeObject.SetActive(false); // my code
} //
}
Nothing happens when I press the toggle button.
The other option I had was to make changes in the TrackedPlaneVisualizer.cs where I did something like this :
for (int i = 0; i < planePolygonCount; ++i)
{
Vector3 v = m_meshVertices[i];
// Vector from plane center to current point
Vector3 d = v - planeCenter;
float scale = 1.0f - Mathf.Min((FEATHER_LENGTH / d.magnitude), FEATHER_SCALE);
m_meshVertices.Add(scale * d + planeCenter);
if (togglePlanesbool == true) // my code
{
m_meshColors.Add(new Color(0.0f, 0.0f, 0.0f, 1.0f)); // my code
}
else
{
m_meshColors.Add(new Color(0.0f, 0.0f, 0.0f, 0.0f)); // my code
}
}
This did work. But I am experiencing delays in toggling and sometimes if two different planes have been rendered they start toggling between themselves(if one is enabled, other gets disabled). So I guess this is also not the option to go for....Anyone who can help?
Note that I am a beginner in Unity.
The sample isn't really designed to hide and show the planes, so you have to add a couple things.
First, there is no collection of the GameObjects that represent the ARCore planes. The easiest way to do this is to add a tag to the game objects:
In the Unity editor, find the TrackedPlaneVisualizer prefab and select it. Then in the property inspector, drop down the Tag dropdown and add a tag named plane.
Next, in the Toggle handler method, you need to find all the game objects with the "plane" tag. Then get both the Renderer and TrackedPlaneVisualizer components and enable or disable them based on the toggle. You need to do both components; the Renderer draws the plane, and the TrackedPlaneVisualizer re-enables the Renderer (ideally it would honor the Renderer's state).
public void OnTogglePlanes(bool flag) {
showPlanes = flag;
foreach (GameObject plane in GameObject.FindGameObjectsWithTag ("plane")) {
Renderer r = plane.GetComponent<Renderer> ();
TrackedPlaneVisualizer t = plane.GetComponent<TrackedPlaneVisualizer>();
r.enabled = flag;
t.enabled = flag;
}
}
You can also do a similar thing where the GameObject is instantiated, so new planes honor the toggle.
I have a helix toolkit project, in WPF, visual studio 2015. Using the example RectSelection I have a 3d viewport in which I can select my objects, which are BoxVisual3D.
What I need to do, is return the 3d position of the selected object. I have:
foreach (var model in models)
{
var geometryModel = model as GeometryModel3D;
if (geometryModel != null)
{
geometryModel.Material = geometryModel.BackMaterial = material;
//do stuff
UserControl1.Point1Position = model.Transform;
UserControl1.returnPoint.X = model.Transform.Value.M14;
UserControl1.returnPoint.Y = geometryModel.Transform.Value.M24;
UserControl1.returnPoint.Z = geometryModel.Transform.Value.M34;
}
}
But the values always return as 0. (I spawn the box myself, so i know they are not 0).
When I step through, there is a selected object, but the transform reads as all zeros. How can i get the position of a BoxVisual3D?
Thanks.
After nearly 3 years maybe not you but someone else could face with this issue. Here is my explanation.
If you created a Model3D with transform attribute you can use OffsetX, OffsetY, OffsetZ. But if you just created your BoxVisual3D with center attributes there will be no transform in it. Hence you just cant reach it. Create your objects with transform attribute. And another issue creating object in the specified Point3D. Here is my code:
my_point = new Point3D(-15,7,5);
var myTransform = new Transform3DGroup();
TranslateTransform3D myTranslate = new TranslateTransform3D(my_point.X, my_point.Y, my_point.Z);
myTransform.Children.Add(myTranslate);
kontrol.Transform = myTransform; //ez
myModel.Children.Add(kontrol);
And here is the getting the Transform back:
Transform3D mytransform = sourceobject.Transform;
Console.WriteLine(mytransform.Value.OffsetX + "," + mytransform.Value.OffsetY+"," + mytransform.Value.OffsetZ);
To get the Position of the Matrix3D you have to use the Offset Properties:
public static Point3D GetPosition(this Matrix3D m)
{
return new Point3D
{
X = m.OffsetX,
Y = m.OffsetY,
Z = m.OffsetZ
};
}
I'm using Gmap.Net on windows form, I want to draw track of an object when I receive its position, I use Routes for this. When I add points to a route, no line is seen on the map, but when I change the zoom of the map, they appear on the map. Also when I set the position of the map after adding a point to the route (gMapControl1.Position = new PointLatLng(...)), it works correctly and I see the route lines on the map, any idea? My code is like below.
void NewDataReceived(DeviceInfo deviceinf)
{
//---some codes
//----For the first time I add layer and route
if (deviceOverLay == null)
{
deviceOverLay = new GMapOverlay(deviceinf.DeviceId.ToString());
gMapControl1.Overlays.Add(deviceOverLay);
deviceRoute = new GMapRoute(new List<PointLatLng>(), deviceinf.DeviceName);
deviceOverLay.Routes.Add(deviceRoute);
//Add all your points here
deviceRoute.Points.Add(new PointLatLng(deviceinf.Latitude, deviceinf.Longitude));
deviceRoute.Tag = deviceinf;
}
else
{
deviceOverLay.Routes[0].Points.Add(new PointLatLng(deviceinf.Latitude, deviceinf.Longitude));
}
//if I call this line it works, but I don't want it
// gMapControl1.Position = new PointLatLng(deviceinf.Latitude, deviceinf.Longitude);
//---some codes
}
Try using
gMapControl1.UpdateRouteLocalPosition(deviceRoute);
This updates the local positions and does a redraw.
I have been trying to program a way of preventing my character from touching a wall so he couldn't go trough it but I can't find a proper way of doing like you can see in this video (that I recorded). Sorry about the (decent) mic quality : https://youtu.be/jhTSDgSXXa8. Also I said prevent the collision but rather it detects it and stops but you can go through.
The collision code is :
foreach (PictureBox pbMur in pbListeMurs)
{
if (pbCharacterCat.Bounds.IntersectsWith(pbMur.Bounds))
{
if (pbCharacterCat.Right > pbMur.Left)
{
bWalkRight = false;
bIdle = true;
}
}
}
Thank you ! :D
I'm not sure how you are using bIdle and walkRight, but these types of boolean flags are easy to get wrong and it turns your whole code into a complete mess as you typically try to plug holes and end up springing new ones in the process.
First of all, why do you even need them? Wouldn't this be enough?
var newPotentialCharacterBounds =
GetNewBounds(pbCharacterCat.Bounds, movementDirection);
var collidedWalls = pbListeMurs.Where(wall =>
wall.Bounds.IntersectsWith(newPotentialCharacterBounds));
if (!collidedWall.Any())
{
pbCharacterCat.Bounds = newPotentialCharacterBounds
}
//else do nothing
How does this work? Well, the premise is that your character can't start in an invalid position, and if it is never allowed to reach an invalid position then you never need to undo movements or reset positions.
I'd propose you create an enumeration that describes all possible directions:
enum Direction { Up, Down, Left, Right };
When the corresponding direction command is given, get the potential new position of the character (newPotentialCharacterBounds and GetNewBounds). If that position collides with anything, simply do nothing, if it doesn't, move!
UPDATE: Pseudocode follows:
//event handler for move right fires, and calls:
TryMove(pbCharacterCat, Direction.Right)
//event handler for move left fires and calls:
TryMove(pbCharacterCat, Direction.Left)
//etc.
private static Rectangle GetNewBounds(
Rectangle current, Direction direction)
{
switch (direction)
{
case Direction.Right:
{
var newBounds = current;
newBounds.Offset(horizontalDelta, 0);
return newBounds;
}
case Direction.Left:
{
var newBounds = current;
newBounds.Offset(-horizontalDelta, 0);
return newBounds;
}
//etc.
}
//uses System.Linq
private bool TryMove(Control ctrl, Direction direction)
{
var newBounds =
GetNewBounds(ctrl.Bounds, direction);
var collidedWalls = pbListeMurs.Where(wall =>
wall.Bounds.IntersectsWith(newBounds));
if (!collidedWall.Any())
{
ctrl.Bounds = newBounds;
return true;
}
//Can't move in that direction
Debug.Assert(collidedWall.Single); //sanity check
return false;
}
Becuase TryMove returns if the movement was successful or not, now you can leverage that information; different sound effects for instance, etc.