Giter VIP home page Giter VIP logo

cesium-unity's People

Contributors

argallegos avatar artoowang avatar azrogers avatar csciguy8 avatar j9liu avatar joseph-kaile avatar kring avatar nithinp7 avatar shehzan10 avatar swardle avatar tal952 avatar tomlee-dev avatar tomleelive avatar weegeekps avatar zagrizzl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cesium-unity's Issues

Add support for exceptions to Reinterop

Currently, exceptions thrown in C# cannot be caught in C++. They won't even run destructors for C++ RAII objects. This isn't a huge immediate problem, because we should generally avoid letting C# code throw exceptions. But if it does happen, it's likely to cause spectacular failures and probably be hard to debug, so it's worth doing at some point.

UnityNativeScripting supports exceptions crossing the interop boundary by catching on one side, squirreling the exception away in a variable, and checking that variable on the other side. This is probably the best approach, but care must be taken to do this in a thread-safe manner.

Make default materials accessible to users

If you create a tileset and click on the "Opaque Material" slot, it won't show the Cesium default materials included in the Cesium for Unity Package. It seems like that window only brings up materials available in the Assets folder. We should try to find a way to make those materials accessible, whether if its from the package itself, or by importing the materials (and other user-accessible resources) into a folder under Assets.

Generate flat normals when normals are missing

In #133 we enabled smooth normal generation when glTFs do not come with normals. However, according the glTF spec, missing normals should be populated with flat normals. This should be the default behavior in the future to be spec-compliant. Since flat normals require vertex duplication however, we should restore the tileset option that allowed users to choose smooth normal generation instead (this option was removed in #133).

Support 3D Tiles 1.1 Implicit Tiling

When configuring a custom tileset (b3dm) from url I see the following error:

[2022-12-01 22:43:13.353] [error] [TilesetJsonLoader.cpp:804] Received status code 404 for tile content http://localhost/content/%7Blevel%7D_%7Bx%7D_%7By%7D.b3dm

UnityEngine.Debug:Log (object)
Reinterop.ReinteropInitializer:UnityEngine_Debug_CallLog_FA05wu8x__otZNsgdHTnU9A (intptr) (at Library/PackageCache/[email protected]/Runtime/generated/Reinterop/Reinterop.RoslynSourceGenerator/ReinteropInitializer.cs:23343)
Reinterop.ReinteropInitializer/ActionNativeFunction:Invoke () (at Library/PackageCache/[email protected]/Runtime/generated/Reinterop/Reinterop.RoslynSourceGenerator/ReinteropInitializer.cs:21315)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback ()

Configuration:

image

`CesiumGeoreference`, `CesiumSubScene`, `CesiumGlobeAnchor` don't refresh with pasted values

Unity has the option to copy the values of a component and paste them into another component's fields.

However, the aforementioned components don't automatically refresh when values are pasted into them. I think the paste bypasses any checks done by the custom editor, and just changes the SerializedObject underneath. This means our components won't know to update themselves unless the values are edited manually in the editor fields themselves.

I'm not sure it's an easy fix to do some code in OnValidate, because OnValidate triggers whenever the custom editors change a value as well. Plus, OnValidate can't tell what property / properties changed. So the check might have to be in the custom editors. Before serializedObject.Update() is called, you may have to store the old values of the serializedObject, then compare them at the end of OnInspectorGUI. Then, if things have changed, refresh the respective components.

UI-related console errors

I'm not sure when exactly these errors happen and when they don't. But they seem to happen more often when one of our custom UI panels is open, but is hidden by something else. For example, I just got some of these errors when I opened the console log (thereby hiding the assets panels) and clicked on something in the hierarchy.

UIErrors

Overhaul generation of bindings layer

