3D Model API#
This API allows the loading of model files from resource packs, as well as performing basic processing of the model data, and uploading them to the GPU for rendering purpose.
The API consists of the following 5 primary types:
- ModelManager: Provide functions to read model data (RawModel) from resource packs & upload the model in the Loading/Parsing stage.
- RawModel: A holder for multiple meshes combined together. Provide functions for basic manipulation such as translation/rotation etc.
- Model: A
RawModelthat is uploaded to the GPU memory, which can be used for rendering. - RawMeshBuilder: Allow using code to form a model by specifying vertices location.
- DynamicModelHolder: A class containing functions that allows uploading a RawModel during the Runtime stage.
Supported model format#
Currently, only the WaveFront OBJ model format is supported for model loading (bbmodel is not supported). These files ends with the .obj file extension, and are usually available for exports in modelling software like Blender.
ModelManager#
| Functions | Description |
|---|---|
static ModelManager.loadModel(id: Identifier, flipV: boolean = true): RawModel |
Load a model file specified by the Identifier. This returns a RawModel where all object parts are combined together. If flipV is true, the texture's V axis will be mirrored. (Required for OBJ exported from commonly used modelling software like blender) |
static ModelManager.loadModelParts(id: Identifier, flipV: boolean = true): Map<String, RawModel> |
Load a model file specified by the Identifier. Returns a map of String & RawModel, each entry corresponding to an object group (or parts) in the model. This allows selectively picking individual objects out for processing/rendering. If flipV is true, the texture's V axis will be mirrored. (Required to be true for OBJ exported from commonly used modelling software like blender) |
static ModelManager.upload(rawModel: RawModel): Model |
Upload the model data to the GPU so it can be effectively rendered. Returns a Model. Note: This should only be invoked during the Loading/Parsing stage of scripts. Use DynamicModelHolder if you need to upload the model at runtime. |
Show deprecated fields/functions
These are the functions implemented in JCM for backward compatibility with scripts made for MTR-NTE.
Newer scripts should not utilize these functions anymore.
| Functions | Description |
|---|---|
static ModelManager.loadRawModel(null, id: Identifier, null): RawModel? |
Equivalent to ModelManager.loadModel with the flipV parameter set to false.Null if the model loading failed. |
static ModelManager.loadPartedRawModel(null, id: Identifier, null): Map<String, RawModel> |
Equivalent to ModelManager.loadModelParts. |
static ModelManager.uploadVertArrays(rawModel: RawModel): Model |
Equivalent to ModelManager.upload. |
RawModel#
RawModel vs Model
- RawModel only represents the static data of a model, such as it's vertices locations, UVs, object groups, how faces are mapped etc.
- RawModel itself cannot be used for rendering. Instead, you must upload the model via
ModelManager.upload(). - A Model represents an instance of a RawModel that has been uploaded to the GPU for rendering.
| Functions | Description |
|---|---|
new RawModel(): RawModel |
Create a new empty RawModel. |
RawModel.append(other: RawMesh): RawModel |
Append/combine another RawMesh (Usually obtained from RawMeshBuilder) to the current RawModel, and return the current RawModel. |
RawModel.append(other: RawModel): RawModel |
Append/combine another RawModel to the current RawModel, and return the current RawModel. |
RawModel.applyTranslation(x: float, y: float, z: float): void |
Translate all vertices in the model by x, y and z. |
RawModel.applyRotation(direction: Vector3f, angle: float): void |
Apply rotation to all vertices in a direction specified by a Vector3f. Transformation is applied relative to the model's origin. angle is the angle to rotate in degree. |
RawModel.applyScale(x: float, y: float, z: float): void |
Scale the model. Transformation is applied relative to the model's origin. |
RawModel.applyMirror(x: boolean, y: boolean, z: boolean): void |
Mirror the model for the respective axis which are set to true.Both the vertices's Position and Normals are mirrored for the respective axis. |
RawModel.applyUVMirror(u: boolean, v: boolean): void |
Set whether the respective UV axis should be mirrored. A value of (false, true) is identical to the result of flipV in MTR. |
RawModel.setAllRenderType(renderType: String): void |
Set the render type for all object to renderType. Supported values:- EXTERIOR- exteriortranslucent- INTERIOR- INTERIOR_TRANSLUCENT / interiortranslucent- ALWAYS_ON_LIGHT / lighttranslucent- LIGHT |
RawModel.copy(): RawModel |
Copy all vertices and material data and return a new instance of RawModel. |
RawModel.replaceTexture(fileName: string, id: Identifier): void |
Replace all texture ending with fileName to id. |
RawModel.replaceAllTexture(id: Identifier): void |
Replace all texture of this RawModel with id. |
Model (a.k.a. ModelCluster)#
This represents a model with all vertices data uploaded to the GPU, therefore at this stage you can no longer change the vertices data.
However texture replacing operation is still possible.
| Functions | Description |
|---|---|
Model.replaceTexture(fileName: string, id: Identifier): void |
Replace all texture ending with fileName to id. |
Model.replaceAllTexture(id: Identifier): void |
Replace all texture of this Model with id. |
Model.copyForMaterialChanges(): Model |
Returns an instance of Model with the material copied. This allows you to replace textures on the newly created copy, without affecting the existing instance. |
Model.close(): void |
Close this Model instance and free-up resources. |
Rendering#
You may either:
- Pass the uploaded model to RenderManager's
drawModelfunction to render it out. - Or pass in the model to the draw model functions in VehicleScriptContext and EyecandyScriptContext
RawMeshBuilder#
The RawMeshBuilder allows forming a Mesh using code, which can then be used for rendering by using asRawModel (Or constructing RawModel manually with getMesh()) to form a 3D model that can be uploaded to the GPU.
| Functions | Description |
|---|---|
new RawMeshBuilder(faceSize: int, renderType: String, texture: Identifier): RawMeshBuilder |
Forms a new RawMeshBuilder.faceSize is to specify the amount of vertices required to form a face.renderType is one of the following:- EXTERIOR / exterior- exteriortranslucent- INTERIOR- INTERIOR_TRANSLUCENT / interiortranslucent- ALWAYS_ON_LIGHT / lighttranslucent- LIGHTtexture is the Identifier pointing to the texture image. |
RawMeshBuilder.vertex(pos: Vector3f): RawMeshBuilder |
Start building a vertex with the position set to pos. |
RawMeshBuilder.vertex(x: double, y: double, z: double): RawMeshBuilder |
Start building a vertex with the position set to x, y and z. |
RawMeshBuilder.normal(x: double, y: double, z: double): RawMeshBuilder |
Set the normal vector of the currently-building vertex. |
RawMeshBuilder.uv(x: float, y: float): RawMeshBuilder |
Set the UV value of the currently-building vertex. |
RawMeshBuilder.endVertex(): RawMeshBuilder |
Finish building the current vertex. This will add the vertices to the mesh, and form a face if necessary. |
RawMeshBuilder.color(r: int, g: int, b: int, a: int): RawMeshBuilder |
Set the color of the mesh in rgba format. Each channel is an int value from 0 - 255. |
RawMeshBuilder.getMesh(): RawMesh |
Obtain the RawMesh, which can be used to be appended to a RawModel |
RawMeshBuilder.asRawModel(): RawModel |
Creates a new RawModel with the mesh of this RawMeshBuilder. This is a shortcut, equivalent to creating a new RawModel and using RawModel.append(rawMesh: RawMesh). |
Example#
DynamicModelHolder#
The ModelManager.upload() can only be invoked when it is executed in the render thread, which is true during the initial Loading/Parsing stage.
But afterwards when the script's function is invoked during the Runtime stage, it will be executed in one of the Background Script Worker thread, preventing the ability to use ModelManager.upload() to upload the model.
If you need the ability to upload a model during the runtime stage (e.g. Lazy loading / Dynamic manipulation of RawModel), you can do so with this class.
| Functions | Description |
|---|---|
new DynamicModelHolder(): DynamicModelHolder |
Create a new instance of DynamicModelHolder. |
DynamicModelHolder.uploadLater(rawModel: RawModel): void |
Request a model upload. This will schedule a task so that when the next frame comes around, JCM will perform the relevant task to get your model uploaded to the GPU. |
DynamicModelHolder.getUploadedModel(): RawModel? |
Obtain the uploaded GPU model, or null if either uploadLater has never been invoked, or uploadLater has just been invoked and the model has not been uploaded yet. |
DynamicModelHolder.close(): void |
Close the model and free-up resources. You should run this after finish using the model in DynamicModelHolder. Note: If multiple uploadLater has been invoked, the previous model would be closed automatically without the execution of this function. |
Avoid using getUploadedModel() for rendering
Please note that the drawModel function in RenderManager and other places not only take in Model, but also DynamicModelHolder directly. You should use those functions whenever possible.
This is due to uploadLater() scheduling the actual upload operation for the next frame. Thus if you call getUploadedModel() after uploadLater() in the same script execution, you are actually obtaining the old uploaded model result (or null if the model has not yet been uploaded), resulting in a 1-frame gap where the model is not rendered.
The drawModel functions with DynamicModelHolder handles that for you by only obtaining the model in the next frame, in which the model would be uploaded by then. (Hence resolving the issue of flickering effects.)
Use Matrices for dynamic transformation in runtime
While it is possible to use RawModel.applyTranslation / RawModel.applyRotation and re-upload the model with DynamicModelHolder for every frame, it is very heavy in performance. For simple transformation, what you likely want to do is to create a Matrices to perform the transformation, and pass that to the matrices parameter in RenderManager's drawModel function.