diff --git a/.github/workflows/j3o-scan.yml b/.github/workflows/j3o-scan.yml index 33ccbc1314..bcb1b57a71 100644 --- a/.github/workflows/j3o-scan.yml +++ b/.github/workflows/j3o-scan.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: temurin - java-version: '17' + java-version: '25' - name: Scan J3O assets run: ./gradlew :jme3-desktop:scanJ3O --console=plain diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d93461de56..0da6c76710 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,7 +69,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Validate the Gradle wrapper uses: gradle/actions/wrapper-validation@v6.1.0 - name: Run Checkstyle @@ -96,7 +96,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Validate the Gradle wrapper uses: gradle/actions/wrapper-validation@v6.1.0 - name: Run SpotBugs @@ -120,6 +120,11 @@ jobs: contents: read steps: - uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' - name: Start xvfb run: | Xvfb :99 -ac -screen 0 1024x768x16 & @@ -150,45 +155,6 @@ jobs: **/build/changed-images/** **/build/test-results/** - # Build iOS natives - BuildIosNatives: - name: Build natives for iOS - runs-on: macOS-14 - - steps: - - name: Check default JAVAs - run: echo $JAVA_HOME --- $JAVA_HOME_8_X64 --- $JAVA_HOME_11_X64 --- $JAVA_HOME_17_X64 --- $JAVA_HOME_21_X64 --- - - - name: Setup the java environment - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup the XCode version to 15.1.0 - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 - with: - xcode-version: '15.1.0' - - - name: Clone the repo - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: Validate the Gradle wrapper - uses: gradle/actions/wrapper-validation@v6.1.0 - - - name: Build - run: | - ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ - :jme3-ios-native:build - - - name: Upload natives - uses: actions/upload-artifact@v7.0.1 - with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework - # Build the natives on android BuildAndroidNatives: name: Build natives for android @@ -202,11 +168,11 @@ jobs: with: fetch-depth: 1 - - name: Setup Java 17 + - name: Setup Java 25 uses: actions/setup-java@v5 with: distribution: temurin - java-version: '17' + java-version: '25' - name: Check java version run: java -version @@ -234,7 +200,7 @@ jobs: # Build the engine, we only deploy from ubuntu-latest jdk25 BuildJMonkey: - needs: [BuildAndroidNatives, BuildIosNatives] + needs: [BuildAndroidNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} runs-on: ${{ matrix.os }} strategy: @@ -259,18 +225,18 @@ jobs: with: fetch-depth: 1 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: ${{ matrix.jdk }} + - name: Download natives for android uses: actions/download-artifact@v8.0.1 with: name: android-natives path: build/native - - name: Download natives for iOS - uses: actions/download-artifact@v8.0.1 - with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework - - name: Validate the Gradle wrapper uses: gradle/actions/wrapper-validation@v6.1.0 - name: Build Engine @@ -453,12 +419,12 @@ jobs: with: fetch-depth: 1 - # Setup jdk 21 used for building Maven-style artifacts + # Setup jdk 25 used for building Maven-style artifacts - name: Setup the java environment uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Download natives for android uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -466,12 +432,6 @@ jobs: name: android-natives path: build/native - - name: Download natives for iOS - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework - - name: Rebuild the maven artifacts and upload them to Sonatype's maven-snapshots repo run: | if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; @@ -502,12 +462,12 @@ jobs: with: fetch-depth: 1 - # Setup jdk 21 used for building Sonatype artifacts + # Setup jdk 25 used for building Sonatype artifacts - name: Setup the java environment uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' # Download all the stuff... - name: Download maven artifacts @@ -528,12 +488,6 @@ jobs: name: android-natives path: build/native - - name: Download natives for iOS - uses: actions/download-artifact@v8.0.1 - with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework - - name: Rebuild the maven artifacts and upload them to Sonatype's Central Publisher Portal run: | if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; diff --git a/build.gradle b/build.gradle index 3a1395207c..ebe2df96eb 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,15 @@ apply from: file('version.gradle') allprojects { repositories { + mavenLocal() mavenCentral() + maven { + name = 'CentralSnapshots' + url = uri('https://central.sonatype.com/repository/maven-snapshots/') + mavenContent { + snapshotsOnly() + } + } google() } tasks.withType(Jar) { diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java index fc05da90d6..70c96cbe26 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java @@ -111,6 +111,7 @@ protected void addListeners(GLSurfaceView view) { public void loadSettings(AppSettings settings) { touchInput.loadSettings(settings); + joyInput.loadSettings(settings); } public TouchInput getTouchInput() { diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java index fbbdc8c831..b8216347c1 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java @@ -31,9 +31,7 @@ */ package com.jme3.input.android; -import android.content.Context; import android.opengl.GLSurfaceView; -import android.os.Vibrator; import com.jme3.input.InputManager; import com.jme3.input.JoyInput; import com.jme3.input.Joystick; @@ -42,6 +40,7 @@ import com.jme3.input.event.JoyAxisEvent; import com.jme3.input.event.JoyButtonEvent; import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; @@ -55,9 +54,9 @@ * This class manages all the joysticks and feeds the inputs from each back * to jME's InputManager. * - * This handler also supports the joystick.rumble(rumbleAmount) method. In this - * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate - * if the device has a built-in vibrate motor. + * This handler also supports redirecting joystick.rumble(rumbleAmount) to the + * Android device vibrator if AppSettings#setOnDeviceJoystickRumble(boolean) is + * enabled and the device has a built-in vibrate motor. * * Because Android does not allow for the user to define the intensity of the * vibration, the rumble amount (ie strength) is converted into vibration pulses @@ -92,9 +91,7 @@ public class AndroidJoyInput implements JoyInput { private RawInputListener listener = null; private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue<>(); private AndroidSensorJoyInput sensorJoyInput; - private Vibrator vibrator = null; - private boolean vibratorActive = false; - private long maxRumbleTime = 250; // 250ms + private boolean onDeviceJoystickRumble = false; public AndroidJoyInput(AndroidInputHandler inputHandler) { this.inputHandler = inputHandler; @@ -102,23 +99,13 @@ public AndroidJoyInput(AndroidInputHandler inputHandler) { } public void setView(GLSurfaceView view) { - if (view == null) { - vibrator = null; - } else { - // Get instance of Vibrator from current Context - vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE); - if (vibrator == null) { - logger.log(Level.FINE, "Vibrator Service not found."); - } - } - if (sensorJoyInput != null) { sensorJoyInput.setView(view); } } public void loadSettings(AppSettings settings) { - + onDeviceJoystickRumble = settings.isOnDeviceJoystickRumble(); } public void addEvent(InputEvent event) { @@ -133,8 +120,8 @@ public void pauseJoysticks() { if (sensorJoyInput != null) { sensorJoyInput.pauseSensors(); } - if (vibrator != null && vibratorActive) { - vibrator.cancel(); + if (onDeviceJoystickRumble) { + JmeSystem.setDeviceRumble(0f); } } @@ -183,27 +170,8 @@ public long getInputTimeNanos() { @Override public void setJoyRumble(int joyId, float amount) { - // convert amount to pulses since Android doesn't allow intensity - if (vibrator != null) { - final long rumbleOnDur = (long)(amount * maxRumbleTime); // ms to pulse vibration on - final long rumbleOffDur = maxRumbleTime - rumbleOnDur; // ms to delay between pulses - final long[] rumblePattern = { - 0, // start immediately - rumbleOnDur, // time to leave vibration on - rumbleOffDur // time to delay between vibrations - }; - final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from - -// logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", -// new Object[]{amount, rumbleOnDur, rumbleOffDur}); - - if (rumbleOnDur > 0) { - vibrator.vibrate(rumblePattern, rumbleRepeatFrom); - vibratorActive = true; - } else { - vibrator.cancel(); - vibratorActive = false; - } + if (onDeviceJoystickRumble && JmeSystem.isDeviceRumbleSupported()) { + JmeSystem.setDeviceRumble(amount); } } diff --git a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java index 0ba2d85e0c..0e9b4d9b45 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java +++ b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java @@ -5,6 +5,8 @@ import android.graphics.Bitmap; import android.os.Build; import android.os.Environment; +import android.os.VibrationEffect; +import android.os.Vibrator; import android.view.View; import android.view.inputmethod.InputMethodManager; import com.jme3.audio.AudioRenderer; @@ -33,6 +35,7 @@ public class JmeAndroidSystem extends JmeSystemDelegate { private static View view; private static String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + private static final long DEVICE_RUMBLE_TIME = 250; static { try { @@ -210,6 +213,51 @@ public static String getAudioRendererType() { return audioRendererType; } + @Override + public boolean isDeviceRumbleSupported() { + Vibrator vibrator = getVibrator(); + return vibrator != null && vibrator.hasVibrator(); + } + + @Override + @SuppressWarnings("deprecation") + public void setDeviceRumble(float amount) { + Vibrator vibrator = getVibrator(); + if (vibrator == null || !vibrator.hasVibrator()) { + return; + } + + float rumble = Math.max(0f, Math.min(1f, amount)); + try { + if (rumble <= 0f) { + vibrator.cancel(); + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && vibrator.hasAmplitudeControl()) { + int amplitude = Math.max(1, Math.round(rumble * 255f)); + vibrator.vibrate(VibrationEffect.createWaveform( + new long[]{0, DEVICE_RUMBLE_TIME}, + new int[]{0, amplitude}, + 0)); + } else { + long rumbleOnDur = (long) (rumble * DEVICE_RUMBLE_TIME); + long rumbleOffDur = DEVICE_RUMBLE_TIME - rumbleOnDur; + vibrator.vibrate(new long[]{0, rumbleOnDur, rumbleOffDur}, 0); + } + } catch (SecurityException ignored) { + // Applications without VIBRATE permission should degrade to no-op. + } + } + + private static Vibrator getVibrator() { + View currentView = view; + if (currentView == null || currentView.getContext() == null) { + return null; + } + return (Vibrator) currentView.getContext().getSystemService(Context.VIBRATOR_SERVICE); + } + @Override public void showSoftKeyboard(final boolean show) { view.getHandler().post(new Runnable() { diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java index 54e0bac574..af468ba78a 100644 --- a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java @@ -38,7 +38,6 @@ import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; @@ -98,7 +97,6 @@ public void bakeSphericalHarmonicsCoefficients() { Material mat = new Material(assetManager, "Common/IBLSphH/IBLSphH.j3md"); mat.setTexture("Texture", envMap); - mat.setVector2("Resolution", new Vector2f(envMap.getImage().getWidth(), envMap.getImage().getHeight())); screen.setMaterial(mat); float remapMaxValue = 0; @@ -117,38 +115,21 @@ public void bakeSphericalHarmonicsCoefficients() { mat.clearParam("RemapMaxValue"); } - Texture2D shCoefTx[] = { new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format), new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format) }; + Texture2D shCoefTx = new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format); - FrameBuffer shbaker[] = { new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1), new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1) }; - shbaker[0].setSrgb(false); - shbaker[0].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[0])); + FrameBuffer shbaker = new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1); + shbaker.setSrgb(false); + shbaker.addColorTarget(FrameBufferTarget.newTarget(shCoefTx)); - shbaker[1].setSrgb(false); - shbaker[1].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[1])); + screen.updateLogicalState(0); + screen.updateGeometricState(); - int renderOnT = -1; + renderManager.setCamera(updateAndGetInternalCamera(0, shbaker.getWidth(), shbaker.getHeight(), Vector3f.ZERO, 1, 1000), false); + renderManager.getRenderer().setFrameBuffer(shbaker); + renderManager.renderGeometry(screen); - for (int faceId = 0; faceId < 6; faceId++) { - if (renderOnT != -1) { - int s = renderOnT; - renderOnT = renderOnT == 0 ? 1 : 0; - mat.setTexture("ShCoef", shCoefTx[s]); - } else { - renderOnT = 0; - } - - mat.setInt("FaceId", faceId); - - screen.updateLogicalState(0); - screen.updateGeometricState(); - - renderManager.setCamera(updateAndGetInternalCamera(0, shbaker[renderOnT].getWidth(), shbaker[renderOnT].getHeight(), Vector3f.ZERO, 1, 1000), false); - renderManager.getRenderer().setFrameBuffer(shbaker[renderOnT]); - renderManager.renderGeometry(screen); - } - - ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker[renderOnT].getColorTarget().getFormat().getBitsPerPixel() / 8)); - renderManager.getRenderer().readFrameBufferWithFormat(shbaker[renderOnT], shCoefRaw, shbaker[renderOnT].getColorTarget().getFormat()); + ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker.getColorTarget().getFormat().getBitsPerPixel() / 8)); + renderManager.getRenderer().readFrameBufferWithFormat(shbaker, shCoefRaw, shbaker.getColorTarget().getFormat()); shCoefRaw.rewind(); Image img = new Image(format, NUM_SH_COEFFICIENT, 1, shCoefRaw, ColorSpace.Linear); @@ -164,7 +145,6 @@ public void bakeSphericalHarmonicsCoefficients() { else if (weightAccum != c.a) { LOG.warning("SH weight is not uniform, this may cause issues."); } - } if (remapMaxValue > 0) weightAccum /= remapMaxValue; @@ -176,6 +156,7 @@ else if (weightAccum != c.a) { } EnvMapUtils.prepareShCoefs(shCoef); img.dispose(); + shbaker.dispose(); } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index 9361addba9..ad813db94f 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -427,6 +427,11 @@ public enum Caps { */ DepthTexture, + /** + * Supports hardware depth texture comparison for shadow maps. + */ + TextureShadowCompare, + /** * Supports 32-bit index buffers. */ diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index ae40e05d00..d52dd8eee0 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -453,6 +453,10 @@ private void loadCapabilitiesCommon() { caps.add(Caps.DepthTexture); } + if (gl2 != null || caps.contains(Caps.OpenGLES30)) { + caps.add(Caps.TextureShadowCompare); + } + if (caps.contains(Caps.OpenGL20) || caps.contains(Caps.OpenGLES30) || caps.contains(Caps.WebGL) || hasExtension("GL_OES_depth24")) { caps.add(Caps.Depth24); @@ -2673,7 +2677,7 @@ && isMipmapGenerationSupported(image.getFormat(), } ShadowCompareMode texCompareMode = tex.getShadowCompareMode(); - if ( (gl2 != null || caps.contains(Caps.OpenGLES30)) && curState.shadowCompareMode != texCompareMode) { + if (caps.contains(Caps.TextureShadowCompare) && curState.shadowCompareMode != texCompareMode) { bindTextureAndUnit(target, image, unit); if (texCompareMode != ShadowCompareMode.Off) { gl.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_REF_TO_TEXTURE); diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index 7e5fe0d9aa..6c01c23794 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -330,6 +330,7 @@ public final class AppSettings extends HashMap { defaults.put("JoysticksTriggerToButtonThreshold", 0.5f); defaults.put("JoysticksAxisJitterThreshold", 0.0001f); defaults.put("SDLGameControllerDBResourcePath", ""); + defaults.put("OnDeviceJoystickRumble", false); // defaults.put("Icons", null); } @@ -782,6 +783,15 @@ public void setUseJoysticks(boolean use) { putBoolean("DisableJoysticks", !use); } + /** + * @param enabled If true, joystick rumble requests may be redirected to + * the device rumble motor on supported platforms. + * (Default: false) + */ + public void setOnDeviceJoystickRumble(boolean enabled) { + putBoolean("OnDeviceJoystickRumble", enabled); + } + /** * Set the graphics renderer to use, one of:
*