Jump to content

Recommended Posts

Hi there, I'm on a project that is a sandbox instructors' tool for various airspace coordination scenarios (basically don't shoot down your own air support 101). The instructor can drag many things into the scene (various aircraft/artillery, marker gizmos, etc). One of the things they want to be able to do is mark "zones" in both the air and on the ground. Right now I've just put in some regular unity cubes with a 2 sided shader with transparency so they can see through them but also still see them when the camera is inside them. The user would just scale them as needed.

cubeZone.thumb.png.6b1ad5c323df3eae3b6c3a9c57b68d94.png

The problem is they're still just scaled cubes. What they would like is the ability to make "corridors".

So I back-engineered your example scene for extruding faces of a cube at runtime and the user can now resize the "zones" by extruding faces....

extrudeCubeZone.png.ee89accab75bbc3064eea62ec78fd14b.png

....As it is right now I can only extrude entire faces, but I'm unable to branch off as is depicted in the image below.

branching.png.e11fd39424c66aae0b230fb2b1dfdd12.png

I'm not fully familiar with all the geometry terminology but I have gotten this far, would anyone be able to point me in the right direction or have examples of what I've described? I've been able to do it in the editor, but I need to know how to do it via C# at runtime.

 

Any help is much appreciated :) 

 

PS I'm running Unity 5.6.2f1 and I don't have the liberty to change versions.

Share this post


Link to post
Share on other sites

It looks like so far you've just translated faces along their normal. To create branches, you need to extrude the face prior to translating. So using the RuntimeEdit example, you'd add

currentSelection.pb.Extrude(new pb_Face[] { currentSelection.face }, ExtrudeMethod.IndividualFaces, Mathf.Epsilon);

prior to translating the vertices. That creates new segments that you can then branch off however you'd like.

Share this post


Link to post
Share on other sites
19 minutes ago, karl said:

It looks like so far you've just translated faces along their normal. To create branches, you need to extrude the face prior to translating. So using the RuntimeEdit example, you'd add


currentSelection.pb.Extrude(new pb_Face[] { currentSelection.face }, ExtrudeMethod.IndividualFaces, Mathf.Epsilon);

prior to translating the vertices. That creates new segments that you can then branch off however you'd like.

Cheers. It's a snow day for me so I won't be able to try this until tomorrow...unless that's a snow day too. I'll let you know how it works out. : ]

Share this post


Link to post
Share on other sites
20 hours ago, karl said:

It looks like so far you've just translated faces along their normal. To create branches, you need to extrude the face prior to translating. So using the RuntimeEdit example, you'd add


currentSelection.pb.Extrude(new pb_Face[] { currentSelection.face }, ExtrudeMethod.IndividualFaces, Mathf.Epsilon);

prior to translating the vertices. That creates new segments that you can then branch off however you'd like.

There's no .Extrude method for pb_Object. At least not a public one.

Share this post


Link to post
Share on other sites
38 minutes ago, karl said:

You'll need to include the ProBuilder2.MeshOperations namespace.

That sorted out the syntax error, but I think I need to change when things are happening because I'm creating waaaay too much geometry. Also I think there's a texturing issue. In game view it appears as though I'm just pulling the face right off the cube but scene view shows that I am indeed doing what I wanted to, but way too many times.

pulledOffFace.png.8be0b9d65c27fc059ca2b92d2ab70f7e.png

sceneView2.png.36f8afcb9b24dbf21cad70e1a41e987d.png