The "bindings" layer that allows our C++ plugin to call into Unity and the .NET platform is currently generated by a fork of UnityNativeScripting. UnityNativeScripting is a fantastic idea, it's backed up by really useful tech articles, and is a solid proof-of-concept implementation. To take Cesium for Unity to production, however, we need something a bit more production-ready. Some of the problems with it:

  1. Lots of bugs and shortcomings. I've had to make significant changes to it to support the Cesium for Unity prototype so far.
  2. The code generator is integrated into Unity, which makes it awkward to run outside of Unity as a batch job.
  3. The code generator is a single source file with over 13k lines of code, which makes it hard to understand and modify. A lot of this comes from generating code by repeatedly calling Append on a StringBuilder, rather than using templating.
  4. Its hot-reloading system is unlikely to ever work for us, because it requires all C++ allocations to happen in a designated C#-owned buffer. And it also seems to be getting in our way. Anytime Unity tries to hot-reload anything (even if it's game code not plugin code being reloaded), we get into an inconsistent state that causes errors and crashes. We can live without hot-reloading of our plugin if necessary, but we can't tell our users that they're not allowed to hot-reload their own code.
  5. It uses a custom system to allow the C++ side to "reference" C# objects. This system requires designating a maximum number of objects up-front. And it's mostly unncessary, because .NET's built-in GCHandle does this already.
  6. UnityNativeSharp allows a C++ class to derive from a C# class. So, for example, we can implement a MonoBehaviour in C++. However, it doesn't handle the ownership/lifetime in a principled way, leading to hard-to-debug gotchas. For example, you can't create an instance of MyCPPMonoBehaviour and attach it to a GameObject without carefully ensuring that the C++ class instance sticks around until the GameObject is destroyed or the MonoBehaviour is removed. Otherwise you'll end up with a broken MonoBehaviour attached to the GameObject. This means the rules for using C++ objects derived from C# classes are different from the rules for using regular C# classes, which is super confusing. I believe the right way to solve this is to separate the "C++ wrapper class" from the "C++ implementation class." The C++ wrapper instance simply refers to the C# instance and allows it to be used from C++, just like any wrapper for any C# object. The C++ implementation instance is created by and owned by the C# instance, and allows for the implementation of parts of the C# class to be delegated to C++.
  7. The types, properties, and methods to expose to C++ are defined in a JSON file, but writing that JSON file is more difficult than it needs to be. The syntax is confusing and inconsistent, particularly for generic types. And if you want to expose a particular class, you also need to manually expose every base class or interface implemented by that class. Similarly, if a method returns a particular type, you have to manually expose that type. The generator could fairly easily automatically discover the set of all types that need to be exposed from a much smaller user-specified set.

Add option to select tileset data source

Currently, Cesium3DTilesetImpl::LoadTileset only references the ion tileset constructor variant. A URL field is present in the editor UI for providing a custom URL, however this is not referenced by the tileset loading routine. Adding an enumeration or similar to select the desired data source, ion or URL, is needed to select and invoke the correct tileset constructor.

Crash when exiting application

When shutting down the application,

void JustBeforeDelete(
      const DotNet::CesiumForUnity::CesiumCreditSystem& creditSystem);
  void Update(const DotNet::CesiumForUnity::CesiumCreditSystem& creditSystem);

is called.

There seems to be a null reference exception when calling something in ClearLoadedImages().

Honor glTF texture wrapmode and filtering

Currently we are using Unity defaults for texture wrapmode (I think wrap) and texture filtering (aniso). We should honor the instructions in the actual glTF texture sampler - this might fix some tile edge issues we've been seeing.

Intellisense doc comments for public classes

All public Cesium for Unity C# classes should have Microsoft's XML comments so that Intellisense will help people use our classes. Eventually we can generate reference docs from these as well, but Intellisense is enough for now.

Vertex colors not utilized when rendering untextured geometry

Currently, when loading tiles which contain geometry possessing vertex colors, these vertex attributes are ignored by the material. Comparing with cesium-unreal, loading untextured geometry with vertex colors results in in meshes drawn using said color attributes.

Pass a delta time to the Tileset::updateView call

Use Lod Transitions has been added as an editor option in #25. Even though the transition shader effect isn't implemented, the user is still able to enable the transition period and change the length. Doing so currently will result in tiles not getting hidden correctly, because their transition percentage never gets updated. To fix this, the Tileset::updateView invocation should pass along the delta time for the frame.

Lines between tiles

There are seams between tiles when using raster overlays:

image

It's caused by the texture sampling Wrap Mode being set to "Repeat" (because that's the default)? If we change it to "Clamp" the problem will go away.

Tileset displacement incorrect after origin change

#65 was partially fixed in #122, but still seems to have the following issue.

Currently transforms for tiles in Unity generally get constructed like this:
tileLocalToUnity = UnityAdjustmentTransform * ECEFToUnity * tileLocalToECEF
where "UnityAdjustmentTransform" is the hand-edited transform applied in addition to the default tileset transform.

The problem is that the ECEFToUnity transform changes every time the origin moves. This means that a "UnityAdjustmentTransform" relative to an original georeference origin, will be incorrect when composed onto the ECEFToUnity corresponding to a new georeference origin. To fix this, we'll need to do a change of basis on the UnityAdjustmentTransform at the time the change is commited, so it becomes an ECEFAdjustmentTransform:
ECEFAdjustmentTransform = UnityToECEF * UnityAdjustmentTransform * ECEFToUnity

Later we can compute tile transforms by:
tileLocalToUnity = ECEFToUnity * ECEFAdjustmentTransform * tileLocalToECEF
Note the new adjustment matrix is no longer relative to a transient frame-of-reference. Further Unity adjustments can similarly be changed to an ECEF basis and composed with the previous adjustment.

The only tricky part will be automatically detecting / overriding Unity tileset transform events (whether in editor or during play). But I suspect some of these details have already been sorted out over the course of #122 and the globe anchor implementation.

Georeferencing

We need a strategy for georeferencing in Unity. The approach we use should be based on the best ideas from:

  1. The Cesium for Unreal georeferencing system.
  2. The georeferencing system built into later versions of Unreal Engine itself (which Alban added, loosely based on (1))
  3. The Cesium for O3DE georeferencing system.
  4. The Unity High Precision Framework.

