Update: More comprehensive guides and reference documentation for the display-3d-model
UI component and use cases around embedding 3D models in your app can be found here.
A how-to guide that provides information about how to import and display 3D models in an application built on the JourneyApps platform using FBX files.
Prerequisites
- Make sure you have already created an app on OXIDE. If not, follow the guide on our official documentation.
- You have a .fbx file ready that you want to import. At the time of publishing this guide, the JourneyApps application runtime only supports .fbx files.
- To upload the 3D model to your application youâll also need to zip the 3d model (How-to Windows - How-To Mac). If you donât have a 3D model to test with, feel free to download our sample forklift 3D model using this link.
Data Model Setup
Letâs start by defining a new data model object in our app. Here is a sample:
<?xml version="1.0" encoding="UTF-8"?>
<data-model>
<model name="user" label="User">
<field name="name" label="Name" type="text:name"/>
<display>{name}</display>
</model>
<model name="forklift" label="Forklift">
<field name="name" label="Name" type="text" />
<field name="model" label="Model" type="attachment" media="any" />
<display>{name}</display>
</model>
</data-model>
In this snippet, we have defined forklift
in our data model (this is just an example, you can name it whatever you like).
We have also defined a new field on forklift
called model
. Note that the model
field is of type âattachmentâ, which means weâll be able to upload our zipped .fbx file to the application database and make it available to the application runtime on the userâs device.
Now deploy the application, by clicking the âDeploy to Testingâ button in OXIDE. Then, open the âTestingâ database in the Data Browser (click on the âData browserâ button in the top toolbar).
Once thatâs loaded in a new browser window, select the âForkliftâ type in the menu on the left and create a new instance of a âForkliftâ object by clicking on the blue button in the top right corner.
Finally, give the Forklift a name and upload the zipped .fbx file to our newly created Forklift object. If you donât have a 3D model to test with, feel free to download our sample forklift 3D model using this link.
Views
On the main.js or main.ts view of the application, weâre going to start wiring up the UI by adding the display-3d-model
view component. Add the following to your view XML:
<?xml version="1.0" encoding="UTF-8"?>
<view title="3d Model">
<var name="camera" type="text" />
<var name="scene" type="attachment" media="any" />
<var name="wireframe" type="boolean" />
<var name="fog" type="boolean" />
<var name="floor" type="boolean" />
<var name="doubleSided" type="boolean" />
<var name="debug" type="boolean" />
<var name="scale" type="number" />
<var name="offsetx" type="number" />
<var name="offsety" type="number" />
<var name="offsetz" type="number" />
<var name="bounding" type="boolean" />
<var name="normals" type="boolean" />
<var name="vertexGroups" type="boolean" />
<display-3d-model
id="1"
scale="scale"
bind="scene"
wireframe="wireframe"
double-sided-materials="doubleSided"
fog="fog"
floor="floor"
stats="debug"
autoplay="false"
loop="false"
offset-x="offsetx"
offset-y="offsety"
offset-z="offsetz"
bounding-box="bounding"
material-vertex-groups="vertexGroups"
debug-normals="normals"
on-pick-mesh="$:pick($meshes)"
/>
<button-group mode="row">
<button label="Toggle wireframe" on-press="$:toggleWireframe()"/>
<button label="Bounding" on-press="$:view.bounding = !view.bounding" />
<button label="Stats" on-press="$:stats()" />
<button label="Normals" on-press="$:normals()" />
<button label="Vertex Material Groups" on-press="$:updateVertexGroups()" />
<button label="Double Sided" on-press="$:doubleSided()" />
</button-group>
<button-group mode="row">
<button label="-" on-press="$:scaleDown()"/>
<button label="+" on-press="$:scaleUp()"/>
<button label="Fit" on-press="$:computeScale()"/>
<button label="Center" on-press="$:computeOffsets()"/>
<button label="Toggle fog" on-press="$:toggleFog()" />
<button label="Toggle floor" on-press="$:toggleFloor()" />
<button label="Get camera" on-press="$:getPosition()" />
<button label="Set camera" on-press="$:setPosition()" />
</button-group>
<button-group mode="row">
<button label="Start" on-press="$:start()" />
<button label="Stop" on-press="$:stop()" />
<button label="Get playhead" on-press="$:getPlayhead()" />
<button label="Set playhead" on-press="$:setPlayhead()" />
</button-group>
</view>
Notice weâve added a few attributes here, each specifying behavior of the 3D model, such as:
-
id
: The unique ID of the model in the view. This ID allows us to identify the component from the viewâs JS/TS. -
scale
: Set the scale -
bind
: The 3D attachment stored in the application database -
wireframe
: Enable or disable -
double-sided-materials
: Specify double-sided materials -
fog
: Enable or disable fog -
floor
: Enable or disable debug info such as the frames rendered per second -
stats
: Enable or disable debug info such as the frames rendered per second -
autoplay
: Enable or disable autoplay, this is useful for 3d models with animations -
loop
: Enable or disable if the animations should play in a loop -
offset-x
: Control x offset -
offset-y
: Control y offset -
offset-z
: Control z offset -
bounding-box
: Enable or disable the bounding-box -
material-vertex-groups
: Enable or disable the material-vertex-groups -
debug-normals
: Enable or disable the to debug model normals -
on-pick-mesh
: Specify a function that fetches the meshes from the model
Now letâs write a few lines of JavaScript or TypeScript code to retrieve the 3D model from the application database and bind it to the display-3d-model
component. Weâll also add a few sample buttons to the view which will provide us with controls.
View Logic
In your main.js or main.ts, paste the following:
// This function is called when the app navigates to this view (using a link)
function init() {
var forklift = DB.forklift.first("name = ?", "Forklift");
view.scene = forklift.model;
view.wireframe = false;
view.fog = true;
view.doubleSided = false;
view.debug = true;
view.floor = true;
view.scale = 0.1;
view.offsetx = 0;
view.offsety = 0;
view.offsetz = 0;
view.bounding = true;
view.vertexGroups = false;
view.normals = false;
}
// This function is called when the user returns to this view from another view
function resume(from) {
// from.back (true/false) if true, the user pressed the "Back" button to return to this view
// from.dismissed (true/false) if true, the app dismissed to return to this view
// from.path contains the path of the view that the user returned from
// if any data needs to be refreshed when the user returns to this view, you can do that here:
}
function normals(){
view.normals = !view.normals;
}
function updateVertexGroups(){
view.vertexGroups = !view.vertexGroups;
}
function save(){
var model = DB.forklift.first();
model.model = view.scene;
model.save();
}
function toggleWireframe(){
view.wireframe = !view.wireframe;
}
function pick($meshes){
for(var i = 0; i < $meshes.length; i++){
console.log($meshes[i]); // {name : "scam_break_line_left_thing"}
}
}
function scaleUp(){
view.scale *= 10;
}
function computeScale(){
view.scale = component.display3dModel({id: "1"}).computeScaleFactor(550);
}
function computeOffsets(){
var offsets = component.display3dModel({id: "1"}).computeCenterOffset();
view.offsetx = offsets[0];
view.offsety = offsets[1];
view.offsetz = offsets[2];
}
function scaleDown(){
view.scale /= 10;
}
function toggleFog(){
view.fog = !view.fog;
}
function toggleFloor(){
view.floor = !view.floor;
}
function getPosition(){
var c = component.display3dModel({id: "1"}).getCamera();
view.camera = JSON.stringify(c);
console.log(c);
}
function setPosition(){
component.display3dModel({id: "1"}).setCamera(JSON.parse(view.camera));
}
function start(){
component.display3dModel({id: "1"}).animationStart();
}
function stop(){
component.display3dModel({id: "1"}).animationStop();
}
function doubleSided(){
view.doubleSided = !view.doubleSided;
}
function stats(){
view.debug = !view.debug;
}
function getPlayhead(){
var pos = component.display3dModel({id: "1"}).getPlayhead();
journey.dialog.simple({
title: 'Playhead position',
message: "The position is: "+ pos
})
}
function setPlayhead(){
var pos = journey.dialog.input({
title: 'Set Playhead position',
message: "enter the value in ms"
});
component.display3dModel({id: "1"}).setPlayhead(pos);
}
In the above example, first, we fetch the 3D model attachment from the application database and bind it to the display-3d-model
component, and then initialize a few default attributes used by the component.
Furthermore, we have created a few functions that give us control over various aspects of the 3D model rendered in the app.
Using the component.display3dModel
function in our view JavaScript or TypeScript file we can manipulate the model programmatically. The code sample above provides a few useful samples of this. Try it out.
Test App
Now, letâs launch the application and inspect our 3D model. To do so, click the âTest Appâ button at the bottom left in OXIDE. Select your preferred platform â the application will work on all of the supported platforms (iOS, Android, Windows, macOS, Linux, and RealWear).