Here's the bit of code where it's happening:

	if (_preview!=null && Input.GetMouseButton(0))
        {            
            UpdateNodeRestrictions();

            //check if behind face, if so use a nagative multiplier below

            Vector3 facePos = FaceCenter(_currentSelection.Face, _currentSelection.PB);
            Vector3 toWidget = (_widget.localPosition - facePos).normalized;
            bool behind = Mathf.Approximately(Vector3.Dot(toWidget, _localNormal), -1.0f);

            _currentSelection.PB.Extrude(
                                new pb_Face[] { _currentSelection.Face }, ExtrudeMethod.IndividualFaces, Mathf.Epsilon);

            _currentSelection.PB.TranslateVertices(
                        _currentSelection.Face.distinctIndices,
                        _localNormal.normalized * (Vector3.Distance(_widget.localPosition, facePos)) * (behind ? -1.0f : 1.0f)
                                                  );
            
            // Refresh will update the Collision mesh volume, face UVs as applicatble, and normal information.
            _currentSelection.PB.Refresh();

            // this create the selected face preview
            RefreshSelectedFacePreview();
        }

Share this post


Link to post
Share on other sites

You're almost there, it's just missing a few parts. In psuedo-code, it should look like this:

if (mouseDrag)
{
	if (!wasDragging)
	{
		pb.Extrude(faces)
		pb.ToMesh();
		pb.Refresh();
		wasDragging = true;
	}
	
	pb.TranslateVertices(...);
	pb.Refresh();
}

 

Share this post


Link to post
Share on other sites
24 minutes ago, karl said:

You're almost there, it's just missing a few parts. In psuedo-code, it should look like this:


if (mouseDrag)
{
	if (!wasDragging)
	{
		pb.Extrude(faces)
		pb.ToMesh();
		pb.Refresh();
		wasDragging = true;
	}
	
	pb.TranslateVertices(...);
	pb.Refresh();
}

 

W0000! Before you had replied I had sorted out the bool thing so that it only does it once per drag, but I didn't know that ToMesh() was needed. I take it that method is like a "commit changes" sort of thing?

I went a little nuts testing but it works : ]

mostlyWorking.thumb.png.78dc12ae769a0392d97eb374e9374324.png

 

Just one more thing though. Is there a way I can do this with my own rectangle (just like the preview plane) that isn't sized from the values of the face. I just size it myself and then use it like a cookie cutter to extrude with? As it is now, you kind of have to play a bit to get a face to be the right size to make a "corridor". Also the end user isn't going to have any comprehension of what's under the hood.

Share this post


Link to post
Share on other sites

Yes, ToMesh is what applies the mesh data stored in pb_Object to the actual mesh. Most mesh modification calls require ToMesh after (TranslateVertices is actually one of a small few that does not).

18 hours ago, gord0 said:

Is there a way I can do this with my own rectangle (just like the preview plane) that isn't sized from the values of the face

I don't follow, could you explain this further? 

Share this post


Link to post
Share on other sites
28 minutes ago, karl said:

Yes, ToMesh is what applies the mesh data stored in pb_Object to the actual mesh. Most mesh modification calls require ToMesh after (TranslateVertices is actually one of a small few that does not).

I don't follow, could you explain this further? 

In the picture below. Let the blue represent a face. Let the red represent the preview plane. Instead of having it sized to match the face, have it sized by the user in some way so they can just extrude whatever size of rectangle from wherever they place the preview plane. Programmatically we would be adding the necessary geometry for it to work. Is that doable with probuilder?

example.png.15714f0afda6c81c24f0f1f2aba21e1c.png

Share this post


Link to post
Share on other sites
1 hour ago, karl said:

I see. Yes, you could do an extrusion and scale to get the sizing, then a second extrude to pull the new section out.

So which overload of Extrude() would work? Would we not need to add vertices to the face to create the smaller face within the original face?

I made another diagram using regular cubes to illustrate what I'm trying to do. Where the red plane is sized and placed by the user.

1.png.a7a4826b4135dfe824d6ab9c35dbe9fe.png

2.png.a0e11aa04d06ae327a4510d23f207f7a.png

Share this post


Link to post
Share on other sites
19 minutes ago, karl said:

You just extrude with no distance, then scale the face inward, then extrude again. Here's a gif showing what I mean.

extrude.gif