Georeferencing's killer application

The georeferencing system, however it's implemented, has one purpose which is so essential that, without it, we really couldn't have Cesium for Unity (or Cesium for any game engine) at all. That is: it places the globe within the Unity world such that a particular point on the globe is at the origin (0,0,0) of Unity's coordinate system and there are sensible coordinate axes at that point. It's possible to think of this as "placing the globe within the Unity world" or alternatively as "placing the Unity world on the globe".

This is essential because, without it, the center of the Earth would be located at (0,0,0) in Unity's coordinate system, and the Unity axes would be aligned with ECEF, and so:

  1. Coordinate values for objects on the surface of the Earth would be enormous, making them imprecise and nearly impossible to work with in the Editor.
  2. The "up" direction for Unity world building would be different everywhere on the globe. As the Editor camera isn't aware of the local up direction, the world would be titlted sideways when working with it in the Editor. In-game, special logic would be needed to keep objects upright.

Even an extremely simple georeferencing system solves both of these problems quickly and easily for the common case where only one area on the globe is of interest, and is where all the world-building takes place, and the globe and other georeferenced data serve as a background within that area.

However, if the scenario isn't limited to just one area on the globe, we need more from the georeferencing system.

Origin rebasing

As the camera moves away from the Unity world origin, vertex precision becomes a serious problem. This is because the transformation matrix for an object near the camera will have very similar translation values to the camera itself. Yet when these matrices are combined (e.g. to form a model-view matrix) using single-precision arithmetic, these translations do not cancel out precisely due to the limitations of floating point arithmetic. The residual may be quite large compared to the actual distance between the objects, causing them to jitter around relative to each other.

The only solution to this (short of computing the model-view matrix in double precision as UE5 and CesiumJS both do, but that is not currently an option in Unity) is to make sure the translation parts of those matrices are not large in the first place. We can do this by keeping the world origin near the camera, and adjusting the coordinate values of all objects in the scene based on the new origin. This is commonly called origin rebasing.

The basic georeferencing described in the "killer application" section above can be thought of as a form of origin rebasing. We're rebasing the world origin to be at a point of interest on the globe, and adjusting the position of the globe itself accordingly. Objects near that point of interest (including the tiles that make up the globe itself) will have small, precise coordinate values. Objects far away from that point of interest will have larger, less precise coordinate values, but we don't mind because they're far enough away that the errors cause less than a pixel shift, and are therefore invisible.

So far, though, this is only what we might call "static rebasing." The rebased origin is set once and never changes. As soon as we move away from that origin rebased origin - to another city in a faraway part of the globe, for instance - all our original vertex precision problems return.

If we want to be able to visit multiple places on the globe, and have precise rendering at each of them, then we need to rebase the origin while the game is running: dynamic rebasing. This can happen continuously: every time the camera moves, set the origin to the new location of the camera, and adjust the position of every other object in the scene accordingly. Or it can happen in steps: only adjust the origin when the camera moves too far from the previous origin.

Either way, changing the origin at runtime is a drastic thing to do. Unreal Engine 4 has a built-in system for doing this; Unity does not. But even being built-in, it's the cause of many bugs in UE4. It is very common for Actors or ActorComponents to break in strange and interesting ways when the origin is rebased. When the origin is rebased, every object's position in the world changes. That can cause problems in two ways:

  1. Some objects don't expect to move. They're static, with some aspects of their behaviour tied to the position they were given at Editor / baking time.
  2. On the other hand, some objects expect to move, but take the "movement" resulting from rebasing too seriously. The rebase should not cause any collision, acceleration, or elastic effects, for instance, because the entire world is moving along with this object. But some objects will just see the (potentially large and sudden) movement and respond to it inappropriately.

For global roaming in a single-precision game engine, though, there's nothing for it. We have to do origin rebasing, and we (and our users) have to deal with whatever problems it causes.

Sub-levels

Cesium for Unreal has a sub-level system that can soften the blow of origin rebasing. The idea is that we often have multiple areas of interest in different parts of the globe. At each location, there may be any number of things happening in the scene, involving physics, gravity, navigation, particle effects, you name it. But in between there is not very much, other than the globe itself.

So, if we put all the interesting stuff happening at each location into a separate sub-level for each location, and only activate the objects in the sub-level when the camera approaches it (and deactivate them when it moves away), then we can avoid any origin rebasing while all that interesting stuff is loaded and ticking. None of it ever needs to deal with origin rebasing. The scene objects that are active in between these interesting locations still need to deal with origin rebasing, but this is a much smaller set of objects and often only includes Cesium-provided objects, such as a Tileset responsible for rendering the globe.

In Cesium for Unreal, we leverage an Unreal Engine system for the sub-levels, but it isn't necessary for Unity to have something equivalent. All we need is a way to activate and deactivate groups of GameObjects based on the proximity to the camera.

How it Works Elsewhere

Cesium for Unreal

