This document shows how to maximize Spatial SDK’s model loading to load and manage 3D assets. Spatial SDK simplifies the process of loading and handling your 3D assets. For example, you can load your models by using Mesh(Uri.parse("myMesh.glb")).
Coordinate space
Spatial SDK uses a left-handed coordinate system, where X+ is to the right, Y+ is upward, and Z+ is forward. Any loaded glTF models will be converted to match this coordinate space. All units are in meters unless otherwise specified.
The Mesh component
The primary way to load a 3D asset is via a Mesh component.
class Mesh(
var mesh: Uri = Uri.parse("about:blank"),
var hittable: MeshCollision = MeshCollision.LineTest,
var defaultShaderOverride: String = "",
var defaultSceneOverride: Int = -1,
)
Here is a closer look at each attribute.
mesh: A URI that defines where the mesh lives. This URI determines whether the mesh is a local file, a networked file, or a custom object.
hittable: Either LineTest or NoCollision. These specify whether the mesh should block rays (like controller pointers) cast through the scene. If you include NoCollision, rays will pass through the mesh.
defaultShaderOverride: The string path to optionally override which shader the model uses. By default, Spatial SDK uses a physically-based rendering shader.
defaultSceneOverride: The index to optionally override which scene you want to load. If not specified, the Mesh class will load the default scene.
The URI
Spatial SDK supports multiple protocols for pointing a URI to a mesh. The mesh:// protocol is the primary one, but you can use any of the following protocols:
mesh://: Use a custom mesh creator to load your object.
file://: Load a glTF/glb file from the given absolute file path.
apk://: Load a glTF/glb file relative to the assets folder of your Spatial SDK app.
http:// and https://: Load the glb file from the given networked URL. You must use OkHTTP to support the https:// protocol
This is an experimental feature.
No protocol specified: Spatial SDK will attempt to read your local mesh file relative to your assets directory (similar to apk://).
The mesh:// protocol
The mesh:// protocol enables you to define custom meshes parameterized by runtime values. For example, mesh://sphere loads a sphere mesh with a radius defined by the attached Sphere(radius: Float) component and the material defined by the Material component.
// create a blue sphere with radius 2
Entity.create(
listOf(
Mesh(Uri.parse("mesh://sphere")),
Sphere(2.0f),
Material().apply {
baseColor = Color4(blue=1.0f)
}
)
)
Out of the box, Spatial SDK supports these meshes using the mesh:// protocol:
mesh://plane: Uses the Plane(width: Float, depth: Float) to create a plane with the given width x 0.1 x depth dimensions.
mesh://sphere: Uses Sphere(radius: Float) to create a sphere mesh with the given radius.
mesh://box: Uses Box(min: Vector3, max: Vector3) to create a rectangular prism, with one corner at the min point and the opposite corner at the max point.
mesh://roundedbox: This mesh is the same as mesh://Box but uses RoundedBox(min: Vector3, max: Vector3, radius: Vector3) with a radius to apply rounding to the edges of the box.
mesh://dome: Creates a half sphere that is 1 km in radius.
mesh://axis: Create an RGB XYZ axis, which is useful for debugging which direction is up, forward, and down.
mesh://skybox: Creates an entire sphere with a 1 km radius.
Registering custom mesh://s with registerMeshCreator
Spatial SDK offers several meshes that serve as essential primitives. However, you can register custom meshes with the registerMeshCreator function.
// in the app's onCreate()
registerMeshCreator("mesh://floor") {
SceneMesh.box(
-5f,
-0.1f,
-5f,
5f,
0f,
5f,
// load a grass texture that's already set up
SceneMaterial(SceneTexture(getDrawable(R.drawable.Grass_02)))
)
}
This example loads a floor mesh, a thin 10 x 10 square with a grass texture. Spatial SDK will load this mesh whenever you specify a mesh with the mesh://floor URI. Spatial SDK provides a static mesh, but since registerMeshCreator takes a function, you can select any custom implementation or dependencies on custom components.
Loading glTF/glb files
See the glTFs page for more information about glTF/glb files.
Put your gITF/glb files in your assets folder and use the following code to load your models.
You can control the translation and rotation of an Entity by using the Transform component. This allows you to set a pose, which contains a Vector3 and Quaternion. The Vector3 represents the translation in xyz space, while the quaternion represents the rotation.
If you wanted to create a glb model at point (1, 2, 3) and rotate it 180 degrees around the entity’s local y-axis, you could create it like this:
The TransformParent component allows us to set the transform entity to be local to another entity. For example, if you have an entity that represents the Earth and another entity that represents the Moon, then as the Earth entity moves, you would want the Moon entity to stay in the same place relative to the Earth (ignore the Moon’s orbit for now). To do this, you can set the TransformParent component on the Moon to be the Earth entity.
val earth = Entity.create(
listOf(
Mesh(Uri.parse("earth.glb")),
Transform(Pose(Vector3(1f, 2f, 3f)))
)
)
val moon = Entity.create(
listOf(
Mesh(Uri.parse("moon.glb")),
TransformParent(earth)
// Note: This is a transform local to the Earth's transform (1, 2, 3)
Transform(Pose(Vector3(1, 0, 0)))
)
)
This code creates an Earth entity and a Moon entity. Because the Moon entity has the TransformParent component set, the Moon entity’s Transform is set as a local transform to the Earth entity’s transform. In this example, the Earth would be at world coordinates (1, 2, 3), and the Moon would be at world coordinates at (2, 2, 3).
If the Earth entity has its transform changed, the Moon will also be changed so that its local transform to the Earth remains unchanged. You can also change the Moon’s transform, and it will change to the new local position relative to the Earth.
To remove a TransformParent and have an entity use a normal absolute transform, you can set the TransformParent to be the nullEntity.
// set the TransformParent
myEntity.setComponent(TransformParent(myParentEntity))
...
// remove the TransformParent
myEntity.setComponent(TransformParent(Entity.nullEntity()))
Scaling meshes
You can use the Scale component to scale a mesh or a panel. This component will scale the mesh uniformly in the x, y, and z directions, and you can change this scale at any time.
glTF models often use centimeters instead of meters, which is the wrong scale. This mismatched scaling frequently requires you to add Scale(Vector3(0.01f)) to your mesh if the mesh is too big.