Ahhh. Nice. So what I'm going to do now is work on a way for the user to just draw the preview plane on a face (perhaps by holding a kb key and dragging the mouse). Then I can do as you've explained above. Instead of sizing the preview plane to match the face, I'll scale/position the face to match the preview plane. : ]

Share this post


Link to post
Share on other sites

Alright, I've hit a wall that I can't see the other side of. The following is the code I'm using to allow the user to draw a plane on a pbObject. It gets called every frame if the user is pressing the right inputs.

protected void DrawCustomPlane()
    {
        if (Input.GetMouseButtonUp(0)) //they let go of the button, lets get outta here
        {
            _customExtruding = false;
            return;
        }
        if (!Input.GetKey(KeyCode.LeftControl)) { return; } //they're not holding left control, so let's chill

        if (_customExtruderPlane==null) //if our plane is null
        {
            _ray = _cam.ScreenPointToRay(Input.mousePosition); //let's fire a ray to see if we can fix that
            if (Physics.Raycast(_ray, out _hit, Mathf.Infinity, _lm)) //if we hit something
            {
                if (_hit.collider.gameObject.GetComponent<pb_Object>()==null) { return; } //forget it, not pb object

                Vector3[] verts = new Vector3[4] //we'll start our 4 vertices at the hit point
                {
                    new Vector3(_hit.point.x, _hit.point.y, _hit.point.z),
                    new Vector3(_hit.point.x, _hit.point.y, _hit.point.z),
                    new Vector3(_hit.point.x, _hit.point.y, _hit.point.z),
                    new Vector3(_hit.point.x, _hit.point.y, _hit.point.z)
                };
                int[] indices = new int[verts.Length]; //define our indices
                for (int iVert=0; iVert<verts.Length; iVert++) { indices[iVert] = iVert; } //..

                //now we can make the plane
                _customExtruderPlane = pb_Object.CreateInstanceWithVerticesFaces(verts, new pb_Face[] { new pb_Face(indices) });
                _customExtruderPlane.SetFaceMaterial(_customExtruderPlane.faces, PreviewMaterial);
                _customExtruderPlane.ToMesh();
                _customExtruderPlane.Refresh();
                
                //move it just a little in front of whatever the ray hit
                _customExtruderPlane.gameObject.transform.position += _hit.normal * 0.1f;
            }
        }
        if (_customExtruderPlane==null) { return; } //i guess we missed
        else //lets move our vertices according to mouse movement
        {
            /*i'm assuming the following is what each vertice is, i could very well be wrong, we'll find out soon enough
            verts[0] -- upper left corner
            verts[1] -- upper right corner
            verts[2] -- lower right corner
            verts[3] -- lower left corner
            */

            Vector3[] verts = _customExtruderPlane.vertices;
            verts[1].x += Input.GetAxis("Mouse X") * CustomExtrudeDragSpeed * Time.deltaTime;
            verts[2].x = verts[1].x;
            verts[2].y += Input.GetAxis("Mouse Y") * CustomExtrudeDragSpeed * Time.deltaTime;
            verts[3].y = verts[2].y;

            _customExtruderPlane.SetVertices(verts);
            _customExtruderPlane.ToMesh();
            _customExtruderPlane.Refresh();
        }
    }

I get an index out of  range error at this line:

_customExtruderPlane = pb_Object.CreateInstanceWithVerticesFaces(verts, new pb_Face[] { new pb_Face(indices) });
Quote

IndexOutOfRangeException: Array index is out of range.
ProBuilder2.Common.pb_Face.CacheEdges ()
ProBuilder2.Common.pb_Face.RebuildCaches ()
ProBuilder2.Common.pb_Face.SetIndices (System.Int32[] i)
ProBuilder2.Common.pb_Face..ctor (System.Int32[] i)
PBCorridor.DrawCustomPlane () (at Assets/Scripts/DraggableScripts/Shapes/PBCorridor.cs:166)
PBCorridor.MainLogic () (at Assets/Scripts/DraggableScripts/Shapes/PBCorridor.cs:235)