A CesiumGeoreference Actor is added to the scene. It allows the user to specify a position on the globe that becomes the center of the Unreal world coordinate system. At that origin, the Unreal X axis points East, the Y axis points South, and the Z axis points Up.

It is possible to change this origin location at runtime, but doing so does not affect all objects in the scene, as you might expect from an origin rebase. Instead, it only affects objects that are anchored to the globe, which means Cesium objects and other objects that have a GlobeAnchorComponent attached to them. All other objects keep their same world coordinates after the georeference changes, meaning that they appear to move with respect to the globe. This is almost never what anyone wants or expects, but it's not clear what we could do that would be better.

By adding a GlobeAnchorComponent to an Actor, that Actor is attached to the globe. Now when the georeference origin is changed, the Actor's position is adjusted accordingly so that the Actor maintains the same position relative to the globe. It is still possible to change the regular transform on an Actor with a GlobeAnchorComponent attached, in which case the globe position maintained by the GlobeAnchorComponent is updated to match the new game engine transform.

There is a lot of complexity in the CesiumGeoreference and GlobeAnchorComponent to allow either the globe position or the world position to be authoritative at any given time and update the other accordingly. For example, a globe-anchored actor can be affected by physics. And it can be rebased by changing the CesiumGeoreference's origin.

Separate from all of this, Unreal has its own origin rebasing system, called the "world origin". This is a translation only, and it is expressed in integer coordinates. When the world origin is changed, Unreal changes the coordinates of every object in the scene accordingly. There is no need for a special component to opt-in to this rebasing, as there is with the CesiumGeoreference.

However, the way this coordinate adjustment works is inherently imprecise. Imagine two objects near the origin in the scene, spaced only a centimeter apart. Then, we set the world origin to be a million meters away in another part of the globe. The coordinates of our two objects are adjusted for the new origin, so their coordinate values in meters are now quite large, some components containing 6-7 digits each. Because the single-precision floating-point numbers representing the position can only reliably represent 6-7 significant digits, the 0.01 meters that separated our two objects gets lost in the noise. The actual distance between the two objects may change by as much as a meter!

This isn't a problem at first, because the two objects are a million meters away, and we can't see the difference between a centimeter and meter at that distance. But when the camera moves back toward those objects, and we shift the origin back to be near them, we have a problem. The origin shift is subtracted from the coordinate values, and the coordinate values are small again. Now if the distance between those objects has become a meter, that's a big difference when viewed up close, and we'll definitely be able to tell.

Fortunately this problem only exists in UE4. In UE5, coordinate are stored in double precision, so that centimeter difference would be easily preserved. Better still, in UE5, we don't need to rebase the origin at all because the increased coordinate precision.

In summary, in UE4, we can use UE's built-in origin rebasing system, which creates imprecision in positions when moving far away from objects and then moving back. Or we can use the CesiumGeoreference to effectively rebase the origin, but that only affects Georeference-aware objects and leaves all others in their previous (now wrong) position.

Unity High Precision Framework

The HPRoot behavior is very similar to the CesiumGeoreference. The user can specify a globe position (they use the term "universe position") that is placed at the origin of Unity's coordinate system. Actually, it is placed, oriented, and scaled according to the Transform behavior on the same GameObject, which is a nice touch that allows the globe to be located, oriented, and scaled however the user likes within the Unity world. This should be useful for putting a globe on a table in AR, for example.

The HPRoot serves as both a georeferencing mechanism and an origin rebasing system. Another behavior, HPTransform, is very similar to Cesium for Unreal's GlobeAnchorComponent. It specifies the globe/universe location and orientation of the object to which it is attached. Changing the location of the HPRoot on the globe will only adjust GameObjects with an HPTransform. Other objects will maintain their old world coordinates, so they'll appear to move relative to the globe. Just like with CesiumGeoreference and GlobeAnchorComponent.

In the High Precision Framework, separate components are responsible for actually doing the origin rebasing at the appropriate time. For example, the provided LocalVerticalCoordinateSystem takes an object with an HPTransform (usually a camera) and keeps the HPRoot near that object at all times. It also adjusts to the HPRoot's orientation so that the Unity Y-axis is up on the globe at that position. Thus, a camera flying around the globe will always appear to stay upright because the globe and everything attached to it is rotated undernearth it. This is different from the approach taken with the DynamicPawn, which uses UE4's origin rebasing system which is limited to translation not rotation. So the pawn stays upright by rotating as it moves over the globe while the globe itself maintains its original orientation.

Can't have multiple references to arrays in `ConfigureInterop`

I can confirm that #38 allows for support for arrays in interop, but if it seems that if there is more than one reference to an array type of a custom script, it prints these errors to the console:

