Support for external haptics providers

This commit is contained in:
Simon 2022-11-18 21:02:36 +00:00
parent 077cfe159e
commit 7b410f5867
8 changed files with 310 additions and 32 deletions

View file

@ -48,6 +48,7 @@ android {
dependencies {
implementation "com.android.support:support-compat:26.1.0"
implementation "com.android.support:support-core-utils:26.1.0"
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
repositories {

View file

@ -1661,6 +1661,59 @@ void JKVR_processHaptics() {
}
}
extern "C" {
void jni_haptic_event(const char *event, int position, int flags, int intensity, float angle,
float yHeight);
void jni_haptic_updateevent(const char *event, int intensity, float angle);
void jni_haptic_stopevent(const char *event);
void jni_haptic_endframe();
void jni_haptic_enable();
void jni_haptic_disable();
};
void JKVR_ExternalHapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight )
{
jni_haptic_event(event, position, flags, intensity, angle, yHeight);
}
void JKVR_HapticUpdateEvent(const char* event, int intensity, float angle )
{
jni_haptic_updateevent(event, intensity, angle);
}
void JKVR_HapticEndFrame()
{
jni_haptic_endframe();
}
void JKVR_HapticStopEvent(const char* event)
{
jni_haptic_stopevent(event);
}
void JKVR_HapticEnable()
{
static bool firstTime = true;
if (firstTime) {
jni_haptic_enable();
firstTime = false;
jni_haptic_event("fire_pistol", 0, 0, 100, 0, 0);
}
}
void JKVR_HapticDisable()
{
jni_haptic_disable();
}
/*
* event - name of event
* position - for the use of external haptics providers to indicate which bit of haptic hardware should be triggered
* flags - a way for the code to specify which controller to produce haptics on, if 0 then weaponFireChannel is calculated in this function
* intensity - 0-100
* angle - yaw angle (again for external haptics devices) to place the feedback correctly
* yHeight - for external haptics devices to place the feedback correctly
*/
void JKVR_HapticEvent(const char* event, int position, int flags, int intensity, float angle, float yHeight )
{
if (vr_haptic_intensity->value == 0.0f)
@ -1668,9 +1721,8 @@ void JKVR_HapticEvent(const char* event, int position, int flags, int intensity,
return;
}
// engine_t* engine = VR_GetEngine();
// jstring StringArg1 = (*(engine->java.Env))->NewStringUTF(engine->java.Env, event);
// (*(engine->java.Env))->CallVoidMethod(engine->java.Env, engine->java.ActivityObject, android_haptic_event, StringArg1, position, flags, (int)(intensity * vr_hapticIntensity->value), angle, yHeight);
//Pass on to any external services
JKVR_ExternalHapticEvent(event, position, flags, intensity, angle, yHeight);
//Controller Haptic Support
int weaponFireChannel = vr.weapon_stabilised ? 3 : (vr_control_scheme->integer ? 2 : 1);
@ -1977,6 +2029,94 @@ void jni_shutdown()
return env->CallVoidMethod(jniCallbackObj, android_shutdown);
}
jmethodID android_haptic_event;
jmethodID android_haptic_updateevent;
jmethodID android_haptic_stopevent;
jmethodID android_haptic_endframe;
jmethodID android_haptic_enable;
jmethodID android_haptic_disable;
void jni_haptic_event(const char* event, int position, int flags, int intensity, float angle, float yHeight)
{
JNIEnv *env;
jobject tmp;
if ((jVM->GetEnv((void**) &env, JNI_VERSION_1_4))<0)
{
jVM->AttachCurrentThread(&env, NULL);
}
jstring StringArg1 = env->NewStringUTF(event);
return env->CallVoidMethod(jniCallbackObj, android_haptic_event, StringArg1, position, flags, intensity, angle, yHeight);
}
void jni_haptic_updateevent(const char* event, int intensity, float angle)
{
JNIEnv *env;
jobject tmp;
if ((jVM->GetEnv((void**) &env, JNI_VERSION_1_4))<0)
{
jVM->AttachCurrentThread(&env, NULL);
}
jstring StringArg1 = env->NewStringUTF(event);
return env->CallVoidMethod(jniCallbackObj, android_haptic_updateevent, StringArg1, intensity, angle);
}
void jni_haptic_stopevent(const char* event)
{
ALOGV("Calling: jni_haptic_stopevent");
JNIEnv *env;
jobject tmp;
if ((jVM->GetEnv((void**) &env, JNI_VERSION_1_4))<0)
{
jVM->AttachCurrentThread(&env, NULL);
}
jstring StringArg1 = env->NewStringUTF(event);
return env->CallVoidMethod(jniCallbackObj, android_haptic_stopevent, StringArg1);
}
void jni_haptic_endframe()
{
JNIEnv *env;
jobject tmp;
if ((jVM->GetEnv((void**) &env, JNI_VERSION_1_4))<0)
{
jVM->AttachCurrentThread(&env, NULL);
}
return env->CallVoidMethod(jniCallbackObj, android_haptic_endframe);
}
void jni_haptic_enable()
{
ALOGV("Calling: jni_haptic_enable");
JNIEnv *env;
jobject tmp;
if ((jVM->GetEnv((void**) &env, JNI_VERSION_1_4))<0)
{
jVM->AttachCurrentThread(&env, NULL);
}
return env->CallVoidMethod(jniCallbackObj, android_haptic_enable);
}
void jni_haptic_disable()
{
ALOGV("Calling: jni_haptic_disable");
JNIEnv *env;
jobject tmp;
if ((jVM->GetEnv((void**) &env, JNI_VERSION_1_4))<0)
{
jVM->AttachCurrentThread(&env, NULL);
}
return env->CallVoidMethod(jniCallbackObj, android_haptic_disable);
}
int JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
@ -2074,6 +2214,12 @@ JNIEXPORT void JNICALL Java_com_drbeef_jkquest_GLES3JNILib_onStart( JNIEnv * env
jclass callbackClass = env->GetObjectClass( jniCallbackObj);
android_shutdown = env->GetMethodID(callbackClass,"shutdown","()V");
android_haptic_event = env->GetMethodID(callbackClass, "haptic_event", "(Ljava/lang/String;IIIFF)V");
android_haptic_updateevent = env->GetMethodID(callbackClass, "haptic_updateevent", "(Ljava/lang/String;IF)V");
android_haptic_stopevent = env->GetMethodID(callbackClass, "haptic_stopevent", "(Ljava/lang/String;)V");
android_haptic_endframe = env->GetMethodID(callbackClass, "haptic_endframe", "()V");
android_haptic_enable = env->GetMethodID(callbackClass, "haptic_enable", "()V");
android_haptic_disable = env->GetMethodID(callbackClass, "haptic_disable", "()V");
ovrAppThread * appThread = (ovrAppThread *)((size_t)handle);
ovrMessage message;

View file

@ -841,9 +841,6 @@ void CL_Frame ( int msec,float fractionMsec ) {
JKVR_processHaptics();
//trigger frame tick for haptics
JKVR_HapticEvent("frame_tick", 0, 0, 0, 0, 0);
// see if we need to update any userinfo
CL_CheckUserinfo();
@ -894,6 +891,8 @@ void CL_Frame ( int msec,float fractionMsec ) {
Con_RunConsole();
JKVR_HapticEndFrame();
cls.framecount++;
}

View file

@ -4792,7 +4792,9 @@ Ghoul2 Insert End
{
cvar_t *vr_saber_block_debounce_time = gi.cvar("vr_saber_block_debounce_time", "200", CVAR_ARCHIVE); // defined in VrCvars.h
vr->saberBlockDebounce = cg.time + vr_saber_block_debounce_time->integer;
}
cgi_HapticEvent("shotgun_fire", 0, 0, 100, 0, 0);
}
if (CG_getPlayer1stPersonSaber(cent) &&
cent->gent->client->ps.saberLockEnemy != ENTITYNUM_NONE)

View file

@ -2004,6 +2004,12 @@ wasForceSpeed=isForceSpeed;
cgi_CM_SnapPVS( cg.refdef.vieworg, cg.snap->areamask );
}
if (cg.predicted_player_state.stats[STAT_HEALTH] > 0 &&
cg.predicted_player_state.stats[STAT_HEALTH] < 40)
{
cgi_HapticEvent("heartbeat", 0, 0, cg.predicted_player_state.stats[STAT_HEALTH], 0, 0);
}
if (vr->item_selector)
{
CG_DrawItemSelector();

View file

@ -3241,7 +3241,7 @@ void CG_FireWeapon( centity_t *cent, qboolean alt_fire )
//Haptics
switch (ent->weapon) {
case WP_SABER:
cgi_HapticEvent("chainsaw_fire", position, 0, 50, 0, 0);
cgi_HapticEvent("chainsaw_fire", position, 0, 40, 0, 0);
break;
case WP_BRYAR_PISTOL:
case WP_BOWCASTER:

Binary file not shown.

View file

@ -2,6 +2,27 @@
package com.drbeef.jkquest;
import static android.system.Os.setenv;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import com.drbeef.externalhapticsservice.HapticServiceClient;
import com.drbeef.externalhapticsservice.HapticsConstants;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
@ -10,35 +31,14 @@ import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import static android.system.Os.setenv;
import java.util.Vector;
@SuppressLint("SdCardPath") public class GLES3JNIActivity extends Activity implements SurfaceHolder.Callback
{
private static String game = "";
private boolean hapticsEnabled = false;
// Load the gles3jni library right away to make sure JNI_OnLoad() gets called as the very first thing.
static
{
@ -85,11 +85,21 @@ import static android.system.Os.setenv;
// Main components
protected static GLES3JNIActivity mSingleton;
private Vector<HapticServiceClient> externalHapticsServiceClients = new Vector<>();
//Use a vector of pairs, it is possible a given package _could_ in the future support more than one haptic service
//so a map here of Package -> Action would not work.
private static Vector<Pair<String, String>> externalHapticsServiceDetails = new Vector<>();
public static void initialize() {
// The static nature of the singleton and Android quirkyness force us to initialize everything here
// Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
mSingleton = null;
//Add possible external haptic service details here
externalHapticsServiceDetails.add(Pair.create(HapticsConstants.BHAPTICS_PACKAGE, HapticsConstants.BHAPTICS_ACTION_FILTER));
externalHapticsServiceDetails.add(Pair.create(HapticsConstants.FORCETUBE_PACKAGE, HapticsConstants.FORCETUBE_ACTION_FILTER));
}
public void shutdown() {
@ -235,6 +245,16 @@ import static android.system.Os.setenv;
}
for (Pair<String, String> serviceDetail : externalHapticsServiceDetails) {
HapticServiceClient client = new HapticServiceClient(this, (state, desc) -> {
Log.v(APPLICATION, "ExternalHapticsService " + serviceDetail.second + ": " + desc);
}, new Intent(serviceDetail.second)
.setPackage(serviceDetail.first));
client.bindService();
externalHapticsServiceClients.add(client);
}
mNativeHandle = GLES3JNILib.onCreate( this, commandLineParams );
}
@ -371,4 +391,108 @@ import static android.system.Os.setenv;
mSurfaceHolder = null;
}
}
public void haptic_event(String event, int position, int flags, int intensity, float angle, float yHeight) {
boolean areHapticsEnabled = hapticsEnabled;
for (HapticServiceClient externalHapticsServiceClient : externalHapticsServiceClients) {
if (externalHapticsServiceClient.hasService()) {
try {
//Enabled all haptics services if required
if (!areHapticsEnabled)
{
externalHapticsServiceClient.getHapticsService().hapticEnable();
hapticsEnabled = true;
continue;
}
//Uses the Doom3Quest and RTCWQuest haptic patterns
String app = "Doom3Quest";
String eventID = event;
if (event.contains(":"))
{
String[] items = event.split(":");
app = items[0];
eventID = items[1];
}
externalHapticsServiceClient.getHapticsService().hapticEvent(app, eventID, position, flags, intensity, angle, yHeight);
}
catch (RemoteException r)
{
Log.v(TAG, r.toString());
}
}
}
}
public void haptic_updateevent(String event, int intensity, float angle) {
for (HapticServiceClient externalHapticsServiceClient : externalHapticsServiceClients) {
if (externalHapticsServiceClient.hasService()) {
try {
externalHapticsServiceClient.getHapticsService().hapticUpdateEvent(APPLICATION, event, intensity, angle);
} catch (RemoteException r) {
Log.v(APPLICATION, r.toString());
}
}
}
}
public void haptic_stopevent(String event) {
for (HapticServiceClient externalHapticsServiceClient : externalHapticsServiceClients) {
if (externalHapticsServiceClient.hasService()) {
try {
externalHapticsServiceClient.getHapticsService().hapticStopEvent(APPLICATION, event);
} catch (RemoteException r) {
Log.v(APPLICATION, r.toString());
}
}
}
}
public void haptic_endframe() {
for (HapticServiceClient externalHapticsServiceClient : externalHapticsServiceClients) {
if (externalHapticsServiceClient.hasService()) {
try {
externalHapticsServiceClient.getHapticsService().hapticFrameTick();
} catch (RemoteException r) {
Log.v(APPLICATION, r.toString());
}
}
}
}
public void haptic_enable() {
for (HapticServiceClient externalHapticsServiceClient : externalHapticsServiceClients) {
if (externalHapticsServiceClient.hasService()) {
try {
externalHapticsServiceClient.getHapticsService().hapticEnable();
} catch (RemoteException r) {
Log.v(APPLICATION, r.toString());
}
}
}
}
public void haptic_disable() {
for (HapticServiceClient externalHapticsServiceClient : externalHapticsServiceClients) {
if (externalHapticsServiceClient.hasService()) {
try {
externalHapticsServiceClient.getHapticsService().hapticDisable();
} catch (RemoteException r) {
Log.v(APPLICATION, r.toString());
}
}
}
}
}