Extending the API
This page shows how to extend the ApertusVR Java API, if needed.
Overview
It may occour, that you want to extend the API, becouse there are entities with missing wrappers, or you wrote new ApertusVR entity, or feature. We will shortly discuss, what you have to do, to make your existing C++ feature in ApertusVR to work on Android using Java.
In this page, we will go through step-by-step on the extension process using the ConeGeometry
entity as an example.
There are three places, where you have to make modifications:
ApertusJNI.java, where you have to register new native functions
apeJNIPlugin shared library, where you have to include new .cpp files, and implement the registered functions
org.apertusvr Java-package, where you have to create new wrappers
ApertusJNI
For each member function in the ape::IConeGeometry
interface, we have to register a static native Java-function in the ApertusJNI.java file. So take a glance at the IConeGeometry
interface:
We are concerned only about the public virtual function. The convention is that we indicate the entity type in the static native function. So in the ConeGeometry
case, it looks like this:
After the set
, get
, is
etc., comes the entity name, then the "subject" (e.g. ParentNode
). Comparing the two set of function headers, note that the rules are:
std::string
turns intoString
ape::Entity
types turn intoString
, as we access them by their IDsape::Vector2
,ape::Color
, etc. turns into separatefloat
values, as we pursue to use primitive types whenever it is possible.enum
types turn intoint
variables
Now, we are done with the ApertusJNI file, we can go forward the next step.
apeJNIPlugin
The JNI-functions are contained in the apeJNIPlugin shared library. For each entity and other component (e.g. ape::CoreConfig
) there is a corresponding apeJNI<entityName>.cpp file (e.g. apeJNICoreConfig.cpp).
This will be the case in the new ConeGeometry
entity, we create an apeJNIConeGeometry.cpp file, where we place the JNI-functions. The rule is that for a static native function in the packaganame
package, and className
class with functionName
name, we have its corresponding JNI-function header as:
E.g. our first function's header will look like:
Next task is to implement the function. We will only show the first two, but you can find all of them on GitHub.
JNI-functions are traditionally C-like functions, as the whole API is designed as a C API (even though we can use any C++ feature in JNI-functions). So how do we access the ape::ISceneManager
, to query and modify our entities? We use the ape::JNIPlugin
as a crutch.
This plugin is a special one in the term of that it acts as a singleton. Therefore we can get a pointer pointing to the plugin (which has access to the whole ApertusVR system) with the ape::JNIPlugin::getPluginPtr()
static function. So we query this pointer, and store it to a variable. Then we convert the entity id into a C-style string with the env
pointer:
At this point we have the id, and we have a pointer pointing to the JNIPlugin, which has access to the sceneManager. This means we can do anything we want!
Okay let us move on. As we are implementing the setParameters(...)
function our task is to set the parameters for the cone with the id name
. This goes naturally, just like we use the ApertusVR C++ API:
Great! Now we are finished with the task, then we only have to clean the mess after us, so we just release the UTF string we recently allocated:
Always call the ReleaseStringUTFChars(...)
function for every GetStringUTFChars(...)
allocation before leaving the function, otherwise it will cause memory leak, just like when somebody forgot to call delete
after new
.
What if we have to return some value? Let us see the next function: getParameters(...)
. The first half of the procedure goes exactly the same as in the case when we are not interested in any returning value. However, when we are, then we should ask for it on the same place where we set the parameters in the previous example:
Good. Then we release the allocated utf chars, and then we can return a value. As it was said, our goal is to always cope with primitve values when it is possible, becouse calling Java constructors from JNI-functions can be really expensive. Thus complex types (like ape::GeometryConeParameters
) are returned as arrays. So in our case this complex type in C++ looks like the following struct
:
This means we will return 3 + 2 float value. So first we create a buffer array:
Then we create a new jfloatArray
with the help of the env
pointer, and then place this buffer into the newly create jfloatArray
, and then just return it:
Now do this for each of the member functions. Then if it is done, one last thing remained: we have to include our new apeJNIConeGeometry.cpp file into the cmake project.
To do this open the CMakeLists.txt file, and add the apeJNIConeGeometry.cpp to the SOURCE
list:
Creating wrapper class
Wrapper classes are stored in the org.apertusvr
package. So navigate to the corresponding folder, and create the new file for the wrapper class, as apeConeGeometry.java!
In the C++ API the ape::IConeGeometry
is a subclass of the ape::Geometry
interface. We have to follow this structure here too. We assume that somebody already created the apeGeometry
wrapper for us, so we just type:
Parameter types
For the complex parameter types in the C++ API, we often create a similar complex type as the subclass of the entity interface. In our case this will look like:
We always make a constructor whose parameter is an array if the values are from the same primitive type. This makes it easier to constructing from the returned array from ApertusJNI.
Constructor
The interfaces only store the name and the type of the given entity. This two value are also an obligatory, otherwise we could not identify the entities without them. Thus, we only write constructors where this two parameters are given to the apeEntity
superclass. In our case, we now that we are working with a ConeGeometry
, so we make a constructor with only one String parameter, and call the superclass's constructor with this String and the apeEntity.Type.GOMETRY_CONE
value:
Member functions
The wrapper's member functions simply call the corresponding static native function in ApertusJNI, with the parameters they got from the user, and with the name which is stored in the wrapper. So in the case of the two function whose implementation were discussed in the previous section, looks like this:
The header of the wrapper's member function are always the some (or equivalent) as in the C++ API. This means here we do not use separate float x
and float y
, but an apeVector2
(which is part of the Java API).
Builder class
For entities like apeConeGeometry
, we should always provide a Builder class, which is an implementation of the apeBuilder
interface. This is really important if we want to cast from an apeEntity
or other superclass, because we have to instantiate one for it.
This builder class is responsible for creating an instance of the wrapper when it appears as a template parameter. So without further explanation, the builder class looks like this:
Last updated