ReinteropInitializer.cs(1834,37): error CS0102: The type 'ReinteropInitializer' already contains a definition for 'CesiumForUnity__Property_get_LengthType' [C:\Dev\cesium-unity\CesiumForUnity\CesiumForUnity.csproj]
ReinteropInitializer.cs(1835,80): error CS0102: The type 'ReinteropInitializer' already contains a definition for 'CesiumForUnity__Property_get_LengthDelegate' [C:\Dev\cesium-unity\CesiumForUnity\CesiumForUnity.csproj]
ReinteropInitializer.cs(1837,35): error CS0111: Type 'ReinteropInitializer' already defines a member called 'CesiumForUnity__Property_get_Length' with the same parameter types [C:\Dev\cesium-unity\CesiumForUnity\CesiumForUnity.csproj]

To reproduce: add this line to ConfigureInterop.cs:

CesiumGeoreference[] georeferences = UnityEngine.Object.FindObjectsOfType<CesiumGeoreference>();

Since there is a line containing CesiumRasterOverlay[] in the file, it will print these errors. This works even without the variable assignment, just the line UnityEngine.Object.FindObjectsOfType<CesiumGeoreference>(); Commenting out the lines related to CesiumRasterOverlay[] will cause the interop to run successfully.

Cesium ion token refresh is not working in Unity

When the token expires after an hour, Cesium for Unity reports:

An unexpected error occurs when loading tile: Request failed: HTTP/1.1 401 Unauthorized

But it is unable to refresh the expired token and remains broken until you restart Unity.

The problem is that UnityAssetAccessor::get rejects the Future when the HTTP response is not 2xx, rather than resolving it to a response with an error code.

Incorrect tile loading when changing root 3D tileset transform

When the Unity transform of a root 3D tileset actor is changed, the child transformations of already loaded tiles are updated to reflect the new location. If you then change the location of the camera to trigger new tile loading, new tiles do not inherit the modified root transform.

In the attached photo tiles are first loaded, the scale of the root actor transform is changed from 1 to 0.1 and then the camera is moved to load new tiles. These new tiles load as if the root transform was still at the origin with a scale of 1.

Unity_1kkGA5b2Dc

Use SafeHandle instead of IntPtr for C# to hold a pointer to a C++ implementation class

Originally from #1 (comment) and copy/pasted below.

Currently, managed classes that have some of their methods implemented in C++ (e.g. MonoBehaviours) hold an IntPtr to the C++ implementation class. And they have a Dispose method and a finalizer to make sure that this C++ implementation class is destroyed at the appropriate time. This isn't great for two reasons:

  1. When nothing else references a managed object, it's possible for the finalizer to be called while the native code is still executing. This problem is described pretty well here: https://www.mono-project.com/docs/advanced/pinvoke/#gc-safe-pinvoke-code. This shouldn't be a problem in our case because we also pass a reference to the managed class to the native code, so effectively the native code keeps the managed object alive. But this is a little precarious.
  2. Because of the way finalizers impose work on the garbage collector, finalizable classes should be super lightweight and not hold references to other managed objects. But this will not always be the case. Consider MonoBehaviour, for example.

Both of these problems can be solved by holding a SafeHandle-derived class instead of holding an IntPtr directly. The SafeHandle encapsulates the finalization logic, keeping the GC overhead as low as possible. The P/Invoke system is also smart about SafeHandles and will ensure they are not finalized while they are in use in native code.

Crash when calling a C# method/property that returns a large struct by value

For example, Transform.localToWorldMatrix returns a Matrix4x4, which is a value type with 16 floats and is therefore 64 bytes in total. Reinterop generates a C++ wrapper for this property and the code it generates looks fine. But when we call this from C++, we either get back a garbage Matrix4x4, or we crash. This is on Windows x64, I'm not sure if it happens elsewhere.

This isn't particular to Matrix4x4. Any struct with 16 floats will have the same problem. Some things that magically fix it:

  1. Making the struct smaller. If I get rid of half the floats, making it 32 bytes, then it works fine.
  2. Adding a constructor to the generated C++ code. The constructor doesn't even have to do anything, and is never called. Just existing is enough.

This seems to be very similar to a problem in the other C++/C# interop generator that was the inspiration for Reinterop:
jacksondunstan/UnityNativeScripting#11 (comment)

After many many hours pouring through the disassembly at the interop boundary, as well as Microsoft's x64 calling convention docs I've at least determined the immediate problem. The x64 calling convention says that when a function returns a struct by value, and that struct is bigger than 8 bytes:

  1. The caller allocates (on the stack, or whatever) some memory for the return value and passes its address to the callee in the register RCX.
  2. The callee fills in the allocated memory.
  3. The callee returns the same pointer in the register RAX.

In the problematic scenario, the Mono-generated code is not doing step (3). RAX is zero after the call. Magical fix (2) (add a constructor) magically fixes it because the generated C++ code doesn't use the value in RAX, so it doesn't notice or care that it hasn't been set. Just a quirk of code generation that it happens to not need that value, apparently. That's not too shocking because it already has that same value elsewhere (remember, the callee provided that pointer to the callee initially).