I can't dig into the PB scripts to see why. Would you be able to shine some light?

Cheers

Share this post


Link to post
Share on other sites

Figured it out, it needs 6 vertices and I was giving it 4. It's creating the plane now, but I need to sort out placement. It's not appearing where it should be. I figure it's a local vs world thing.

Share this post


Link to post
Share on other sites
On ‎23‎/‎03‎/‎2018 at 6:48 PM, karl said:

If you've not already found it, you may find our repository of example code helpful. https://github.com/Unity-Technologies/ProBuilder-API-Examples

 

Just got back in from the weekend. I see you've added more stuff to the repo since I last visited a week or two ago. I've made some progress on my side but still stuck with some things. I'll take a look on the repo and report back later.

Share this post


Link to post
Share on other sites

Alright, so nothing in there really helps (that I can tell).

So right now I've got it doing almost what I want but I'm experiencing geometry glitches. I've stripped out as much of my project as I could while keeping code in question functioning and placed it in a zip which I've attached to this post (CorridorTest3.7z).

The code that's doing the work in question is in PBCorridor.cs .

The scene is PBCorridorTest3 .

You'll need to be quite careful as I've only put in super basic functionality and it's easy to break things.

First you'll want to drag out a corridor:

dragPallete.png.0aab0cad8cb818f0d77f8e0a078e4106.png

It should look like this, don't click anywhere just yet:

0.png.573b2af3a8145ba0394c7d20c2c2e716.png

Now move the cursor to somewhere  on the face that is facing the camera. Hold left control and left click and drag the mouse to draw a rectangle on the face (might want to have left control down before the left click):

1.png.02477a2beff4e554c1ba19d6ed11fad9.png

When you let go, the rectangle will disappear (at least for now it does, later I'll have it auto-select). Click roughly near the center of the rectangle you drew. A single arrow should appear with your rectangle highlighted:

2.png.f335f6c1b0ec0578b0c15084add5a9ab.png

Now you'll want to move the camera to see better (camera controls are same as scene view (right click--rotate, middle mouse down--pan/strafe, scroll wheel--zoom). Then left click on arrow and pull it:

3.png.f69098151798587e4c5c391c5fd99d37.png

 

Now you're thinking, so you've got it. What's the problem? Well if you try this on any other face (including a second time on the face you already used) the geometry gets messed up causing faces to no longer be visible. Also, in the screen shot above, it actually has one tiny problem too (the face on the back side that is not in view of the camera is invisible).

This is the point where I no longer know what to do.

Any help would be much appreciated. : ]

Share this post


Link to post
Share on other sites

I'm super stuck. Don't know how to sort this out.

Does probuilder have the ability to "merge" meshes? So I could just merge the user drawn plane (pb_Object) into the target object (pb_Object) so that I don't have to do all this manual jiggering around? Or do you know another way to achieve the desired result?

Share this post


Link to post
Share on other sites
3 minutes ago, karl said:

Can you condense the code to the specific problem area? It would be much easier for me to help pick out the issue.

Yes, you can merge meshes using the MeshOps namespace.

I'm not at work today, but the problem is most likely in CommenceModifications().

The reason I asked about a merging functionality is that it could probably take a huge chunk of the work out of my hands. If I were to place a plane on a face of a cube, said plane is smaller dimensions than the face, and merge would that plane become a face on the cube?

Share this post


Link to post
Share on other sites
1 hour ago, gord0 said:

The reason I asked about a merging functionality is that it could probably take a huge chunk of the work out of my hands. If I were to place a plane on a face of a cube, said plane is smaller dimensions than the face, and merge would that plane become a face on the cube?

Yes, but you'd end up with two faces sandwiched next to each other. If that works for you, it would be fine to use use that approach.

I also just remembered that I did a very similar thing for an article a few years ago. I can't find the article, but the example code is here.

https://github.com/karl-/pipedreams

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×