Meanwhile, magical fix (1) (make the struct smaller) fixes it by making the Mono-generated code actually set RAX properly. So I starting digging through the Mono source trying to understand why Mono is failing to set that RAX in the problematic case. So that maybe I could work around it, or even submit a PR to fix it. 🤷 Unity have helpfully tagged the Mono source used in each Unity release with the Unity version number, so I was looking at this one:
https://github.com/Unity-Technologies/mono/tree/2021.3.11f1

What I've learned so far is that the code generated for the large struct (but not the smaller one in magical fix (1)) has an extra trampoline near the end, and the RAX value appears to be getting lost in the process of calling it. I haven't been able to figure out what this trampoline is yet, though, or why it's only needed for the larger struct. But the problem it causes is obvious, and further reinforces my "bug in Mono" claim. The Mono-generated code for calling this managed delegate via a native function pointer has this gem near the end:

mov rcx,qword ptr [rbp-28h] ; copy the return value address into rcx
...
mov rax,rcx                 ; copy rcx into rax, yay! at this point it is correct
mov eax,40h                 ; wait, what? writing to eax will clobber rax. Presumably this is preparation for calling the trampoline
...                         ; some other unrelated code, presumably preparing to call the trampoline
mov r11,1C8C061845Ah        ; this is the address of the trampoline
call r11                    ; and this calls the trampoline
...                         ; some other stuff, but nothing that restores the proper value of rax

So the generated code literally sets RAX twice with two back-to-back instructions, and the second one clobbers the value that it needs to have by the time the function ends.

As much fun as it is to continue trying to debug Mono, at this point the best course of action is probably just to implement a workaround in Reinterop. We should be able to translate a function returning a struct by value into one that takes a pointer to a caller-allocated instance of that struct and fills it in. i.e. do exactly what the x64 calling convention / ABI say are supposed to be happening anyway, but do it manually.

Metadata Picking

  1. Add script - If the primitive has a "Mesh Primitive Metadata Extension", add a Cesium Metadata script (will rename better) to the Model GameObject if there isn't one added already.

  2. Parse the feature table using the Feature Table View.

  3. Add Feature IDs to mesh. (Thinking this could be added as an attribute, similar to how positions, normals, etc are added.)

  4. When you call Raycast Hit, you get the GameObject that was hit and also the triangle ID.

  5. Given the triangle ID, you can get the vertex index (vertex index = triangles[triangle ID])

  6. Get the feature ID using the vertex index.

  7. Get the script component on the GameObject which has the Feature Table, and given the feature ID get the properties.

  8. implement converting generic values to string for starters.

GlobeAnchor sometimes doesn't register with CesiumGeoreference

When a GlobeAnchor is initially added to an object that is not parented by the CesiumGeoreference component, we currently print out an error informing users to fix that - so far so good. However, once a user fixes the issue by dragging the anchored game object underneath the CesiumGeoreference component, the GlobeAnchor does not seem to automatically register its world position based on its Unity position like it should (so in effect the object remains un-anchored). Currently the globe anchor needs to be removed and re-added to correct the problem. If we can automatically detect that the game object has been newly parented by the georeference, we should try to re-initialize the globe anchor, which previously may have failed to initialize since it wasn't properly parented.

Tiles loading improperly in `Release` build

image

When using the Release version of the build, tiles are loading incorrectly in both the Unity Editor and in the built standalone version of the project. It looks like no matter the zoom amount, the level-of-detail isn't being consistently selected for the current view; some of them are higher / lower detail than others, in the wrong places.

InvalidCastException when starting the scene

I got it building, and I can see/move around the map. But when I press the "play" button to start the scene it stops showing anything at all and I get the below error.

Version info:
Ubuntu 20.04
dotnet 6.0.403
cmake 3.24.1
Unity 2021.3.10f1.

InvalidCastException: Specified cast is not valid.
Reinterop.ReinteropInitializer.CesiumForUnity_NativeDownloadHandler_Property_get_NativeImplementation (System.IntPtr thiz) (at Reinterop/Reinterop.RoslynSourceGenerator/ReinteropInitializer.cs:1085)
(wrapper native-to-managed) Reinterop.ReinteropInitializer.CesiumForUnity_NativeDownloadHandler_Property_get_NativeImplementation(intptr)
Reinterop.ReinteropInitializer+ActionA2lFbwI_1jdffaYu1u_OZgNativeFunction.Invoke (UnityEngine.AsyncOperation obj) (at Reinterop/Reinterop.RoslynSourceGenerator/ReinteropInitializer.cs:4171)
UnityEngine.AsyncOperation.InvokeCompletionEvent () (at /home/bokken/buildslave/unity/build/Runtime/Export/Scripting/AsyncOperation.cs:21)

Screenshot from 2022-12-07 21-28-46

Add support for RasterizedPolygonsTileExcluder

Currently tile exclusion is not exposed to C#. Support for RasterizedPolygonsOverlay/RasterizedPolygonsTileExcluder enables extra optimizations for developers creating more confined game levels or AR experiences. Going the extra mile, support for C# implementations of ITileExcluder and the ability to provide these to TilesetOptions::excluders would provide maximum flexibility for custom culling logic without having to leave the C# environment.

Visualize CesiumSubScene activation radius

In Cesium for Unreal, there's an "Enable Load Radii" option on the CesiumGeoreference. When enabled, sub-level radii are visualized in the Editor as spheres.

We should add a similar "Show Activation Radius" option to CesiumSubScene in Cesium for Unity.

Add convenience buttons to CesiumGeoreference

The custom Inspector UI for CesiumGeoreference should include buttons for the following:

  • Place Origin Here: Update the origin coordinates of the CesiumGeoreference with the current location of the Editor camera, and reset the Editor camera position to (0, 0, 0).
  • Create Sub-Scene Here: Create a new game object as a child of the CesiumGeoreference, add the CesiumSubScene component to it, and set its origin to the current location of the Editor camera. Activate the game object so that the new sub-scene becomes the selected one. If it's not too hard, select the new sub-scene and initiate editing of the object's name so that the user can quickly give the new sub-scene a meaningful name.

Strategy for building runtime UIs

According to https://docs.unity3d.com/Manual/UI-system-compare.html, the recommended runtime UI system (e.g. for our credits system) is "Unity UI", AKA uGUI.

Unfortunately, this UI system (perhaps all of Unity's UI systems?) are shipped not as a built DLL like UnityEngine.dll or UnityEditor.dll, but as C# source code that is included in your project and built along with it. This makes it hard for Cesium for Unity to use it. We currently build the Cesium for Unity plugin first, and then load the project. But if we need to depend on Unity UI, we can't build the plugin until after we load and compile the project to produce the necessary DLL. So, what do we do?

Some options:

  1. Move Cesium for Unity to the same compilation model, where Unity itself builds the plugin. I previously talked about that in #13, "Allow Unity to build Cesium for Unity"
  2. Continue building the main part of Cesium for Unity as we are, but put the parts that interact with Unity UI in a separate assembly built by Unity itself. The separate assembly will depend on Cesium for Unity, and implement an interface defined in Cesium for Unity to which Cesium for Unity will publish e.g. credits. We'll need some way for it to advertise the existence of that interface implementation so that Cesium for Unity can find it. Dependency inversion at its finest. This is like "Separate assembly for Editor functionality" in #13, except the separate assembly is provided by Unity and doesn't need Reinterop, so that makes it easier.
  3. Can the plugin have its own mini project that is built before the plugin? So we build that, then build Cesium for Unity against it, and finally build the "real" project which uses both.

Specify minimum supported NDK version in README / Android build error in some NDK versions

Windows 21H2
dotnet --version: 7.0.100-rc.1.22431.12
cmake --version: 3.23.0-rc2

Build sequence:

dotnet publish CesiumForUnity -c Release -p:Editor=False
cmake -B build-android -S . -G Ninja -DCMAKE_TOOLCHAIN_FILE="CesiumForUnityNative/extern/android-toolchain.cmake" -DEDITOR=false
cmake --build build-android --target install -j32 --config Release

Android NDKs 22.0.7026061 and onward: Builds successfully

Android NDK: 21.4.7075529:

incomplete type 'Cesium3DTilesSelection::RasterOverlay' used in type trait expression
    : public integral_constant<bool, __is_abstract(_Tp)> {};

CesiumUtility/IntrusivePointer.h:90:38: note: in instantiation of template class 'std::__ndk1::is_convertible<Cesium3DTilesSelection::RasterOverlay, Cesium3DTilesSelection::RasterOverlay>' requested here
      typename std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr>
                                     ^
CesiumUtility/IntrusivePointer.h:91:21: note: while substituting prior template arguments into non-type template parameter [with U = Cesium3DTilesSelection::RasterOverlay]
  IntrusivePointer& operator=(const IntrusivePointer<U>& rhs) noexcept {
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

NDK 21.4.7075529 previously built cesium-native & cesium-unreal without issue. Quest 2 cesium-unity build is otherwise functional with NDK 22.0.7026061+.

Support standalone (not editor) games

CesiumForUnity currently depends on the UnityEditor assembly. Because of this, the plugin can't be used from standalone (built) games. Unity reports:

ArgumentException: The Assembly UnityEditor is referenced by CesiumForUnity ('Assets/CesiumForUnity/CesiumForUnity.dll'). But the dll is not allowed to be included or could not be found.

Cesium for Unity will eventually include deeper integration with the Editor (e.g. a Cesium ion UI), which will obviously involve use of the UnityEditor assembly. But even without any of that obvious Editor integration, UnityEditor is still used for:

  1. Subscribing to EditorApplication::update so that Tilesets an tick in the Editor. Otherwise tiles wouldn't be visible in the Editor, or they wouldn't update as the Editor camera moved.
  2. Accessing the Cameras attached to Editor SceneViews so that Editor viewports can drive tile selection.

In a "normal" Unity application, this isn't too hard to deal with. Simply add #if UNITY_EDITOR around the code that should only exist when the Editor exists. Because Unity compiles the C# code, and sets the #defines appropriately, everything works in both Editor and Standalone builds.

However, our plugin is currently built into a .NET assembly using normal C# compiler and build tools, rather than being built automatically by Unity. Plus, we have C++ code to worry about.

Allow Unity to build Cesium for Unity

Rather than Cesium for Unity being a separate project that is built and then copied into the Unity Assets folder, we can instead put all of our source code straight into the Assets folder along with an assembly definition that tells Unity how to package it up. Unity will build it automatically anytime the code changes.

The first challenge here is that we're using build-time code generation via a Roslyn source generator. Fortunately, Unity does support Roslyn source generators! https://docs.unity3d.com/Manual/roslyn-analyzers.html

However, the docs say it only supports version 3.8, whereas we're using 4.0. It's probably not hard to switch to the older version, but I'm not sure.

The biggest challenge, though, is keeping the C++ code in sync. When Unity does a build with or without UNITY_EDITOR defined, it can and will affect the interop interface. For example, methods in the UnityEditor assembly will necessarily be protected by #if UNITY_EDITOR, which means that they will disappear entirely in non-editor builds. This means these methods will disappear from the array of function pointers passed to the C++ code. Thus the C++ code must be recompiled, or nothing will work; we'll almost certainly crash. Unity will automatically recompile our C# code for Standalone when we build a standalone game, but it has no way of compiling our C++ code. The only solution I can think of here is for the C# build to automatically invoke the C++ build, which would be... interesting.

Separate assembly for Editor functionality

CesiumForUnity only depends on UnityEngine, not UnityEditor. An entirely separate Editor-only assembly provides the Editor-time functionality. The main CesiumForUnity accesses this Editor functionality through an interface that is injected (somehow?) at runtime when the Editor is active.

For this to work, we need a way to inject an implementation in one assembly for an interface declared in another. Probably InitializeOnLoad would work.

We also need a way to have two assemblies using Reinterop without them conflicting. If the two assemblies only ever interact via C#, this is probably not too hard. But it would be nice for the C++ code in the Editor assembly to be able to call into the C++ code in the main assembly. And if we allow that, and if any Reinterop-generated types can ever cross that boundary, then we are likely to run into one-definition rule (ODR) violations: two (generated) header files defining a class included in the same compilation unit. The two definitions may even be slightly different.

Build Standalone and Editor entirely separately

This is probably the "least effort" solution. The idea is to just build everything twice (for each platform): once for Editor use, and once for Standalone use. The two builds have different sets of #defines. For development we would generally only need to build the Editor version. The two sets of assemblies would have different names, and would be configured in Unity to only be loaded in the environment for which they are intended.

Cesium for Unity does not work in Unity 2022.2.0f1

It reports:

error CS0016: Could not write to output file 'C:\Users\runneradmin\AppData\Local\Temp\09094c8b-c05b-4ac9-ba57-e1c9f8000f72\generated\Runtime\Unity.SourceGenerators\Unity.MonoScriptGenerator.MonoScriptInfoGenerator\AssemblyMonoScriptTypes.gen.cs' -- 'Could not find a part of the path 'C:\Users\runneradmin\AppData\Local\Temp\09094c8b-c05b-4ac9-ba57-e1c9f8000f72\generated\Runtime\Unity.SourceGenerators\Unity.MonoScriptGenerator.MonoScriptInfoGenerator\AssemblyMonoScriptTypes.gen.cs'.'

The problem is that our build process adds -generatedfilesout:"C:\Users\runneradmin\AppData\Local\Temp\09094c8b-c05b-4ac9-ba57-e1c9f8000f72\generated\Runtime" to the csc.rsp files in the Runtime and Editor directories in order to get the Reinterop-generated output and bake it into the build. But it then erroneously includes these modified csc.rsp files in the package. This is apparently harmless in Unity 2021.3, probably because there are no generated files to write in a standard build. But in 2022.2, it tries to write "something" to that location, and fails because the directory doesn't exist.

The solution is to remove these lines from csc.rsp before packaging.

Add `showTilesInHierarchy` to `Cesium3DTilesetEditor`

The showTilesInHierarchy property was added to Cesium3DTileset in #78. A custom inspector panel for Cesium3DTileset was added in #77. Because these changes happened at about the same time, the new property doesn't show up on the new inspector panel. It should be added.

Combined package does not correctly merge generated files

Which probably means the macOS build doesn't work right because its generated files are overwritten by the Windows versions. We just need to concatenate the contents of the generated files together, rather than allowing one package's generated files to overwrite another's.

Update README.md to be more user-focused

This repo's README is currently very developer focused. Instead, it should be aimed at people that want to use (pre-built) Cesium for Unity, and have links to the developer docs.

Custom cookie to http-request

Hi,
I would like to stream 3D Tiles from AWS with the "Tileset Source" -Option set to "From Url". However my content is private and I use custom cookies in http request header to authenticate request. Is it possible to set custom cookies or somehow customize http requests in Unity/c# side or how can I achieve this?

Br,
TMK

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.