From 2e084480ccf9e623cfba6eae79b0d787e81ed462 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Mon, 23 Sep 2024 19:49:01 -0600 Subject: [PATCH 01/12] Fix plugin fs to depend on TOML plugin and author rather than filename --- .../eocvsim/plugin/loader/PluginClassLoader.kt | 8 ++++---- .../eocvsim/plugin/loader/PluginContext.kt | 2 +- .../deltacv/eocvsim/plugin/loader/PluginLoader.kt | 15 +++++++++------ build.gradle | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt index daf81e8a..af498d85 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginClassLoader.kt @@ -43,7 +43,7 @@ import java.util.zip.ZipFile * @param pluginJar the jar file of the plugin * @param pluginContext the plugin context */ -class PluginClassLoader(private val pluginJar: File, val pluginContext: PluginContext) : ClassLoader() { +class PluginClassLoader(private val pluginJar: File, val pluginContextProvider: () -> PluginContext) : ClassLoader() { private val zipFile = try { ZipFile(pluginJar) @@ -65,7 +65,7 @@ class PluginClassLoader(private val pluginJar: File, val pluginContext: PluginCo SysUtil.copyStream(inStream, outStream) val bytes = outStream.toByteArray() - if(!pluginContext.hasSuperAccess) + if(!pluginContextProvider().hasSuperAccess) MethodCallByteCodeChecker(bytes, dynamicLoadingMethodBlacklist) val clazz = defineClass(name, bytes, 0, bytes.size) @@ -85,7 +85,7 @@ class PluginClassLoader(private val pluginJar: File, val pluginContext: PluginCo * @throws IllegalAccessError if the class is blacklisted */ fun loadClassStrict(name: String): Class<*> { - if(!pluginContext.hasSuperAccess) { + if(!pluginContextProvider().hasSuperAccess) { for (blacklistedPackage in dynamicLoadingPackageBlacklist) { if (name.contains(blacklistedPackage)) { throw IllegalAccessError("Plugins are blacklisted to use $name") @@ -109,7 +109,7 @@ class PluginClassLoader(private val pluginJar: File, val pluginContext: PluginCo } } - if(!inWhitelist && !pluginContext.hasSuperAccess) { + if(!inWhitelist && !pluginContextProvider().hasSuperAccess) { throw IllegalAccessError("Plugins are not whitelisted to use $name") } diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginContext.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginContext.kt index f071c849..6fe21731 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginContext.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginContext.kt @@ -31,7 +31,7 @@ class PluginContext( val eocvSim: EOCVSim, val fileSystem: SandboxFileSystem, val loader: PluginLoader ) { companion object { - @JvmStatic fun current(plugin: EOCVSimPlugin) = (plugin.javaClass.classLoader as PluginClassLoader).pluginContext + @JvmStatic fun current(plugin: EOCVSimPlugin) = (plugin.javaClass.classLoader as PluginClassLoader).pluginContextProvider() } val plugin get() = loader.plugin diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt index 004a2420..f239d4d3 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt @@ -78,8 +78,8 @@ class PluginLoader(val pluginFile: File, val eocvSim: EOCVSim) { lateinit var fileSystem: SandboxFileSystem private set - val fileSystemZip = PluginManager.FILESYSTEMS_FOLDER + File.separator + "${hash()}-fs" - val fileSystemZipPath = fileSystemZip.toPath() + val fileSystemZip by lazy { PluginManager.FILESYSTEMS_FOLDER + File.separator + "${hash()}-fs" } + val fileSystemZipPath by lazy { fileSystemZip.toPath() } /** * Whether the plugin has super access (full system access) @@ -87,8 +87,9 @@ class PluginLoader(val pluginFile: File, val eocvSim: EOCVSim) { val hasSuperAccess get() = eocvSim.config.superAccessPluginHashes.contains(pluginHash) init { - setupFs() - pluginClassLoader = PluginClassLoader(pluginFile, PluginContext(eocvSim, fileSystem, this)) + pluginClassLoader = PluginClassLoader(pluginFile) { + PluginContext(eocvSim, fileSystem, this) + } } /** @@ -111,6 +112,8 @@ class PluginLoader(val pluginFile: File, val eocvSim: EOCVSim) { logger.info("Loading plugin $pluginName v$pluginVersion by $pluginAuthor") + setupFs() + if(pluginToml.contains("api-version")) { val parsedVersion = ParsedVersion(pluginToml.getString("api-version")) @@ -190,12 +193,12 @@ class PluginLoader(val pluginFile: File, val eocvSim: EOCVSim) { } /** - * Get the hash of the plugin file based off the absolute path + * Get the hash of the plugin file based off the plugin name and author * @return the hash */ fun hash(): String { val messageDigest = MessageDigest.getInstance("SHA-256") - messageDigest.update(pluginFile.absolutePath.encodeToByteArray()) + messageDigest.update("${pluginName} by ${pluginAuthor}".toByteArray()) return SysUtil.byteArray2Hex(messageDigest.digest()) } diff --git a/build.gradle b/build.gradle index d99a652a..b5c9d98d 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ plugins { allprojects { group 'com.github.deltacv' - version '3.7.0' + version '3.7.1' apply plugin: 'java' From 94d4a6f98ba92b8572bad7f6f532501ae758cf4f Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Tue, 24 Sep 2024 22:13:26 -0600 Subject: [PATCH 02/12] Run dialogs in a thread executor --- .run/Run Simulator.run.xml | 45 ++++++++++--------- .../eocvsim/gui/DialogFactory.java | 9 +++- .../serivesmejia/eocvsim/gui/Visualizer.java | 2 +- .../component/tuner/TunableFieldPanel.java | 2 +- .../compiler/CompiledPipelineManager.kt | 9 ++-- .../eocvsim/virtualreflect/jvm/Label.java | 23 ++++++++++ TeamCode/build.gradle | 7 ++- 7 files changed, 62 insertions(+), 35 deletions(-) diff --git a/.run/Run Simulator.run.xml b/.run/Run Simulator.run.xml index df0d196d..2e975946 100644 --- a/.run/Run Simulator.run.xml +++ b/.run/Run Simulator.run.xml @@ -1,23 +1,24 @@ - - - - - - - true - true - false - - + + + + + + + true + true + false + false + + \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java index e080714d..fd19617d 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java @@ -39,10 +39,15 @@ import java.awt.*; import java.io.File; import java.util.ArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; import java.util.function.IntConsumer; public class DialogFactory { + private static Executor executor = Executors.newFixedThreadPool(4); + private DialogFactory() { } public static void createYesOrNo(Component parent, String message, String submessage, IntConsumer result) { @@ -57,7 +62,7 @@ public static void createYesOrNo(Component parent, String message, String submes panel.setLayout(new GridLayout(2, 1)); } - SwingUtilities.invokeLater(() -> result.accept( + invokeLater(() -> result.accept( JOptionPane.showConfirmDialog(parent, panel, "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE @@ -160,7 +165,7 @@ public static FileAlreadyExists.UserChoice createFileAlreadyExistsDialog(EOCVSim } private static void invokeLater(Runnable runn) { - SwingUtilities.invokeLater(runn); + executor.execute(runn); } public static class FileChooser { diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java index 4a14718e..9e8e0bfa 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java @@ -341,7 +341,7 @@ public void asyncCompilePipelines() { menuBar.workspCompile.setEnabled(false); pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(false); - eocvSim.pipelineManager.compiledPipelineManager.asyncCompile(true, (result) -> { + eocvSim.pipelineManager.compiledPipelineManager.asyncCompile((result) -> { menuBar.workspCompile.setEnabled(true); pipelineSelectorPanel.getButtonsPanel().getPipelineCompileBtt().setEnabled(true); diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java index 523e2e87..38618973 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/tuner/TunableFieldPanel.java @@ -33,7 +33,7 @@ import javax.swing.border.SoftBevelBorder; import java.awt.*; -@SuppressWarnings("Unchecked") +@SuppressWarnings("unchecked") public class TunableFieldPanel extends JPanel { public final TunableField tunableField; diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt index 1f43b1fc..08798cf0 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/compiler/CompiledPipelineManager.kt @@ -88,7 +88,7 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { fun init() { logger.info("Initializing...") - asyncCompile(false) + asyncCompile() workspaceManager.onWorkspaceChange { asyncCompile() @@ -189,7 +189,7 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { return result } - fun compile(fixSelectedPipeline: Boolean = true) = try { + fun compile() = try { runBlocking { uncheckedCompile() } } catch(e: Throwable) { isBuildRunning = false @@ -200,7 +200,7 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { |Unexpected exception thrown while the build was running | |$stacktrace - | + | |If this seems like a bug, please open an issue in the EOCV-Sim github repo """.trimMargin() @@ -217,10 +217,9 @@ class CompiledPipelineManager(private val pipelineManager: PipelineManager) { @JvmOverloads @OptIn(DelicateCoroutinesApi::class) fun asyncCompile( - fixSelectedPipeline: Boolean = true, endCallback: (PipelineCompileResult) -> Unit = {} ) = GlobalScope.launch(Dispatchers.IO) { - endCallback(compile(fixSelectedPipeline)) + endCallback(compile()) } private fun deleteJarFile() { diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/Label.java b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/Label.java index 0b1af0f2..18069124 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/Label.java +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/virtualreflect/jvm/Label.java @@ -1,3 +1,26 @@ +/* + * Copyright (c) 2024 Sebastian Erives + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + package io.github.deltacv.eocvsim.virtualreflect.jvm; import java.lang.annotation.ElementType; diff --git a/TeamCode/build.gradle b/TeamCode/build.gradle index a1e9743b..be3aaf0e 100644 --- a/TeamCode/build.gradle +++ b/TeamCode/build.gradle @@ -8,13 +8,12 @@ apply from: '../build.common.gradle' dependencies { implementation project(':EOCV-Sim') - implementation "com.github.deltacv.AprilTagDesktop:AprilTagDesktop:$apriltag_plugin_version" - implementation "org.jetbrains.kotlin:kotlin-stdlib" } -task(runSim, dependsOn: 'classes', type: JavaExec) { - main = 'com.github.serivesmejia.eocvsim.Main' +tasks.register('runSim', JavaExec) { + dependsOn 'classes' + mainClass = 'com.github.serivesmejia.eocvsim.Main' classpath = sourceSets.main.runtimeClasspath } \ No newline at end of file From 5649923bd8e34fdec1897f35df8d2e39865a55f6 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Wed, 25 Sep 2024 15:38:51 -0600 Subject: [PATCH 03/12] Allow JVM args in JavaProcess --- .../serivesmejia/eocvsim/gui/DialogFactory.java | 4 +--- .../github/serivesmejia/eocvsim/gui/Visualizer.java | 2 +- .../github/serivesmejia/eocvsim/util/JavaProcess.java | 11 +++++++---- .../deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt | 2 +- .../deltacv/eocvsim/plugin/loader/PluginLoader.kt | 2 +- .../deltacv/eocvsim/plugin/loader/PluginManager.kt | 5 ++++- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java index fd19617d..80f7f09c 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/DialogFactory.java @@ -46,8 +46,6 @@ public class DialogFactory { - private static Executor executor = Executors.newFixedThreadPool(4); - private DialogFactory() { } public static void createYesOrNo(Component parent, String message, String submessage, IntConsumer result) { @@ -165,7 +163,7 @@ public static FileAlreadyExists.UserChoice createFileAlreadyExistsDialog(EOCVSim } private static void invokeLater(Runnable runn) { - executor.execute(runn); + SwingUtilities.invokeLater(runn); } public static class FileChooser { diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java index 9e8e0bfa..3b9f6512 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/Visualizer.java @@ -266,7 +266,7 @@ public void mouseClicked(MouseEvent e) { public void joinInit() { while (!hasFinishedInitializing) { - Thread.yield(); + Thread.onSpinWait(); } } diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java index 0f6f5ff4..55a8041a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java @@ -43,7 +43,7 @@ private JavaProcess() {} * @throws InterruptedException if the process is interrupted * @throws IOException if an I/O error occurs */ - public static int execClasspath(Class klass, String classpath, String... args) throws InterruptedException, IOException { + public static int execClasspath(Class klass, String classpath, List jvmArgs, List args) throws InterruptedException, IOException { String javaHome = System.getProperty("java.home"); String javaBin = javaHome + File.separator + "bin" + @@ -52,11 +52,14 @@ public static int execClasspath(Class klass, String classpath, String... args) t List command = new LinkedList<>(); command.add(javaBin); + if(jvmArgs != null) { + command.addAll(jvmArgs); + } command.add("-cp"); command.add(classpath); command.add(className); if (args != null) { - command.addAll(List.of(args)); + command.addAll(args); } ProcessBuilder builder = new ProcessBuilder(command); @@ -67,8 +70,8 @@ public static int execClasspath(Class klass, String classpath, String... args) t } - public static int exec(Class klass, String... args) throws InterruptedException, IOException { - return execClasspath(klass, System.getProperty("java.class.path"), args); + public static int exec(Class klass, List jvmArgs, List args) throws InterruptedException, IOException { + return execClasspath(klass, System.getProperty("java.class.path"), jvmArgs, args); } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt index 72e7b883..6b8d8074 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/gui/dialog/SuperAccessRequest.kt @@ -103,7 +103,7 @@ class SuperAccessRequest(sourceName: String, reason: String) { // Setup the frame frame.contentPane = panel frame.isAlwaysOnTop = true - frame.defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE + frame.defaultCloseOperation = JDialog.HIDE_ON_CLOSE frame.isResizable = false frame.addWindowListener(object: WindowListener { diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt index f239d4d3..c47ebafe 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt @@ -121,7 +121,7 @@ class PluginLoader(val pluginFile: File, val eocvSim: EOCVSim) { throw UnsupportedPluginException("Plugin request api version of v${parsedVersion}, EOCV-Sim is currently running at v${EOCVSim.PARSED_VERSION}") } - if(pluginToml.contains("super-access") && pluginToml.getBoolean("super-access", false)) { + if(pluginToml.getBoolean("super-access", false)) { requestSuperAccess(pluginToml.getString("super-access-reason", "")) } diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt index c1402cbf..ff137677 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt @@ -30,6 +30,7 @@ import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder import com.github.serivesmejia.eocvsim.util.loggerForThis import io.github.deltacv.eocvsim.gui.dialog.SuperAccessRequestMain import java.io.File +import java.util.* /** * Manages the loading, enabling and disabling of plugins @@ -145,6 +146,8 @@ class PluginManager(val eocvSim: EOCVSim) { fun requestSuperAccessFor(loader: PluginLoader, reason: String): Boolean { if(loader.hasSuperAccess) return true + logger.info("Requesting super access for ${loader.pluginName} v${loader.pluginVersion}") + var warning = "$GENERIC_SUPERACCESS_WARN" if(reason.trim().isNotBlank()) { warning += "

$reason" @@ -156,7 +159,7 @@ class PluginManager(val eocvSim: EOCVSim) { val name = "${loader.pluginName} by ${loader.pluginAuthor}".replace(" ", "-") - if(JavaProcess.exec(SuperAccessRequestMain::class.java, name, warning) == 171) { + if(JavaProcess.exec(SuperAccessRequestMain::class.java, null, Arrays.asList(name, warning)) == 171) { eocvSim.config.superAccessPluginHashes.add(loader.pluginHash) eocvSim.configManager.saveToFile() return true From 898946d5edd7f8dd00099fa8ef7ccc09f7ae4d7f Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Thu, 26 Sep 2024 00:12:48 -0600 Subject: [PATCH 04/12] Allow JavaProcess to implements custom IO --- .../eocvsim/util/JavaProcess.java | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java index 55a8041a..451ad89c 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/JavaProcess.java @@ -25,25 +25,34 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.LinkedList; import java.util.List; /** - * A utility class for executing a Java process with a class within this project. + * A utility class for executing a Java process to run a main class within this project. */ public final class JavaProcess { + public interface ProcessIOReceiver { + void receive(InputStream in, InputStream err); + } + private JavaProcess() {} /** * Executes a Java process with the given class and arguments. * @param klass the class to execute + * @param ioReceiver the receiver for the process' input and error streams (will use inheritIO if null) + * @param classpath the classpath to use + * (use System.getProperty("java.class.path") for the default classpath) + * @param jvmArgs the JVM arguments to pass to the process * @param args the arguments to pass to the class * @return the exit value of the process * @throws InterruptedException if the process is interrupted * @throws IOException if an I/O error occurs */ - public static int execClasspath(Class klass, String classpath, List jvmArgs, List args) throws InterruptedException, IOException { + public static int execClasspath(Class klass, ProcessIOReceiver ioReceiver, String classpath, List jvmArgs, List args) throws InterruptedException, IOException { String javaHome = System.getProperty("java.home"); String javaBin = javaHome + File.separator + "bin" + @@ -52,7 +61,7 @@ public static int execClasspath(Class klass, String classpath, List jvmA List command = new LinkedList<>(); command.add(javaBin); - if(jvmArgs != null) { + if (jvmArgs != null) { command.addAll(jvmArgs); } command.add("-cp"); @@ -64,14 +73,38 @@ public static int execClasspath(Class klass, String classpath, List jvmA ProcessBuilder builder = new ProcessBuilder(command); - Process process = builder.inheritIO().start(); - process.waitFor(); - return process.exitValue(); + if (ioReceiver != null) { + Process process = builder.start(); + ioReceiver.receive(process.getInputStream(), process.getErrorStream()); + killOnExit(process); + + process.waitFor(); + return process.exitValue(); + } else { + builder.inheritIO(); + Process process = builder.start(); + killOnExit(process); + + process.waitFor(); + return process.exitValue(); + } } + private static void killOnExit(Process process) { + Runtime.getRuntime().addShutdownHook(new Thread(process::destroy)); + } + /** + * Executes a Java process with the given class and arguments. + * @param klass the class to execute + * @param jvmArgs the JVM arguments to pass to the process + * @param args the arguments to pass to the class + * @return the exit value of the process + * @throws InterruptedException if the process is interrupted + * @throws IOException if an I/O error occurs + */ public static int exec(Class klass, List jvmArgs, List args) throws InterruptedException, IOException { - return execClasspath(klass, System.getProperty("java.class.path"), jvmArgs, args); + return execClasspath(klass, null, System.getProperty("java.class.path"), jvmArgs, args); } } \ No newline at end of file From d418c43d51c63a1dcbe8c2eff8ae7b36b571c4a2 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Thu, 26 Sep 2024 12:46:28 -0600 Subject: [PATCH 05/12] Update skiko to latest --- .../pipeline/util/PipelineExceptionTracker.kt | 1 - .../eventloop/opmode/OpModePipelineHandler.kt | 3 +-- .../main/java/android/graphics/Bitmap.java | 4 ++++ .../main/java/android/graphics/Canvas.java | 4 ++++ .../main/java/android/graphics/FontCache.java | 5 ++++- .../src/main/java/android/graphics/Paint.java | 4 ++++ .../src/main/java/android/graphics/Path.java | 4 ++++ .../main/java/android/graphics/Typeface.java | 4 ++++ .../external/gui/SwingOpenCvViewport.kt | 7 +++---- .../openftc/easyopencv/OpenCvPipeline.java | 21 +++++++++++++++++++ build.gradle | 2 +- 11 files changed, 50 insertions(+), 9 deletions(-) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt index e992a0d1..8b113626 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/util/PipelineExceptionTracker.kt @@ -32,7 +32,6 @@ class PipelineExceptionTracker(private val pipelineManager: PipelineManager) { companion object { const val millisExceptionExpire = 35000L - const val cutStacktraceLines = 9 } val logger by loggerForThis() diff --git a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt index 06ee88aa..dda9ea55 100644 --- a/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt +++ b/EOCV-Sim/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpModePipelineHandler.kt @@ -45,8 +45,7 @@ class OpModePipelineHandler(val inputSourceManager: InputSourceManager, private ThreadSourceHander.register(VisionInputSourceHander(pipeline?.notifier ?: return, viewport)) pipeline?.telemetry = telemetry - pipeline?.hardwareMap = HardwareMap(); - } + pipeline?.hardwareMap = HardwareMap() } override fun init() { } diff --git a/Vision/src/main/java/android/graphics/Bitmap.java b/Vision/src/main/java/android/graphics/Bitmap.java index fcb2d005..8210c5f3 100644 --- a/Vision/src/main/java/android/graphics/Bitmap.java +++ b/Vision/src/main/java/android/graphics/Bitmap.java @@ -211,6 +211,10 @@ public static Bitmap createBitmap(int width, int height, Config config) { return bm; } + /** + * Internal: theBitmap represents the underlying skiko Bitmap + * This field is not present in native android.graphics + */ public final org.jetbrains.skia.Bitmap theBitmap; public Bitmap() { diff --git a/Vision/src/main/java/android/graphics/Canvas.java b/Vision/src/main/java/android/graphics/Canvas.java index 1bea8576..17f7605c 100644 --- a/Vision/src/main/java/android/graphics/Canvas.java +++ b/Vision/src/main/java/android/graphics/Canvas.java @@ -28,6 +28,10 @@ public class Canvas { + /** + * Internal: theCanvas represents the underlying skiko Canvas + * This field is not present in native android.graphics + */ public final org.jetbrains.skia.Canvas theCanvas; private Bitmap backingBitmap = null; diff --git a/Vision/src/main/java/android/graphics/FontCache.java b/Vision/src/main/java/android/graphics/FontCache.java index 052c090c..7b238e98 100644 --- a/Vision/src/main/java/android/graphics/FontCache.java +++ b/Vision/src/main/java/android/graphics/FontCache.java @@ -27,11 +27,14 @@ import java.util.HashMap; +/** + * A cache for fonts to avoid creating the same font multiple times. + */ class FontCache { private static HashMap> cache = new HashMap<>(); - public static Font makeFont(Typeface theTypeface, float textSize) { + static Font makeFont(Typeface theTypeface, float textSize) { if(!cache.containsKey(theTypeface)) { cache.put(theTypeface, new HashMap<>()); } diff --git a/Vision/src/main/java/android/graphics/Paint.java b/Vision/src/main/java/android/graphics/Paint.java index 2abbccb1..201e8116 100644 --- a/Vision/src/main/java/android/graphics/Paint.java +++ b/Vision/src/main/java/android/graphics/Paint.java @@ -176,6 +176,10 @@ public static class FontMetrics { public float leading; } + /** + * Internal: thePaint represents the underlying skiko paint + * This field is not present in native android.graphics + */ public org.jetbrains.skia.Paint thePaint; private Typeface typeface; diff --git a/Vision/src/main/java/android/graphics/Path.java b/Vision/src/main/java/android/graphics/Path.java index 99abbcdd..178d7f90 100644 --- a/Vision/src/main/java/android/graphics/Path.java +++ b/Vision/src/main/java/android/graphics/Path.java @@ -60,6 +60,10 @@ public enum Direction { final int nativeInt; } + /** + * Internal: thePath represents the underlying skiko Path + * This field is not present in native android.graphics + */ public org.jetbrains.skia.Path thePath; public Path() { diff --git a/Vision/src/main/java/android/graphics/Typeface.java b/Vision/src/main/java/android/graphics/Typeface.java index b6967f6e..a2aa44e4 100644 --- a/Vision/src/main/java/android/graphics/Typeface.java +++ b/Vision/src/main/java/android/graphics/Typeface.java @@ -32,6 +32,10 @@ public class Typeface { public static Typeface DEFAULT_BOLD = new Typeface(FontMgr.Companion.getDefault().matchFamilyStyle(null, FontStyle.Companion.getBOLD())); public static Typeface DEFAULT_ITALIC = new Typeface(FontMgr.Companion.getDefault().matchFamilyStyle(null, FontStyle.Companion.getITALIC())); + /** + * Internal: theTypeface represents the underlying skiko Typeface + * This field is not present in native android.graphics + */ public org.jetbrains.skia.Typeface theTypeface; public Typeface(long ptr) { diff --git a/Vision/src/main/java/io/github/deltacv/vision/external/gui/SwingOpenCvViewport.kt b/Vision/src/main/java/io/github/deltacv/vision/external/gui/SwingOpenCvViewport.kt index 8bfc0763..300e15af 100644 --- a/Vision/src/main/java/io/github/deltacv/vision/external/gui/SwingOpenCvViewport.kt +++ b/Vision/src/main/java/io/github/deltacv/vision/external/gui/SwingOpenCvViewport.kt @@ -27,9 +27,9 @@ import android.graphics.Canvas import io.github.deltacv.common.image.MatPoster import org.firstinspires.ftc.robotcore.internal.collections.EvictingBlockingQueue import org.jetbrains.skia.Color -import org.jetbrains.skiko.GenericSkikoView import org.jetbrains.skiko.SkiaLayer -import org.jetbrains.skiko.SkikoView +import org.jetbrains.skiko.SkiaLayerRenderDelegate +import org.jetbrains.skiko.SkikoRenderDelegate import org.opencv.android.Utils import org.opencv.core.Mat import org.opencv.core.Size @@ -44,7 +44,6 @@ import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.TimeUnit import javax.swing.JComponent import javax.swing.SwingUtilities -import kotlin.jvm.Throws class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Vision") : OpenCvViewport, MatPoster { @@ -94,7 +93,7 @@ class SwingOpenCvViewport(size: Size, fpsMeterDescriptor: String = "deltacv Visi framebufferRecycler!!.returnMat(value) } - skiaLayer.skikoView = GenericSkikoView(skiaLayer, object: SkikoView { + skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object: SkikoRenderDelegate { override fun onRender(canvas: org.jetbrains.skia.Canvas, width: Int, height: Int, nanoTime: Long) { renderCanvas(Canvas(canvas, width, height)) diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java index 13cac400..2daee177 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvPipeline.java @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2019 OpenFTC Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package org.openftc.easyopencv; import android.graphics.Canvas; diff --git a/build.gradle b/build.gradle index b5c9d98d..33730078 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { log4j_version = "2.17.1" opencv_version = "4.7.0-0" apriltag_plugin_version = "2.0.0-C" - skiko_version = "0.7.75" + skiko_version = "0.8.15" classgraph_version = "4.8.108" opencsv_version = "5.5.2" From f818d259b3dbf00a85d2cd361a05abe40c50a660 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Fri, 27 Sep 2024 00:37:39 -0600 Subject: [PATCH 06/12] Implement missing robot pose localization api in AprilTagProcessorImpl --- .../src/main/java/android/opengl/Matrix.java | 853 +++++++++++++++++ .../com/android/internal/util/ArrayUtils.java | 29 + .../ftc/robotcore/external/Const.java | 54 ++ .../ftc/robotcore/external/NonConst.java | 54 ++ .../external/matrices/ColumnMajorMatrixF.java | 56 ++ .../external/matrices/ColumnMatrixF.java | 62 ++ .../external/matrices/DenseMatrixF.java | 75 ++ .../external/matrices/GeneralMatrixF.java | 75 ++ .../robotcore/external/matrices/MatrixF.java | 804 ++++++++++++++++ .../external/matrices/OpenGLMatrix.java | 264 ++++++ .../external/matrices/RowMajorMatrixF.java | 57 ++ .../external/matrices/RowMatrixF.java | 62 ++ .../external/matrices/SliceMatrixF.java | 90 ++ .../robotcore/external/matrices/VectorF.java | 318 +++++++ .../external/navigation/Acceleration.java | 120 +++ .../external/navigation/AngleUnit.java | 239 +++++ .../external/navigation/AxesOrder.java | 103 ++ .../external/navigation/AxesReference.java | 57 ++ .../robotcore/external/navigation/Axis.java | 59 ++ .../external/navigation/DistanceUnit.java | 202 ++++ .../external/navigation/Orientation.java | 887 ++++++++++++++++++ .../robotcore/external/navigation/Pose3D.java | 89 ++ .../external/navigation/Position.java | 101 ++ .../navigation/UnnormalizedAngleUnit.java | 197 ++++ .../external/navigation/Velocity.java | 104 ++ .../navigation/YawPitchRollAngles.java | 144 +++ .../camera/calibration/CameraCalibration.java | 4 +- .../eocvsim/util/ClasspathScan.kt | 1 + .../util/template/DefaultWorkspaceTemplate.kt | 2 +- .../util/template/GradleWorkspaceTemplate.kt | 2 +- .../resources/templates/default_workspace.zip | Bin 24969 -> 29308 bytes .../samples/ConceptAprilTagLocalization.java | 240 +++++ .../main/java/android/graphics/FontCache.java | 10 +- .../android/graphics/TemporaryBuffer.java | 27 + .../apriltag/AprilTagCanvasAnnotator.java | 16 +- .../vision/apriltag/AprilTagDetection.java | 11 +- .../vision/apriltag/AprilTagProcessor.java | 58 +- .../apriltag/AprilTagProcessorImpl.java | 153 ++- .../easyopencv/OpenCvCameraFactory.java | 1 - 39 files changed, 5636 insertions(+), 44 deletions(-) create mode 100644 Common/src/main/java/android/opengl/Matrix.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java create mode 100644 TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java create mode 100644 Vision/src/main/java/android/graphics/TemporaryBuffer.java diff --git a/Common/src/main/java/android/opengl/Matrix.java b/Common/src/main/java/android/opengl/Matrix.java new file mode 100644 index 00000000..709339ef --- /dev/null +++ b/Common/src/main/java/android/opengl/Matrix.java @@ -0,0 +1,853 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.opengl; + +import java.util.Objects; + +/** + * Matrix math utilities. These methods operate on OpenGL ES format + * matrices and vectors stored in float arrays. + *

+ * Matrices are 4 x 4 column-vector matrices stored in column-major + * order: + *

+ *  m[offset +  0] m[offset +  4] m[offset +  8] m[offset + 12]
+ *  m[offset +  1] m[offset +  5] m[offset +  9] m[offset + 13]
+ *  m[offset +  2] m[offset +  6] m[offset + 10] m[offset + 14]
+ *  m[offset +  3] m[offset +  7] m[offset + 11] m[offset + 15]
+ * + * Vectors are 4 x 1 column vectors stored in order: + *
+ * v[offset + 0]
+ * v[offset + 1]
+ * v[offset + 2]
+ * v[offset + 3]
+ */ +public class Matrix { + + /** Temporary memory for operations that need temporary matrix data. */ + private final static float[] sTemp = new float[32]; + + /** + * @deprecated All methods are static, do not instantiate this class. + */ + @Deprecated + public Matrix() {} + + // Helper function to calculate the index in the 1D array representing a 4x4 matrix. + private static int I(int i, int j) { + return j + 4 * i; + } + + /** + * Multiplies two 4x4 matrices together and stores the result in a third 4x4 + * matrix. In matrix notation: result = lhs x rhs. Due to the way + * matrix multiplication works, the result matrix will have the same + * effect as first multiplying by the rhs matrix, then multiplying by + * the lhs matrix. This is the opposite of what you might expect. + *

+ * The same float array may be passed for result, lhs, and/or rhs. However, + * the result element values are undefined if the result elements overlap + * either the lhs or rhs elements. + * + * @param result The float array that holds the result. + * @param resultOffset The offset into the result array where the result is + * stored. + * @param lhs The float array that holds the left-hand-side matrix. + * @param lhsOffset The offset into the lhs array where the lhs is stored + * @param rhs The float array that holds the right-hand-side matrix. + * @param rhsOffset The offset into the rhs array where the rhs is stored. + * + * @throws IllegalArgumentException if result, lhs, or rhs are null, or if + * resultOffset + 16 > result.length or lhsOffset + 16 > lhs.length or + * rhsOffset + 16 > rhs.length. + */ + // Java implementation of multiplyMM with offsets + public static void multiplyMM(float[] result, int resultOffset, + float[] lhs, int lhsOffset, float[] rhs, int rhsOffset) { + if(result == null) { + throw new IllegalArgumentException("result must not be null"); + } + if(lhs == null) { + throw new IllegalArgumentException("lhs must not be null"); + } + if(rhs == null) { + throw new IllegalArgumentException("rhs must not be null"); + } + + if (resultOffset + 16 > result.length) { + throw new IllegalArgumentException("resultOffset + 16 > result.length"); + } + + if (lhsOffset + 16 > lhs.length) { + throw new IllegalArgumentException("lhsOffset + 16 > lhs.length"); + } + + if (rhsOffset + 16 > rhs.length) { + throw new IllegalArgumentException("rhsOffset + 16 > rhs.length"); + } + + // Multiply the two 4x4 matrices with the given offsets. + for (int i = 0; i < 4; i++) { + final float rhs_i0 = rhs[rhsOffset + I(i, 0)]; + float ri0 = lhs[lhsOffset + I(0, 0)] * rhs_i0; + float ri1 = lhs[lhsOffset + I(0, 1)] * rhs_i0; + float ri2 = lhs[lhsOffset + I(0, 2)] * rhs_i0; + float ri3 = lhs[lhsOffset + I(0, 3)] * rhs_i0; + + for (int j = 1; j < 4; j++) { + final float rhs_ij = rhs[rhsOffset + I(i, j)]; + ri0 += lhs[lhsOffset + I(j, 0)] * rhs_ij; + ri1 += lhs[lhsOffset + I(j, 1)] * rhs_ij; + ri2 += lhs[lhsOffset + I(j, 2)] * rhs_ij; + ri3 += lhs[lhsOffset + I(j, 3)] * rhs_ij; + } + + result[resultOffset + I(i, 0)] = ri0; + result[resultOffset + I(i, 1)] = ri1; + result[resultOffset + I(i, 2)] = ri2; + result[resultOffset + I(i, 3)] = ri3; + } + } + + private static void mx4transform(float[] lhsMat, int lhsMatOffset, + float[] rhsVec, int rhsVecOffset, + float[] resultVec, int resultVecOffset) { + resultVec[resultVecOffset + 0] = lhsMat[lhsMatOffset + 0 + 4 * 0] * rhsVec[rhsVecOffset + 0] + + lhsMat[lhsMatOffset + 0 + 4 * 1] * rhsVec[rhsVecOffset + 1] + + lhsMat[lhsMatOffset + 0 + 4 * 2] * rhsVec[rhsVecOffset + 2] + + lhsMat[lhsMatOffset + 0 + 4 * 3] * rhsVec[rhsVecOffset + 3]; + + resultVec[resultVecOffset + 1] = lhsMat[lhsMatOffset + 1 + 4 * 0] * rhsVec[rhsVecOffset + 0] + + lhsMat[lhsMatOffset + 1 + 4 * 1] * rhsVec[rhsVecOffset + 1] + + lhsMat[lhsMatOffset + 1 + 4 * 2] * rhsVec[rhsVecOffset + 2] + + lhsMat[lhsMatOffset + 1 + 4 * 3] * rhsVec[rhsVecOffset + 3]; + + resultVec[resultVecOffset + 2] = lhsMat[lhsMatOffset + 2 + 4 * 0] * rhsVec[rhsVecOffset + 0] + + lhsMat[lhsMatOffset + 2 + 4 * 1] * rhsVec[rhsVecOffset + 1] + + lhsMat[lhsMatOffset + 2 + 4 * 2] * rhsVec[rhsVecOffset + 2] + + lhsMat[lhsMatOffset + 2 + 4 * 3] * rhsVec[rhsVecOffset + 3]; + + resultVec[resultVecOffset + 3] = lhsMat[lhsMatOffset + 3 + 4 * 0] * rhsVec[rhsVecOffset + 0] + + lhsMat[lhsMatOffset + 3 + 4 * 1] * rhsVec[rhsVecOffset + 1] + + lhsMat[lhsMatOffset + 3 + 4 * 2] * rhsVec[rhsVecOffset + 2] + + lhsMat[lhsMatOffset + 3 + 4 * 3] * rhsVec[rhsVecOffset + 3]; + } + + /** + * Multiplies a 4 element vector by a 4x4 matrix and stores the result in a + * 4-element column vector. In matrix notation: result = lhs x rhs + *

+ * The same float array may be passed for resultVec, lhsMat, and/or rhsVec. + * However, the resultVec element values are undefined if the resultVec + * elements overlap either the lhsMat or rhsVec elements. + * + * @param resultVec The float array that holds the result vector. + * @param resultVecOffset The offset into the result array where the result + * vector is stored. + * @param lhsMat The float array that holds the left-hand-side matrix. + * @param lhsMatOffset The offset into the lhs array where the lhs is stored + * @param rhsVec The float array that holds the right-hand-side vector. + * @param rhsVecOffset The offset into the rhs vector where the rhs vector + * is stored. + * + * @throws IllegalArgumentException if resultVec, lhsMat, + * or rhsVec are null, or if resultVecOffset + 4 > resultVec.length + * or lhsMatOffset + 16 > lhsMat.length or + * rhsVecOffset + 4 > rhsVec.length. + */ + public static void multiplyMV(float[] resultVec, + int resultVecOffset, float[] lhsMat, int lhsMatOffset, + float[] rhsVec, int rhsVecOffset) { + if(resultVec == null) { + throw new IllegalArgumentException("resultVec must not be null"); + } + + if(lhsMat == null) { + throw new IllegalArgumentException("lhsMat must not be null"); + } + + if(rhsVec == null) { + throw new IllegalArgumentException("rhsVec must not be null"); + } + + if(resultVecOffset + 4 > resultVec.length) { + throw new IllegalArgumentException("resultVecOffset + 4 > resultVec.length"); + } + if(lhsMatOffset + 16 > lhsMat.length) { + throw new IllegalArgumentException("lhsMatOffset + 16 > lhsMat.length"); + } + if(rhsVecOffset + 4 > rhsVec.length) { + throw new IllegalArgumentException("rhsVecOffset + 4 > rhsVec.length"); + } + + mx4transform(lhsMat, lhsMatOffset, rhsVec, rhsVecOffset, resultVec, resultVecOffset); + } + + /** + * Transposes a 4 x 4 matrix. + *

+ * mTrans and m must not overlap. + * + * @param mTrans the array that holds the output transposed matrix + * @param mTransOffset an offset into mTrans where the transposed matrix is + * stored. + * @param m the input array + * @param mOffset an offset into m where the input matrix is stored. + */ + public static void transposeM(float[] mTrans, int mTransOffset, float[] m, + int mOffset) { + for (int i = 0; i < 4; i++) { + int mBase = i * 4 + mOffset; + mTrans[i + mTransOffset] = m[mBase]; + mTrans[i + 4 + mTransOffset] = m[mBase + 1]; + mTrans[i + 8 + mTransOffset] = m[mBase + 2]; + mTrans[i + 12 + mTransOffset] = m[mBase + 3]; + } + } + + /** + * Inverts a 4 x 4 matrix. + *

+ * mInv and m must not overlap. + * + * @param mInv the array that holds the output inverted matrix + * @param mInvOffset an offset into mInv where the inverted matrix is + * stored. + * @param m the input array + * @param mOffset an offset into m where the input matrix is stored. + * @return true if the matrix could be inverted, false if it could not. + */ + public static boolean invertM(float[] mInv, int mInvOffset, float[] m, + int mOffset) { + // Invert a 4 x 4 matrix using Cramer's Rule + + // transpose matrix + final float src0 = m[mOffset + 0]; + final float src4 = m[mOffset + 1]; + final float src8 = m[mOffset + 2]; + final float src12 = m[mOffset + 3]; + + final float src1 = m[mOffset + 4]; + final float src5 = m[mOffset + 5]; + final float src9 = m[mOffset + 6]; + final float src13 = m[mOffset + 7]; + + final float src2 = m[mOffset + 8]; + final float src6 = m[mOffset + 9]; + final float src10 = m[mOffset + 10]; + final float src14 = m[mOffset + 11]; + + final float src3 = m[mOffset + 12]; + final float src7 = m[mOffset + 13]; + final float src11 = m[mOffset + 14]; + final float src15 = m[mOffset + 15]; + + // calculate pairs for first 8 elements (cofactors) + final float atmp0 = src10 * src15; + final float atmp1 = src11 * src14; + final float atmp2 = src9 * src15; + final float atmp3 = src11 * src13; + final float atmp4 = src9 * src14; + final float atmp5 = src10 * src13; + final float atmp6 = src8 * src15; + final float atmp7 = src11 * src12; + final float atmp8 = src8 * src14; + final float atmp9 = src10 * src12; + final float atmp10 = src8 * src13; + final float atmp11 = src9 * src12; + + // calculate first 8 elements (cofactors) + final float dst0 = (atmp0 * src5 + atmp3 * src6 + atmp4 * src7) + - (atmp1 * src5 + atmp2 * src6 + atmp5 * src7); + final float dst1 = (atmp1 * src4 + atmp6 * src6 + atmp9 * src7) + - (atmp0 * src4 + atmp7 * src6 + atmp8 * src7); + final float dst2 = (atmp2 * src4 + atmp7 * src5 + atmp10 * src7) + - (atmp3 * src4 + atmp6 * src5 + atmp11 * src7); + final float dst3 = (atmp5 * src4 + atmp8 * src5 + atmp11 * src6) + - (atmp4 * src4 + atmp9 * src5 + atmp10 * src6); + final float dst4 = (atmp1 * src1 + atmp2 * src2 + atmp5 * src3) + - (atmp0 * src1 + atmp3 * src2 + atmp4 * src3); + final float dst5 = (atmp0 * src0 + atmp7 * src2 + atmp8 * src3) + - (atmp1 * src0 + atmp6 * src2 + atmp9 * src3); + final float dst6 = (atmp3 * src0 + atmp6 * src1 + atmp11 * src3) + - (atmp2 * src0 + atmp7 * src1 + atmp10 * src3); + final float dst7 = (atmp4 * src0 + atmp9 * src1 + atmp10 * src2) + - (atmp5 * src0 + atmp8 * src1 + atmp11 * src2); + + // calculate pairs for second 8 elements (cofactors) + final float btmp0 = src2 * src7; + final float btmp1 = src3 * src6; + final float btmp2 = src1 * src7; + final float btmp3 = src3 * src5; + final float btmp4 = src1 * src6; + final float btmp5 = src2 * src5; + final float btmp6 = src0 * src7; + final float btmp7 = src3 * src4; + final float btmp8 = src0 * src6; + final float btmp9 = src2 * src4; + final float btmp10 = src0 * src5; + final float btmp11 = src1 * src4; + + // calculate second 8 elements (cofactors) + final float dst8 = (btmp0 * src13 + btmp3 * src14 + btmp4 * src15) + - (btmp1 * src13 + btmp2 * src14 + btmp5 * src15); + final float dst9 = (btmp1 * src12 + btmp6 * src14 + btmp9 * src15) + - (btmp0 * src12 + btmp7 * src14 + btmp8 * src15); + final float dst10 = (btmp2 * src12 + btmp7 * src13 + btmp10 * src15) + - (btmp3 * src12 + btmp6 * src13 + btmp11 * src15); + final float dst11 = (btmp5 * src12 + btmp8 * src13 + btmp11 * src14) + - (btmp4 * src12 + btmp9 * src13 + btmp10 * src14); + final float dst12 = (btmp2 * src10 + btmp5 * src11 + btmp1 * src9 ) + - (btmp4 * src11 + btmp0 * src9 + btmp3 * src10); + final float dst13 = (btmp8 * src11 + btmp0 * src8 + btmp7 * src10) + - (btmp6 * src10 + btmp9 * src11 + btmp1 * src8 ); + final float dst14 = (btmp6 * src9 + btmp11 * src11 + btmp3 * src8 ) + - (btmp10 * src11 + btmp2 * src8 + btmp7 * src9 ); + final float dst15 = (btmp10 * src10 + btmp4 * src8 + btmp9 * src9 ) + - (btmp8 * src9 + btmp11 * src10 + btmp5 * src8 ); + + // calculate determinant + final float det = + src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3; + + if (det == 0.0f) { + return false; + } + + // calculate matrix inverse + final float invdet = 1.0f / det; + mInv[ mInvOffset] = dst0 * invdet; + mInv[ 1 + mInvOffset] = dst1 * invdet; + mInv[ 2 + mInvOffset] = dst2 * invdet; + mInv[ 3 + mInvOffset] = dst3 * invdet; + + mInv[ 4 + mInvOffset] = dst4 * invdet; + mInv[ 5 + mInvOffset] = dst5 * invdet; + mInv[ 6 + mInvOffset] = dst6 * invdet; + mInv[ 7 + mInvOffset] = dst7 * invdet; + + mInv[ 8 + mInvOffset] = dst8 * invdet; + mInv[ 9 + mInvOffset] = dst9 * invdet; + mInv[10 + mInvOffset] = dst10 * invdet; + mInv[11 + mInvOffset] = dst11 * invdet; + + mInv[12 + mInvOffset] = dst12 * invdet; + mInv[13 + mInvOffset] = dst13 * invdet; + mInv[14 + mInvOffset] = dst14 * invdet; + mInv[15 + mInvOffset] = dst15 * invdet; + + return true; + } + + /** + * Computes an orthographic projection matrix. + * + * @param m returns the result + * @param mOffset + * @param left + * @param right + * @param bottom + * @param top + * @param near + * @param far + */ + public static void orthoM(float[] m, int mOffset, + float left, float right, float bottom, float top, + float near, float far) { + if (left == right) { + throw new IllegalArgumentException("left == right"); + } + if (bottom == top) { + throw new IllegalArgumentException("bottom == top"); + } + if (near == far) { + throw new IllegalArgumentException("near == far"); + } + + final float r_width = 1.0f / (right - left); + final float r_height = 1.0f / (top - bottom); + final float r_depth = 1.0f / (far - near); + final float x = 2.0f * (r_width); + final float y = 2.0f * (r_height); + final float z = -2.0f * (r_depth); + final float tx = -(right + left) * r_width; + final float ty = -(top + bottom) * r_height; + final float tz = -(far + near) * r_depth; + m[mOffset + 0] = x; + m[mOffset + 5] = y; + m[mOffset +10] = z; + m[mOffset +12] = tx; + m[mOffset +13] = ty; + m[mOffset +14] = tz; + m[mOffset +15] = 1.0f; + m[mOffset + 1] = 0.0f; + m[mOffset + 2] = 0.0f; + m[mOffset + 3] = 0.0f; + m[mOffset + 4] = 0.0f; + m[mOffset + 6] = 0.0f; + m[mOffset + 7] = 0.0f; + m[mOffset + 8] = 0.0f; + m[mOffset + 9] = 0.0f; + m[mOffset + 11] = 0.0f; + } + + + /** + * Defines a projection matrix in terms of six clip planes. + * + * @param m the float array that holds the output perspective matrix + * @param offset the offset into float array m where the perspective + * matrix data is written + * @param left + * @param right + * @param bottom + * @param top + * @param near + * @param far + */ + public static void frustumM(float[] m, int offset, + float left, float right, float bottom, float top, + float near, float far) { + if (left == right) { + throw new IllegalArgumentException("left == right"); + } + if (top == bottom) { + throw new IllegalArgumentException("top == bottom"); + } + if (near == far) { + throw new IllegalArgumentException("near == far"); + } + if (near <= 0.0f) { + throw new IllegalArgumentException("near <= 0.0f"); + } + if (far <= 0.0f) { + throw new IllegalArgumentException("far <= 0.0f"); + } + final float r_width = 1.0f / (right - left); + final float r_height = 1.0f / (top - bottom); + final float r_depth = 1.0f / (near - far); + final float x = 2.0f * (near * r_width); + final float y = 2.0f * (near * r_height); + final float A = (right + left) * r_width; + final float B = (top + bottom) * r_height; + final float C = (far + near) * r_depth; + final float D = 2.0f * (far * near * r_depth); + m[offset + 0] = x; + m[offset + 5] = y; + m[offset + 8] = A; + m[offset + 9] = B; + m[offset + 10] = C; + m[offset + 14] = D; + m[offset + 11] = -1.0f; + m[offset + 1] = 0.0f; + m[offset + 2] = 0.0f; + m[offset + 3] = 0.0f; + m[offset + 4] = 0.0f; + m[offset + 6] = 0.0f; + m[offset + 7] = 0.0f; + m[offset + 12] = 0.0f; + m[offset + 13] = 0.0f; + m[offset + 15] = 0.0f; + } + + /** + * Defines a projection matrix in terms of a field of view angle, an + * aspect ratio, and z clip planes. + * + * @param m the float array that holds the perspective matrix + * @param offset the offset into float array m where the perspective + * matrix data is written + * @param fovy field of view in y direction, in degrees + * @param aspect width to height aspect ratio of the viewport + * @param zNear + * @param zFar + */ + public static void perspectiveM(float[] m, int offset, + float fovy, float aspect, float zNear, float zFar) { + float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0)); + float rangeReciprocal = 1.0f / (zNear - zFar); + + m[offset + 0] = f / aspect; + m[offset + 1] = 0.0f; + m[offset + 2] = 0.0f; + m[offset + 3] = 0.0f; + + m[offset + 4] = 0.0f; + m[offset + 5] = f; + m[offset + 6] = 0.0f; + m[offset + 7] = 0.0f; + + m[offset + 8] = 0.0f; + m[offset + 9] = 0.0f; + m[offset + 10] = (zFar + zNear) * rangeReciprocal; + m[offset + 11] = -1.0f; + + m[offset + 12] = 0.0f; + m[offset + 13] = 0.0f; + m[offset + 14] = 2.0f * zFar * zNear * rangeReciprocal; + m[offset + 15] = 0.0f; + } + + /** + * Computes the length of a vector. + * + * @param x x coordinate of a vector + * @param y y coordinate of a vector + * @param z z coordinate of a vector + * @return the length of a vector + */ + public static float length(float x, float y, float z) { + return (float) Math.sqrt(x * x + y * y + z * z); + } + + /** + * Sets matrix m to the identity matrix. + * + * @param sm returns the result + * @param smOffset index into sm where the result matrix starts + */ + public static void setIdentityM(float[] sm, int smOffset) { + for (int i=0 ; i<16 ; i++) { + sm[smOffset + i] = 0; + } + for(int i = 0; i < 16; i += 5) { + sm[smOffset + i] = 1.0f; + } + } + + /** + * Scales matrix m by x, y, and z, putting the result in sm. + *

+ * m and sm must not overlap. + * + * @param sm returns the result + * @param smOffset index into sm where the result matrix starts + * @param m source matrix + * @param mOffset index into m where the source matrix starts + * @param x scale factor x + * @param y scale factor y + * @param z scale factor z + */ + public static void scaleM(float[] sm, int smOffset, + float[] m, int mOffset, + float x, float y, float z) { + for (int i=0 ; i<4 ; i++) { + int smi = smOffset + i; + int mi = mOffset + i; + sm[ smi] = m[ mi] * x; + sm[ 4 + smi] = m[ 4 + mi] * y; + sm[ 8 + smi] = m[ 8 + mi] * z; + sm[12 + smi] = m[12 + mi]; + } + } + + /** + * Scales matrix m in place by sx, sy, and sz. + * + * @param m matrix to scale + * @param mOffset index into m where the matrix starts + * @param x scale factor x + * @param y scale factor y + * @param z scale factor z + */ + public static void scaleM(float[] m, int mOffset, + float x, float y, float z) { + for (int i=0 ; i<4 ; i++) { + int mi = mOffset + i; + m[ mi] *= x; + m[ 4 + mi] *= y; + m[ 8 + mi] *= z; + } + } + + /** + * Translates matrix m by x, y, and z, putting the result in tm. + *

+ * m and tm must not overlap. + * + * @param tm returns the result + * @param tmOffset index into sm where the result matrix starts + * @param m source matrix + * @param mOffset index into m where the source matrix starts + * @param x translation factor x + * @param y translation factor y + * @param z translation factor z + */ + public static void translateM(float[] tm, int tmOffset, + float[] m, int mOffset, + float x, float y, float z) { + for (int i=0 ; i<12 ; i++) { + tm[tmOffset + i] = m[mOffset + i]; + } + for (int i=0 ; i<4 ; i++) { + int tmi = tmOffset + i; + int mi = mOffset + i; + tm[12 + tmi] = m[mi] * x + m[4 + mi] * y + m[8 + mi] * z + + m[12 + mi]; + } + } + + /** + * Translates matrix m by x, y, and z in place. + * + * @param m matrix + * @param mOffset index into m where the matrix starts + * @param x translation factor x + * @param y translation factor y + * @param z translation factor z + */ + public static void translateM( + float[] m, int mOffset, + float x, float y, float z) { + for (int i=0 ; i<4 ; i++) { + int mi = mOffset + i; + m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z; + } + } + + /** + * Rotates matrix m by angle a (in degrees) around the axis (x, y, z). + *

+ * m and rm must not overlap. + * + * @param rm returns the result + * @param rmOffset index into rm where the result matrix starts + * @param m source matrix + * @param mOffset index into m where the source matrix starts + * @param a angle to rotate in degrees + * @param x X axis component + * @param y Y axis component + * @param z Z axis component + */ + public static void rotateM(float[] rm, int rmOffset, + float[] m, int mOffset, + float a, float x, float y, float z) { + synchronized(sTemp) { + setRotateM(sTemp, 0, a, x, y, z); + multiplyMM(rm, rmOffset, m, mOffset, sTemp, 0); + } + } + + /** + * Rotates matrix m in place by angle a (in degrees) + * around the axis (x, y, z). + * + * @param m source matrix + * @param mOffset index into m where the matrix starts + * @param a angle to rotate in degrees + * @param x X axis component + * @param y Y axis component + * @param z Z axis component + */ + public static void rotateM(float[] m, int mOffset, + float a, float x, float y, float z) { + synchronized(sTemp) { + setRotateM(sTemp, 0, a, x, y, z); + multiplyMM(sTemp, 16, m, mOffset, sTemp, 0); + System.arraycopy(sTemp, 16, m, mOffset, 16); + } + } + + /** + * Creates a matrix for rotation by angle a (in degrees) + * around the axis (x, y, z). + *

+ * An optimized path will be used for rotation about a major axis + * (e.g. x=1.0f y=0.0f z=0.0f). + * + * @param rm returns the result + * @param rmOffset index into rm where the result matrix starts + * @param a angle to rotate in degrees + * @param x X axis component + * @param y Y axis component + * @param z Z axis component + */ + public static void setRotateM(float[] rm, int rmOffset, + float a, float x, float y, float z) { + rm[rmOffset + 3] = 0; + rm[rmOffset + 7] = 0; + rm[rmOffset + 11]= 0; + rm[rmOffset + 12]= 0; + rm[rmOffset + 13]= 0; + rm[rmOffset + 14]= 0; + rm[rmOffset + 15]= 1; + a *= (float) (Math.PI / 180.0f); + float s = (float) Math.sin(a); + float c = (float) Math.cos(a); + if (1.0f == x && 0.0f == y && 0.0f == z) { + rm[rmOffset + 5] = c; rm[rmOffset + 10]= c; + rm[rmOffset + 6] = s; rm[rmOffset + 9] = -s; + rm[rmOffset + 1] = 0; rm[rmOffset + 2] = 0; + rm[rmOffset + 4] = 0; rm[rmOffset + 8] = 0; + rm[rmOffset + 0] = 1; + } else if (0.0f == x && 1.0f == y && 0.0f == z) { + rm[rmOffset + 0] = c; rm[rmOffset + 10]= c; + rm[rmOffset + 8] = s; rm[rmOffset + 2] = -s; + rm[rmOffset + 1] = 0; rm[rmOffset + 4] = 0; + rm[rmOffset + 6] = 0; rm[rmOffset + 9] = 0; + rm[rmOffset + 5] = 1; + } else if (0.0f == x && 0.0f == y && 1.0f == z) { + rm[rmOffset + 0] = c; rm[rmOffset + 5] = c; + rm[rmOffset + 1] = s; rm[rmOffset + 4] = -s; + rm[rmOffset + 2] = 0; rm[rmOffset + 6] = 0; + rm[rmOffset + 8] = 0; rm[rmOffset + 9] = 0; + rm[rmOffset + 10]= 1; + } else { + float len = length(x, y, z); + if (1.0f != len) { + float recipLen = 1.0f / len; + x *= recipLen; + y *= recipLen; + z *= recipLen; + } + float nc = 1.0f - c; + float xy = x * y; + float yz = y * z; + float zx = z * x; + float xs = x * s; + float ys = y * s; + float zs = z * s; + rm[rmOffset + 0] = x*x*nc + c; + rm[rmOffset + 4] = xy*nc - zs; + rm[rmOffset + 8] = zx*nc + ys; + rm[rmOffset + 1] = xy*nc + zs; + rm[rmOffset + 5] = y*y*nc + c; + rm[rmOffset + 9] = yz*nc - xs; + rm[rmOffset + 2] = zx*nc - ys; + rm[rmOffset + 6] = yz*nc + xs; + rm[rmOffset + 10] = z*z*nc + c; + } + } + + /** + * Converts Euler angles to a rotation matrix. + * + * @param rm returns the result + * @param rmOffset index into rm where the result matrix starts + * @param x angle of rotation, in degrees + * @param y angle of rotation, in degrees + * @param z angle of rotation, in degrees + */ + public static void setRotateEulerM(float[] rm, int rmOffset, + float x, float y, float z) { + x *= (float) (Math.PI / 180.0f); + y *= (float) (Math.PI / 180.0f); + z *= (float) (Math.PI / 180.0f); + float cx = (float) Math.cos(x); + float sx = (float) Math.sin(x); + float cy = (float) Math.cos(y); + float sy = (float) Math.sin(y); + float cz = (float) Math.cos(z); + float sz = (float) Math.sin(z); + float cxsy = cx * sy; + float sxsy = sx * sy; + + rm[rmOffset + 0] = cy * cz; + rm[rmOffset + 1] = -cy * sz; + rm[rmOffset + 2] = sy; + rm[rmOffset + 3] = 0.0f; + + rm[rmOffset + 4] = cxsy * cz + cx * sz; + rm[rmOffset + 5] = -cxsy * sz + cx * cz; + rm[rmOffset + 6] = -sx * cy; + rm[rmOffset + 7] = 0.0f; + + rm[rmOffset + 8] = -sxsy * cz + sx * sz; + rm[rmOffset + 9] = sxsy * sz + sx * cz; + rm[rmOffset + 10] = cx * cy; + rm[rmOffset + 11] = 0.0f; + + rm[rmOffset + 12] = 0.0f; + rm[rmOffset + 13] = 0.0f; + rm[rmOffset + 14] = 0.0f; + rm[rmOffset + 15] = 1.0f; + } + + /** + * Defines a viewing transformation in terms of an eye point, a center of + * view, and an up vector. + * + * @param rm returns the result + * @param rmOffset index into rm where the result matrix starts + * @param eyeX eye point X + * @param eyeY eye point Y + * @param eyeZ eye point Z + * @param centerX center of view X + * @param centerY center of view Y + * @param centerZ center of view Z + * @param upX up vector X + * @param upY up vector Y + * @param upZ up vector Z + */ + public static void setLookAtM(float[] rm, int rmOffset, + float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, float upX, float upY, + float upZ) { + + // See the OpenGL GLUT documentation for gluLookAt for a description + // of the algorithm. We implement it in a straightforward way: + + float fx = centerX - eyeX; + float fy = centerY - eyeY; + float fz = centerZ - eyeZ; + + // Normalize f + float rlf = 1.0f / Matrix.length(fx, fy, fz); + fx *= rlf; + fy *= rlf; + fz *= rlf; + + // compute s = f x up (x means "cross product") + float sx = fy * upZ - fz * upY; + float sy = fz * upX - fx * upZ; + float sz = fx * upY - fy * upX; + + // and normalize s + float rls = 1.0f / Matrix.length(sx, sy, sz); + sx *= rls; + sy *= rls; + sz *= rls; + + // compute u = s x f + float ux = sy * fz - sz * fy; + float uy = sz * fx - sx * fz; + float uz = sx * fy - sy * fx; + + rm[rmOffset + 0] = sx; + rm[rmOffset + 1] = ux; + rm[rmOffset + 2] = -fx; + rm[rmOffset + 3] = 0.0f; + + rm[rmOffset + 4] = sy; + rm[rmOffset + 5] = uy; + rm[rmOffset + 6] = -fy; + rm[rmOffset + 7] = 0.0f; + + rm[rmOffset + 8] = sz; + rm[rmOffset + 9] = uz; + rm[rmOffset + 10] = -fz; + rm[rmOffset + 11] = 0.0f; + + rm[rmOffset + 12] = 0.0f; + rm[rmOffset + 13] = 0.0f; + rm[rmOffset + 14] = 0.0f; + rm[rmOffset + 15] = 1.0f; + + translateM(rm, rmOffset, -eyeX, -eyeY, -eyeZ); + } +} \ No newline at end of file diff --git a/Common/src/main/java/com/android/internal/util/ArrayUtils.java b/Common/src/main/java/com/android/internal/util/ArrayUtils.java index 2e30c597..4af45f05 100644 --- a/Common/src/main/java/com/android/internal/util/ArrayUtils.java +++ b/Common/src/main/java/com/android/internal/util/ArrayUtils.java @@ -41,6 +41,35 @@ public class ArrayUtils { public static final File[] EMPTY_FILE = new File[0]; private ArrayUtils() { /* cannot be instantiated */ } + + public static int idealByteArraySize(int need) { + for (int i = 4; i < 32; i++) + if (need <= (1 << i) - 12) + return (1 << i) - 12; + return need; + } + public static int idealBooleanArraySize(int need) { + return idealByteArraySize(need); + } + public static int idealShortArraySize(int need) { + return idealByteArraySize(need * 2) / 2; + } + public static int idealCharArraySize(int need) { + return idealByteArraySize(need * 2) / 2; + } + public static int idealIntArraySize(int need) { + return idealByteArraySize(need * 4) / 4; + } + public static int idealFloatArraySize(int need) { + return idealByteArraySize(need * 4) / 4; + } + public static int idealObjectArraySize(int need) { + return idealByteArraySize(need * 4) / 4; + } + public static int idealLongArraySize(int need) { + return idealByteArraySize(need * 8) / 8; + } + public static byte[] newUnpaddedByteArray(int minLen) { return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen); } diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java new file mode 100644 index 00000000..97652a60 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Const.java @@ -0,0 +1,54 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@link Const} documents a method that promises not to change the internal state + * of the method receiver. Documenting methods in this way helps programmers understand + * which methods examine the object and return results based on that examination but don't + * change the internal object state and which methods, by contrast, perform their function + * but updating or changing internal object state. + * @see NonConst + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface Const +{ +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java new file mode 100644 index 00000000..36f8dffb --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/NonConst.java @@ -0,0 +1,54 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@link NonConst} documents a method that performs its function by updating internal + * state of the method receiver. Documenting methods in this way helps programmers understand + * which methods examine the object and return results based on that examination but don't + * change the internal object state and which methods, by contrast, perform their function + * but updating or changing internal object state. + * @see Const + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface NonConst +{ +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java new file mode 100644 index 00000000..1d6f5f22 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMajorMatrixF.java @@ -0,0 +1,56 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +/** + * A {@link ColumnMajorMatrixF} is a dense matrix whose entries are arranged in + * column-major order. + * @see Row Major Order + */ +public abstract class ColumnMajorMatrixF extends DenseMatrixF +{ + public ColumnMajorMatrixF(int nRows, int nCols) + { + super(nRows, nCols); + } + + @Override protected int indexFromRowCol(int row, int col) + { + return col * numRows + row; + } + + @Override public VectorF toVector() + { + return new VectorF(this.getData()); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java new file mode 100644 index 00000000..79e845f8 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/ColumnMatrixF.java @@ -0,0 +1,62 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +/** + * A {@link ColumnMatrixF} is a matrix that converts a VectorF into a 1xn matrix + */ +public class ColumnMatrixF extends MatrixF +{ + VectorF vector; + + public ColumnMatrixF(VectorF vector) + { + super(vector.length(), 1); + this.vector = vector; + } + + @Override public float get(int row, int col) + { + return this.vector.get(row); + } + + @Override public void put(int row, int col, float value) + { + this.vector.put(row, value); + } + + @Override public MatrixF emptyMatrix(int numRows, int numCols) + { + return new GeneralMatrixF(numRows, numCols); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java new file mode 100644 index 00000000..396bd97a --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/DenseMatrixF.java @@ -0,0 +1,75 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +/** + * A {@link DenseMatrixF} is a matrix of floats whose storage is a contiguous float[] array. It may + * logically be ranged arranged either in row or column major order. + * + * @see MatrixF + * @see RowMajorMatrixF + * @see ColumnMajorMatrixF + * @see SliceMatrixF + */ +public abstract class DenseMatrixF extends MatrixF +{ + protected DenseMatrixF(int nRows, int nCols) + { + super(nRows, nCols); + } + + @Override public float get(int row, int col) + { + return getData()[indexFromRowCol(row, col)]; + } + + @Override public void put(int row, int col, float value) + { + getData()[indexFromRowCol(row, col)] = value; + } + + /** + * Returns the contiguous array of floats which is the storage for this matrix + * @return the contiguous array of floats which is the storage for this matrix + */ + public abstract float[] getData(); + + /** + * Given a row and column index into the matrix, returns the corresponding index + * into the underlying float[] array. + * @param row the row whose index is desired + * @param col the column whose index is desired + * @return the index of (row,col) in the data returned by {@link #getData()} + */ + protected abstract int indexFromRowCol(int row, int col); +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java new file mode 100644 index 00000000..3e86df07 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/GeneralMatrixF.java @@ -0,0 +1,75 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +/** + * A {@link GeneralMatrixF} is a concrete matrix implementation that is supported by + * a backing store consisting of an array of floats. The matrix is stored in row-major order. + */ +public class GeneralMatrixF extends RowMajorMatrixF +{ + float[] data; + + public GeneralMatrixF(int numRows, int numCols) + { + super(numRows, numCols); + this.data = new float[numRows * numCols]; + } + + private GeneralMatrixF(int numRows, int numCols, int flag) + { + super(numRows, numCols); + } + + public GeneralMatrixF(int numRows, int numCols, float[] data) + { + super(numRows, numCols); + if (data.length != numRows * numCols) throw dimensionsError(numRows, numCols); + this.data = data; + } + + @Override public float[] getData() + { + return this.data; + } + + @Override public MatrixF emptyMatrix(int numRows, int numCols) + { + return new GeneralMatrixF(numRows, numCols); + } + + public GeneralMatrixF transposed() + { + return (GeneralMatrixF)super.transposed(); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java new file mode 100644 index 00000000..bfbd11f3 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/MatrixF.java @@ -0,0 +1,804 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +import android.annotation.SuppressLint; + +import org.firstinspires.ftc.robotcore.external.Const; +import org.firstinspires.ftc.robotcore.external.NonConst; +import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; +import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; +import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; +import org.firstinspires.ftc.robotcore.external.navigation.Orientation; + +import java.util.Arrays; + +/** + * {@link MatrixF} represents a matrix of floats of a defined dimensionality but abstracts + * the means by which a particular element of the matrix is retrieved or updated. {@link MatrixF} + * is an abstract class: it is never instantiated; rather, only instances of its subclasses are + * made. + * + * @see Matrix (mathematics) + * @see Matrix multiplication + * @see OpenGLMatrix + * @see GeneralMatrixF + * @see SliceMatrixF + */ +public abstract class MatrixF +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + protected int numRows; + protected int numCols; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + /** + * Creates a matrix containing the indicated number of rows and columns. + */ + public MatrixF(int numRows, int numCols) + { + this.numRows = numRows; + this.numCols = numCols; + if (numRows <= 0 || numCols <= 0) throw dimensionsError(); + } + + /** + * Returns a matrix which a submatrix of the receiver. + * @param row the row in the receiver at which the submatrix is to start + * @param col the column in the receiver at which the submatrix is to start + * @param numRows the number of rows in the submatrix + * @param numCols the number of columns in the submatrix + * @return the newly created submatrix + * @see #slice(int, int) + */ + @Const public SliceMatrixF slice(int row, int col, int numRows, int numCols) + { + return new SliceMatrixF(this, row, col, numRows, numCols); + } + + /** + * Returns a matrix which is a submatrix of the receiver starting at (0,0) + * @param numRows the number of rows in the submatrix + * @param numCols the number of columns in the submatrix + * @return the newly created submatrix + * @see #slice(int, int, int, int) + */ + @Const public SliceMatrixF slice(int numRows, int numCols) + { + return slice(0,0, numRows, numCols); + } + + /** + * Returns an identity matrix of the indicated dimension. An identity matrix is zero + * everywhere except on the diagonal, where it is one. + * @param dim the size of the indentity matrix to return + * @return the new identity matrix + */ + public static MatrixF identityMatrix(int dim) + { + return diagonalMatrix(dim, 1f); + } + + /** + * Returns a new matrix which is zero everywhere except on the diagonal, where it has + * an indicated value. + * @param dim the size of the matrix to return + * @param scale the value to place on its diagonal + * @return the new matrix + */ + public static MatrixF diagonalMatrix(int dim, float scale) + { + GeneralMatrixF result = new GeneralMatrixF(dim, dim); + for (int i = 0; i < dim; i++) + { + result.put(i,i, scale); + } + return result; + } + + /** + * Returns a new matrix which is zero everywhere, except on the diagonal, where its + * values are taken from an indicated vector + * @param vector the values to place on the diagonal + * @return the new matrix + */ + public static MatrixF diagonalMatrix(VectorF vector) + { + int dim = vector.length(); + GeneralMatrixF result = new GeneralMatrixF(dim, dim); + for (int i = 0; i < dim; i++) + { + result.put(i,i, vector.get(i)); + } + return result; + } + + /** + * Returns a new empty matrix of the indicated dimensions. If a specific implementation + * associated with the receiver can be used with these dimensions, then such is used; otherwise + * a general matrix implementation will be used. + * @return a new empty matrix of the indicated dimensions + * @see OpenGLMatrix#emptyMatrix(int, int) + */ + @Const public abstract MatrixF emptyMatrix(int numRows, int numCols); + + //---------------------------------------------------------------------------------------------- + // Accessing + //---------------------------------------------------------------------------------------------- + + /** + * Returns the number of rows in this matrix + * @return the number of rows in this matrix + */ + @Const public int numRows() { return this.numRows; } + + /** + * Returns the number of columns in this matrix + * @return the number of columns in this matrix + */ + @Const public int numCols() { return this.numCols; } + + /** + * Returns a particular element of this matrix + * @param row the index of the row of the element to return + * @param col the index of the column of the element to return + * @return the element at the indicated row and column + * @see #put(int, int, float) + */ + @Const public abstract float get(int row, int col); + + /** + * Updates a particular element of this matrix + * @param row the index of the row of the element to update + * @param col the index of the column of the element to update + * @param value the new value for the indicated element + */ + @NonConst public abstract void put(int row, int col, float value); + + /** + * Returns a vector containing data of a particular row of the receiver. + * @param row the row to extract + * @return a vector containing the data of the indicated row + */ + @Const public VectorF getRow(int row) + { + VectorF result = VectorF.length(this.numCols); + for (int j = 0; j < numCols; j++) + { + result.put(j, this.get(row, j)); + } + return result; + } + + /** + * Returns a vector containing data of a particular column of the receiver. + * @param col the column to extract + * @return a vector containing data of the indicated column + */ + @Const public VectorF getColumn(int col) + { + VectorF result = VectorF.length(this.numRows); + for (int i = 0; i < numRows; i++) + { + result.put(i, this.get(i, col)); + } + return result; + } + + @Const @Override public String toString() + { + StringBuilder result = new StringBuilder(); + result.append("{"); + for (int i = 0; i < this.numRows; i++) + { + if (i > 0) result.append(","); + result.append("{"); + for (int j = 0; j < this.numCols; j++) + { + if (j > 0) result.append(","); + result.append(String.format("%.3f", this.get(i,j))); + } + result.append("}"); + } + result.append("}"); + return result.toString(); + } + + //---------------------------------------------------------------------------------------------- + // Transformation matrix operations + //---------------------------------------------------------------------------------------------- + + /** + * Transforms the vector according to this matrix interpreted as a transformation matrix. + * Conversion to homogeneous + * coordinates is automatically provided. + * @param him the 3D coordinate or 3D homogeneous coordinate that is to be transformed + * @return the normalized homogeneous coordinate resulting from the transformation. + * + * @see Homogeneous coordinates + * @see Transformation Matrix + * @see VectorF#normalized3D() + */ + @Const public VectorF transform(VectorF him) + { + him = adaptHomogeneous(him); + return this.multiplied(him).normalized3D(); + } + + /** + * Automatically adapts vectors to and from homogeneous coordinates according to the + * size of the receiver matrix. + * @see #transform(VectorF) + * @see Homogeneous coordinates + */ + @Const protected VectorF adaptHomogeneous(VectorF him) + { + if (this.numCols == 4) + { + if (him.length() == 3) + { + float[] newData = Arrays.copyOf(him.getData(), 4); + newData[3] = 1f; + return new VectorF(newData); + } + } + else if (this.numCols == 3) + { + if (him.length() == 4) + { + return new VectorF(Arrays.copyOf(him.normalized3D().getData(),3)); + } + } + return him; + } + + /** + * A simple utility that extracts positioning information from a transformation matrix + * and formats it in a form palatable to a human being. This should only be invoked on + * a matrix which is a transformation matrix. + * + * We report here using an extrinsic angle reference, meaning that all three angles are + * rotations in the (fixed) field coordinate system, as this is perhaps easiest to + * conceptually understand. And we use an angle order of XYZ, which results in the Z + * angle, being applied last (after X and Y rotations) and so representing the robot's + * heading on the field, which is often what is of most interest in robot navigation. + * + * @return a description of the angles represented by this transformation matrix. + * @see #formatAsTransform(AxesReference, AxesOrder, AngleUnit) + * @see Transformation Matrix + */ + public String formatAsTransform() + { + return formatAsTransform(AxesReference.EXTRINSIC, AxesOrder.XYZ, AngleUnit.DEGREES); + } + + /** + * A simple utility that extracts positioning information from a transformation matrix + * and formats it in a form palatable to a human being. This should only be invoked on + * a matrix which is a transformation matrix. + * + * @param axesReference the reference frame of the angles to use in reporting the transformation + * @param axesOrder the order of the angles to use in reporting the transformation + * @param unit the angular unit to use in reporting the transformation + * @return a description of the angles represented by this transformation matrix. + * @see #formatAsTransform() + * @see Transformation Matrix + */ + public String formatAsTransform(AxesReference axesReference, AxesOrder axesOrder, AngleUnit unit) + { + /** + * An easy way to understand what a transform does is to look at the location + * to which it transforms the origin of the coordinate system. Calling getTranslation() + * carries out an equivalent computation as it extracts the translational aspect. + */ + VectorF translation = this.getTranslation(); + + /** + * Figure out in which direction we'd be looking after the transformation. Note that + * the decomposition of a transformation into orientation angles can be subtle. See + * {@link Orientation} for a full discussion. + */ + Orientation orientation = Orientation.getOrientation(this, axesReference, axesOrder, unit); + + return String.format("%s %s", orientation.toString(), translation.toString()); + } + + //---------------------------------------------------------------------------------------------- + // Matrix operations + //---------------------------------------------------------------------------------------------- + + /** + * Returns a matrix which is the transposition of the receiver matrix. + * @return a matrix which is the transposition of the receiver matrix. + */ + @Const public MatrixF transposed() + { + MatrixF result = this.emptyMatrix(this.numCols, this.numRows); + for (int i = 0; i < result.numRows; i++) + { + for (int j = 0; j < result.numCols; j++) + { + result.put(i,j, this.get(j,i)); + } + } + return result; + } + + /** + * Updates the receiver to be the product of itself and another matrix. + * @param him the matrix with which the receiver is to be multiplied. + */ + @NonConst public void multiply(MatrixF him) + { + /** + * If we multiply C = A x B, the dimensions work out as C(i x k) = A(i x j) B(j x k). + * If A and C are the same matrix, we have j==k; that is, B must be square. + */ + if (this.numCols == him.numRows) + { + if (him.numRows == him.numCols) + { + MatrixF temp = this.multiplied(him); + + // Copy the matrix back + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numCols; j++) + { + this.put(i,j, temp.get(i,j)); + } + } + } + else + throw dimensionsError(); + } + else + throw dimensionsError(); + } + + /** + * Returns a matrix which is the multiplication of the recevier with another matrix. + * @param him the matrix with which the receiver is to be multiplied. + * @return a matrix which is the product of the two matrices + */ + @Const public MatrixF multiplied(MatrixF him) + { + if (this.numCols == him.numRows) + { + MatrixF result = this.emptyMatrix(this.numRows, him.numCols); + for (int i = 0; i < result.numRows; i++) + { + for (int j = 0; j < result.numCols; j++) + { + float sum = 0f; + for (int k = 0; k < this.numCols; k++) + { + sum += this.get(i, k) * him.get(k, j); + } + result.put(i,j,sum); + } + } + return result; + } + else + throw dimensionsError(); + } + + /** + * Returns a new matrix in which all the entries of the receiver have been scaled + * by an indicated value. + * @param scale the factor with which to scale each entry of the receiver + * @return the new, scaled matrix + */ + @Const public MatrixF multiplied(float scale) + { + MatrixF result = this.emptyMatrix(this.numCols, this.numRows); + for (int i = 0; i < result.numRows; i++) + { + for (int j = 0; j < result.numCols; j++) + { + result.put(i,j, this.get(i,j) * scale); + } + } + return result; + } + + @NonConst public void multiply(float scale) + { + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numCols; j++) + { + this.put(i,j, this.get(i,j) * scale); + } + } + } + + /** + * Multiplies the receiver by the indicated vector, considered as a column matrix. + * @param him the vector with which the receiver is to be multiplied + * @return a matrix which is the product of the receiver and the vector + */ + @Const public VectorF multiplied(VectorF him) + { + return this.multiplied(new ColumnMatrixF(him)).toVector(); + } + + @NonConst public void multiply(VectorF him) + { + VectorF result = this.multiplied(new ColumnMatrixF(him)).toVector(); + for (int i = 0; i < result.length(); i++) + { + this.put(i,0, result.get(i)); + } + } + + /** + * Multiplies the receiver by the indicated vector, considered as a column matrix. + * @param him the vector with which the receiver is to be multiplied + * @return a matrix which is the product of the receiver and the vector + */ + @Const public VectorF multiplied(float[] him) + { + return this.multiplied(new VectorF(him)); + } + + @NonConst public void multiply(float[] him) + { + VectorF result = this.multiplied(new VectorF(him)); + for (int i = 0; i < result.length(); i++) + { + this.put(i,0, result.get(i)); + } + } + + /** + * If the receiver is one-dimensional in one of its dimensions, returns a vector + * containing the data of the receiver; otherwise, an exception is thrown. + * @return a vector containing the data of the receiver + */ + @Const public VectorF toVector() + { + if (this.numCols == 1) + { + VectorF result = VectorF.length(this.numRows); + for (int i = 0; i < this.numRows; i++) + { + result.put(i, this.get(i,0)); + } + return result; + } + else if (this.numRows == 1) + { + VectorF result = VectorF.length(this.numCols); + for (int j = 0; j < this.numCols; j++) + { + result.put(j, this.get(0,j)); + } + return result; + } + else + throw dimensionsError(); + } + + /** + * Returns a new matrix whose elements are the sum of the corresponding elements of + * the receiver and the addend + * @param addend the matrix which is to be added to the receiver + * @return the new matrix + */ + @Const public MatrixF added(MatrixF addend) + { + if (this.numRows==addend.numRows && this.numCols==addend.numCols) + { + MatrixF result = this.emptyMatrix(this.numRows, this.numCols); + for (int i = 0; i < result.numRows; i++) + { + for (int j = 0; j < result.numCols; j++) + { + result.put(i,j, this.get(i,j) + addend.get(i,j)); + } + } + return result; + } + else + throw dimensionsError(); + } + + /** + * Adds a matrix, in place, to the receiver + * @param addend the matrix which is to be added to the receiver + */ + @NonConst public void add(MatrixF addend) + { + if (this.numRows==addend.numRows && this.numCols==addend.numCols) + { + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numCols; j++) + { + this.put(i,j, this.get(i,j) + addend.get(i,j)); + } + } + } + else + throw dimensionsError(); + } + + /** + * Returns a new matrix whose elements are the difference of the corresponding elements of + * the receiver and the subtrahend + * @param subtrahend the matrix which is to be subtracted from the receiver + * @return the new matrix + */ + @Const public MatrixF subtracted(MatrixF subtrahend) + { + if (this.numRows==subtrahend.numRows && this.numCols==subtrahend.numCols) + { + MatrixF result = this.emptyMatrix(this.numRows, this.numCols); + for (int i = 0; i < result.numRows; i++) + { + for (int j = 0; j < result.numCols; j++) + { + result.put(i,j, this.get(i,j) - subtrahend.get(i,j)); + } + } + return result; + } + else + throw dimensionsError(); + } + + /** + * Subtracts a matrix, in place, from the receiver. + * @param subtrahend the matrix which is to be subtracted from the receiver + */ + @NonConst public void subtract(MatrixF subtrahend) + { + if (this.numRows==subtrahend.numRows && this.numCols==subtrahend.numCols) + { + for (int i = 0; i < this.numRows; i++) + { + for (int j = 0; j < this.numCols; j++) + { + this.put(i,j, this.get(i,j) - subtrahend.get(i,j)); + } + } + } + else + throw dimensionsError(); + } + + /** @see #added(MatrixF) */ + @Const public MatrixF added(VectorF him) + { + return this.added(new ColumnMatrixF(him)); + } + /** @see #added(VectorF) */ + @Const public MatrixF added(float[] him) + { + return this.added(new VectorF(him)); + } + /** @see #subtracted(MatrixF) */ + @Const public MatrixF subtracted(VectorF him) + { + return this.subtracted(new ColumnMatrixF(him)); + } + /** @see #subtracted(VectorF) */ + @Const public MatrixF subtracted(float[] him) + { + return this.subtracted(new VectorF(him)); + } + + /** @see #add(MatrixF) */ + @NonConst public void add(VectorF him) + { + this.add(new ColumnMatrixF(him)); + } + /** @see #add(VectorF) */ + @NonConst public void add(float[] him) + { + this.add(new VectorF(him)); + } + /** @see #subtract(MatrixF) */ + @NonConst public void subtract(VectorF him) + { + this.subtract(new ColumnMatrixF(him)); + } + /** @see #subtract(VectorF) */ + @NonConst public void subtract(float[] him) + { + this.subtract(new VectorF(him)); + } + + //---------------------------------------------------------------------------------------------- + // Transformations + //---------------------------------------------------------------------------------------------- + + /** + * Assumes that the receiver is non-perspective transformation matrix. Returns the translation + * component of the transformation. + * @return the translation component of the transformation + */ + @Const public VectorF getTranslation() + { + return this.getColumn(3).normalized3D(); + } + + //---------------------------------------------------------------------------------------------- + // Utility + //---------------------------------------------------------------------------------------------- + + protected RuntimeException dimensionsError() + { + return dimensionsError(this.numRows, this.numCols); + } + + @SuppressLint("DefaultLocale") + protected static RuntimeException dimensionsError(int numRows, int numCols) + { + return new IllegalArgumentException(String.format("matrix dimensions are incorrect: rows=%d cols=%d", numRows, numCols)); + } + + //---------------------------------------------------------------------------------------------- + // Inverses (at end because of verbosity) + //---------------------------------------------------------------------------------------------- + + /** + * Returns a matrix which is the matrix-multiplication inverse of the receiver. + * @return a matrix which is the matrix-multiplication inverse of the receiver + */ + @Const public MatrixF inverted() + { + // Algorithms were generated with the help of Mathematica: general nxn matrices with symbolic + // (instead of numeric) entries were defined, their inverse symbolically computed, then + // automatically transcribed to Java. + + if (this.numRows != this.numCols) throw dimensionsError(); + + if (this.numRows == 4) + { + MatrixF result = this.emptyMatrix(4,4); + + final float m00=get(0,0), m01=get(0,1), m02=get(0,2), m03=get(0,3); + final float m10=get(1,0), m11=get(1,1), m12=get(1,2), m13=get(1,3); + final float m20=get(2,0), m21=get(2,1), m22=get(2,2), m23=get(2,3); + final float m30=get(3,0), m31=get(3,1), m32=get(3,2), m33=get(3,3); + + final float denom = m00 * m11 * m22 * m33 + + m00 * m12 * m23 * m31 + + m00 * m13 * m21 * m32 + + m01 * m10 * m23 * m32 + + m01 * m12 * m20 * m33 + + m01 * m13 * m22 * m30 + + m02 * m10 * m21 * m33 + + m02 * m11 * m23 * m30 + + m02 * m13 * m20 * m31 + + m03 * m10 * m22 * m31 + + m03 * m11 * m20 * m32 + + m03 * m12 * m21 * m30 + - m01 * m10 * m22 * m33 + - m00 * m12 * m21 * m33 + - m02 * m11 * m20 * m33 + - m00 * m11 * m23 * m32 + - m03 * m10 * m21 * m32 + - m01 * m13 * m20 * m32 + - m02 * m10 * m23 * m31 + - m00 * m13 * m22 * m31 + - m03 * m12 * m20 * m31 + - m01 * m12 * m23 * m30 + - m03 * m11 * m22 * m30 + - m02 * m13 * m21 * m30; + + result.put(0, 0, (m11 * m22 * m33 + m12 * m23 * m31 + m13 * m21 * m32 - m12 * m21 * m33 - m11 * m23 * m32 - m13 * m22 * m31) / denom); + result.put(0, 1, (m01 * m23 * m32 + m02 * m21 * m33 + m03 * m22 * m31 - m01 * m22 * m33 - m03 * m21 * m32 - m02 * m23 * m31) / denom); + result.put(0, 2, (m01 * m12 * m33 + m02 * m13 * m31 + m03 * m11 * m32 - m02 * m11 * m33 - m01 * m13 * m32 - m03 * m12 * m31) / denom); + result.put(0, 3, (m01 * m13 * m22 + m02 * m11 * m23 + m03 * m12 * m21 - m01 * m12 * m23 - m03 * m11 * m22 - m02 * m13 * m21) / denom); + result.put(1, 0, (m10 * m23 * m32 + m12 * m20 * m33 + m13 * m22 * m30 - m10 * m22 * m33 - m13 * m20 * m32 - m12 * m23 * m30) / denom); + result.put(1, 1, (m00 * m22 * m33 + m02 * m23 * m30 + m03 * m20 * m32 - m02 * m20 * m33 - m00 * m23 * m32 - m03 * m22 * m30) / denom); + result.put(1, 2, (m00 * m13 * m32 + m02 * m10 * m33 + m03 * m12 * m30 - m00 * m12 * m33 - m03 * m10 * m32 - m02 * m13 * m30) / denom); + result.put(1, 3, (m00 * m12 * m23 + m02 * m13 * m20 + m03 * m10 * m22 - m02 * m10 * m23 - m00 * m13 * m22 - m03 * m12 * m20) / denom); + result.put(2, 0, (m10 * m21 * m33 + m11 * m23 * m30 + m13 * m20 * m31 - m11 * m20 * m33 - m10 * m23 * m31 - m13 * m21 * m30) / denom); + result.put(2, 1, (m00 * m23 * m31 + m01 * m20 * m33 + m03 * m21 * m30 - m00 * m21 * m33 - m03 * m20 * m31 - m01 * m23 * m30) / denom); + result.put(2, 2, (m00 * m11 * m33 + m01 * m13 * m30 + m03 * m10 * m31 - m01 * m10 * m33 - m00 * m13 * m31 - m03 * m11 * m30) / denom); + result.put(2, 3, (m00 * m13 * m21 + m01 * m10 * m23 + m03 * m11 * m20 - m00 * m11 * m23 - m03 * m10 * m21 - m01 * m13 * m20) / denom); + result.put(3, 0, (m10 * m22 * m31 + m11 * m20 * m32 + m12 * m21 * m30 - m10 * m21 * m32 - m12 * m20 * m31 - m11 * m22 * m30) / denom); + result.put(3, 1, (m00 * m21 * m32 + m01 * m22 * m30 + m02 * m20 * m31 - m01 * m20 * m32 - m00 * m22 * m31 - m02 * m21 * m30) / denom); + result.put(3, 2, (m00 * m12 * m31 + m01 * m10 * m32 + m02 * m11 * m30 - m00 * m11 * m32 - m02 * m10 * m31 - m01 * m12 * m30) / denom); + result.put(3, 3, (m00 * m11 * m22 + m01 * m12 * m20 + m02 * m10 * m21 - m01 * m10 * m22 - m00 * m12 * m21 - m02 * m11 * m20) / denom); + + return result; + } + + if (this.numRows == 3) + { + MatrixF result = this.emptyMatrix(3,3); + + final float m00=get(0,0), m01=get(0,1), m02=get(0,2); + final float m10=get(1,0), m11=get(1,1), m12=get(1,2); + final float m20=get(2,0), m21=get(2,1), m22=get(2,2); + + final float denom = m00 * m11 * m22 + + m01 * m12 * m20 + + m02 * m10 * m21 + - m01 * m10 * m22 + - m00 * m12 * m21 + - m02 * m11 * m20; + + result.put(0, 0, (m11 * m22 - m12 * m21) / denom); + result.put(0, 1, (m02 * m21 - m01 * m22) / denom); + result.put(0, 2, (m01 * m12 - m02 * m11) / denom); + result.put(1, 0, (m12 * m20 - m10 * m22) / denom); + result.put(1, 1, (m00 * m22 - m02 * m20) / denom); + result.put(1, 2, (m02 * m10 - m00 * m12) / denom); + result.put(2, 0, (m10 * m21 - m11 * m20) / denom); + result.put(2, 1, (m01 * m20 - m00 * m21) / denom); + result.put(2, 2, (m00 * m11 - m01 * m10) / denom); + + return result; + } + + if (this.numRows == 2) + { + MatrixF result = this.emptyMatrix(2,2); + + final float m00=get(0,0), m01=get(0,1); + final float m10=get(1,0), m11=get(1,1); + + final float denom = m00 * m11 - m01 * m10; + + result.put(0, 0, (m11) / denom); + result.put(0, 1, (-m01) / denom); + result.put(1, 0, (-m10) / denom); + result.put(1, 1, (m00) / denom); + + return result; + } + + if (this.numRows == 1) + { + MatrixF result = this.emptyMatrix(1,1); + result.put(0,0, 1 / get(0,0)); + return result; + } + + throw dimensionsError(); // really NYI: we haven't bothered to code other cases + } + +} \ No newline at end of file diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java new file mode 100644 index 00000000..277867f9 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/OpenGLMatrix.java @@ -0,0 +1,264 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +import android.opengl.Matrix; + +import org.firstinspires.ftc.robotcore.external.Const; +import org.firstinspires.ftc.robotcore.external.NonConst; +import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; +import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; +import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; +import org.firstinspires.ftc.robotcore.external.navigation.Orientation; + +/** + * An {@link OpenGLMatrix} is a 4x4 matrix commonly used as a transformation matrix for 3D + * homogeneous coordinates. The data layout of an {@link OpenGLMatrix} is used heavily in the + * OpenGL high performance graphics standard. + * + * @see Homogenous coordinates + * @see Transformation Matrix + * @see android.opengl.Matrix + * @see Matrix + */ +public class OpenGLMatrix extends ColumnMajorMatrixF +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + float[] data; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + public OpenGLMatrix() + { + super(4,4); + this.data = new float[4*4]; + Matrix.setIdentityM(this.data, 0); + } + + public OpenGLMatrix(float[] data) + { + super(4,4); + this.data = data; + if (this.data.length != 4*4) throw dimensionsError(); + } + + /** + * Constructs an OpenGL matrix whose values are initialized from the other matrix. + * The other matrix must have dimensions at most 4x4. + * @param him the matrix from which to initialize our own data + */ + public OpenGLMatrix(MatrixF him) + { + this(); + if (him.numRows > 4 || him.numCols > 4) throw him.dimensionsError(); + for (int i = 0; i < Math.min(4,him.numRows); i++) + { + for (int j = 0; j < Math.min(4,him.numCols); j++) + { + this.put(i,j, him.get(i,j)); + } + } + } + + @Override public MatrixF emptyMatrix(int numRows, int numCols) + { + if (numRows==4 && numCols==4) + return new OpenGLMatrix(); + else + return new GeneralMatrixF(numRows, numCols); + } + + /** + * Creates a matrix for rotation by the indicated angle around the indicated vector. + */ + public static OpenGLMatrix rotation(AngleUnit angleUnit, float angle, float dx, float dy, float dz) + { + float[] data = new float[16]; + Matrix.setRotateM(data, 0, angleUnit.toDegrees(angle), dx, dy, dz); + return new OpenGLMatrix(data); + } + + /** + * Creates a matrix for a rotation specified by three successive rotation angles. + * @see Orientation#getRotationMatrix(AxesReference, AxesOrder, AngleUnit, float, float, float) + */ + public static OpenGLMatrix rotation(AxesReference axesReference, AxesOrder axesOrder, AngleUnit angleUnit, float first, float second, float third) + { + OpenGLMatrix rotation = Orientation.getRotationMatrix(axesReference, axesOrder, angleUnit, first, second, third); + return identityMatrix().multiplied(rotation); + } + public static OpenGLMatrix translation(float dx, float dy, float dz) + { + OpenGLMatrix result = new OpenGLMatrix(); + result.translate(dx, dy, dz); + return result; + } + public static OpenGLMatrix identityMatrix() + { + return new OpenGLMatrix(); + } + + //---------------------------------------------------------------------------------------------- + // Accessing + //---------------------------------------------------------------------------------------------- + + @Override public float[] getData() + { + return this.data; + } + + //---------------------------------------------------------------------------------------------- + // Transformation matrix operations (in-place). These methods all return the receiver + // in order to facilitate chaining. + // + // Note that these are some of the very view matrix operations that update-in-place rather than + // returning a new matrix and leaving the receiver unmodified. Care must thus be taken to avoid + // sharing the data of this matrix (using getData()) with other matrix-related objects and then + // subsequently modifying this matrix. + //---------------------------------------------------------------------------------------------- + + @NonConst public void scale(float scaleX, float scaleY, float scaleZ) + { + Matrix.scaleM(this.data, 0, scaleX, scaleY, scaleZ); + } + @NonConst public void scale(float scale) + { + this.scale(scale, scale, scale); + } + @NonConst public void translate(float dx, float dy, float dz) + { + Matrix.translateM(this.data, 0, dx, dy, dz); + } + @NonConst public void rotate(AngleUnit angleUnit, float angle, float dx, float dy, float dz) + { + Matrix.rotateM(this.data, 0, angleUnit.toDegrees(angle), dx, dy, dz); + } + @NonConst public void rotate(AxesReference axesReference, AxesOrder axesOrder, AngleUnit angleUnit, float first, float second, float third) + { + OpenGLMatrix rotation = Orientation.getRotationMatrix(axesReference, axesOrder, angleUnit, first, second, third); + this.data = this.multiplied(rotation).getData(); + } + + //---------------------------------------------------------------------------------------------- + // Transformation matrix operations + //---------------------------------------------------------------------------------------------- + + @Const public OpenGLMatrix scaled(float scaleX, float scaleY, float scaleZ) + { + OpenGLMatrix result = new OpenGLMatrix(); + Matrix.scaleM(result.data, 0, this.data, 0, scaleX, scaleY, scaleZ); + return result; + } + @Const public OpenGLMatrix scaled(float scale) + { + return scaled(scale, scale, scale); + } + @Const public OpenGLMatrix translated(float dx, float dy, float dz) + { + OpenGLMatrix result = new OpenGLMatrix(); + Matrix.translateM(result.data, 0, this.data, 0, dx, dy, dz); + return result; + } + @Const public OpenGLMatrix rotated(AngleUnit angleUnit, float angle, float dx, float dy, float dz) + { + OpenGLMatrix result = new OpenGLMatrix(); + Matrix.rotateM(result.data, 0, this.data, 0, angleUnit.toDegrees(angle), dx, dy, dz); + return result; + } + @Const public OpenGLMatrix rotated(AxesReference axesReference, AxesOrder axesOrder, AngleUnit angleUnit, float first, float second, float third) + { + OpenGLMatrix rotation = Orientation.getRotationMatrix(axesReference, axesOrder, angleUnit, first, second, third); + return this.multiplied(rotation); + } + + //---------------------------------------------------------------------------------------------- + // Matrix operations + //---------------------------------------------------------------------------------------------- + + @Override @Const public OpenGLMatrix inverted() + { + OpenGLMatrix result = new OpenGLMatrix(); + Matrix.invertM(result.data, 0, this.data, 0); + return result; + } + + @Override @Const public OpenGLMatrix transposed() + { + return (OpenGLMatrix)super.transposed(); + } + + @Const public OpenGLMatrix multiplied(OpenGLMatrix him) + { + OpenGLMatrix result = new OpenGLMatrix(); + Matrix.multiplyMM(result.data, 0, this.data, 0, him.getData(), 0); + return result; + } + + @Override @Const public MatrixF multiplied(MatrixF him) + { + if (him instanceof OpenGLMatrix) + { + return this.multiplied((OpenGLMatrix)him); + } + else + return super.multiplied(him); + } + + /** + * Updates the receiver to be the product of itself and another matrix. + * @param him the matrix with which the receiver is to be multiplied. + */ + @NonConst public void multiply(OpenGLMatrix him) + { + this.data = this.multiplied(him).getData(); + } + + /** + * Updates the receiver to be the product of itself and another matrix. + * @param him the matrix with which the receiver is to be multiplied. + */ + @Override @NonConst public void multiply(MatrixF him) + { + if (him instanceof OpenGLMatrix) + { + this.multiply((OpenGLMatrix)him); + } + else + super.multiply(him); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java new file mode 100644 index 00000000..9a36052a --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMajorMatrixF.java @@ -0,0 +1,57 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +/** + * A {@link RowMajorMatrixF} is a dense matrix whose entries are arranged in + * row-major order. + * @see Row Major Order + */ +public abstract class RowMajorMatrixF extends DenseMatrixF +{ + public RowMajorMatrixF(int nRows, int nCols) + { + super(nRows, nCols); + } + + @Override + protected int indexFromRowCol(int row, int col) + { + return row * numCols + col; + } + + @Override public VectorF toVector() + { + return new VectorF(this.getData()); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java new file mode 100644 index 00000000..86b5cee8 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/RowMatrixF.java @@ -0,0 +1,62 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +/** + * A {@link ColumnMatrixF} is a matrix that converts a VectorF into a 1xn matrix + */ +public class RowMatrixF extends MatrixF +{ + VectorF vector; + + public RowMatrixF(VectorF vector) + { + super(1, vector.length()); + this.vector = vector; + } + + @Override public float get(int row, int col) + { + return this.vector.get(col); + } + + @Override public void put(int row, int col, float value) + { + this.vector.put(col, value); + } + + @Override public MatrixF emptyMatrix(int numRows, int numCols) + { + return new GeneralMatrixF(numRows, numCols); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java new file mode 100644 index 00000000..d85b9d9e --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/SliceMatrixF.java @@ -0,0 +1,90 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +/** + * A {@link SliceMatrixF} is a matrix whose implementation is a submatrix of some other matrix. + */ +public class SliceMatrixF extends MatrixF +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + protected MatrixF matrix; + protected int row; + protected int col; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + /** + * Creates a {@link SliceMatrixF} based on the indicated matrix whose upper left corner is at + * (row, col) of that matrix and whose size is numRows x numCols. + * @param matrix the matrix we are to take a slice of + * @param row the row in matrix in which the slice is to begin + * @param col the column in matrix in which the slice is to begin + * @param numRows the number of rows that the slice should be + * @param numCols the number of columns that the slice should be + */ + public SliceMatrixF(MatrixF matrix, int row, int col, int numRows, int numCols) + { + super(numRows, numCols); + this.matrix = matrix; + this.row = row; + this.col = col; + + if (row + numRows >= matrix.numRows) throw dimensionsError(); + if (col + numCols >= matrix.numCols) throw dimensionsError(); + } + + //---------------------------------------------------------------------------------------------- + // Accessing + //---------------------------------------------------------------------------------------------- + + @Override public float get(int row, int col) + { + return this.matrix.get(this.row + row, this.col + col); + } + + @Override public void put(int row, int col, float value) + { + this.matrix.put(this.row + row, this.col + col, value); + } + + @Override public MatrixF emptyMatrix(int numRows, int numCols) + { + return this.matrix.emptyMatrix(numRows, numCols); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java new file mode 100644 index 00000000..67ae41ba --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/matrices/VectorF.java @@ -0,0 +1,318 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.matrices; + +import android.annotation.SuppressLint; + +import org.firstinspires.ftc.robotcore.external.Const; +import org.firstinspires.ftc.robotcore.external.NonConst; + +/** + * A {@link VectorF} represents a single-dimensional vector of floats. It is not a matrix, + * but can easily be converted into either a {@link RowMatrixF} or a {@link ColumnMatrixF} should + * that be desired. That said, vectors can be multiplied by matrices to their left (or right); this + * is commonly used to transform a set of coordinates (in the vector) by a transformation matrix. + * + * @see MatrixF + * @see RowMatrixF + * @see ColumnMatrixF + */ +public class VectorF +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + protected float[] data; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + /** + * Creates a new vector of the indicated length. The vector will contain zeros. + * @param length the length of the new vector to return + * @return the newly created vector + */ + public static VectorF length(int length) + { + return new VectorF(new float[length]); + } + + public VectorF(float[] data) + { + this.data = data; + } + + public VectorF(float x) + { + this.data = new float[1]; + this.data[0] = x; + } + + public VectorF(float x, float y) + { + this.data = new float[2]; + this.data[0] = x; + this.data[1] = y; + } + + public VectorF(float x, float y, float z) + { + this.data = new float[3]; + this.data[0] = x; + this.data[1] = y; + this.data[2] = z; + } + + public VectorF(float x, float y, float z, float w) + { + this.data = new float[4]; + this.data[0] = x; + this.data[1] = y; + this.data[2] = z; + this.data[3] = w; + } + + //---------------------------------------------------------------------------------------------- + // Accessing + //---------------------------------------------------------------------------------------------- + + @Const public float[] getData() + { + return this.data; + } + + @Const public int length() + { + return this.data.length; + } + + @Const public float get(int index) + { + return this.data[index]; + } + + @NonConst public void put(int index, float value) + { + this.data[index] = value; + } + + @Override public String toString() + { + StringBuilder result = new StringBuilder(); + result.append("{"); + for (int i = 0; i < this.length(); i++) + { + if (i > 0) result.append(" "); + result.append(String.format("%.2f", this.data[i])); + } + result.append("}"); + return result.toString(); + } + + //---------------------------------------------------------------------------------------------- + // Transformation matrix operations + //---------------------------------------------------------------------------------------------- + + /** + * Consider this vector as a 3D coordinate or 3D homogeneous coordinate, and, if the + * latter, return its normalized form. In either case, the result is of length three, and + * contains coordinate values for x, y, and z at indices 0, 1, and 2 respectively. + * @return the normalized form of this coordinate vector + * + * @see Homogeneous coordinates + */ + @Const public VectorF normalized3D() + { + if (this.length()==3) + { + return this; + } + else if (this.length()==4) + { + return new VectorF( + this.data[0]/this.data[3], + this.data[1]/this.data[3], + this.data[2]/this.data[3]); + } + else + throw dimensionsError(); + } + + //---------------------------------------------------------------------------------------------- + // Matrix Operations + //---------------------------------------------------------------------------------------------- + + @Const public float magnitude() + { + return (float)Math.sqrt(this.dotProduct(this)); + } + + /** + * Returns the dot product of this vector and another. + * @param him the other vector with whom the dot product is to be formed + * @return the dot product of this vector and another. + * + * @see Dot product + */ + @Const public float dotProduct(VectorF him) + { + if (this.length() == him.length()) + { + float sum = 0; + for (int i = 0; i < this.length(); i++) + { + sum += this.get(i) * him.get(i); + } + return sum; + } + else + throw dimensionsError(); + } + + /** + * Multiplies this vector, taken as a row vector, against the indicated matrix. + */ + @Const public MatrixF multiplied(MatrixF him) + { + return new RowMatrixF(this).multiplied(him); + } + + /** + * Adds this vector, taken as a row vector against, to the indicated matrix. + */ + @Const public MatrixF added(MatrixF addend) + { + return new RowMatrixF(this).added(addend); + } + + @Const public VectorF added(VectorF addend) + { + if (this.length() == addend.length()) + { + VectorF result = VectorF.length(this.length()); + for (int i = 0; i < this.length(); i++) + { + result.put(i, this.get(i) + addend.get(i)); + } + return result; + } + else + throw dimensionsError(); + } + + @NonConst public void add(VectorF addend) + { + if (this.length() == addend.length()) + { + for (int i = 0; i < this.length(); i++) + { + this.put(i, this.get(i) + addend.get(i)); + } + } + else + throw dimensionsError(); + } + + /** + * Subtracts the indicated matrix from this vector, taken as a row vector. + */ + @Const public MatrixF subtracted(MatrixF subtrahend) + { + return new RowMatrixF(this).subtracted(subtrahend); + } + + @Const public VectorF subtracted(VectorF subtrahend) + { + if (this.length() == subtrahend.length()) + { + VectorF result = VectorF.length(this.length()); + for (int i = 0; i < this.length(); i++) + { + result.put(i, this.get(i) - subtrahend.get(i)); + } + return result; + } + else + throw dimensionsError(); + } + + @NonConst public void subtract(VectorF subtrahend) + { + if (this.length() == subtrahend.length()) + { + for (int i = 0; i < this.length(); i++) + { + this.put(i, this.get(i) - subtrahend.get(i)); + } + } + else + throw dimensionsError(); + } + + /** + * Returns a new vector containing the elements of this vector scaled by the indicated factor. + */ + @Const public VectorF multiplied(float scale) + { + VectorF result = VectorF.length(this.length()); + for (int i = 0; i < this.length(); i++) + { + result.put(i, this.get(i) * scale); + } + return result; + } + + @NonConst public void multiply(float scale) + { + for (int i = 0; i < this.length(); i++) + { + this.put(i, this.get(i) * scale); + } + } + + //---------------------------------------------------------------------------------------------- + // Utility + //---------------------------------------------------------------------------------------------- + + protected RuntimeException dimensionsError() + { + return dimensionsError(this.length()); + } + + @SuppressLint("DefaultLocale") protected static RuntimeException dimensionsError(int length) + { + return new IllegalArgumentException(String.format("vector dimensions are incorrect: length=%d", length)); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java new file mode 100644 index 00000000..da450693 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Acceleration.java @@ -0,0 +1,120 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +import java.util.Locale; + +/** + * Instances of {@link Acceleration} represent the second derivative of {@link Position} over time. This + * is also to say that {@code Position} is a double integration of {@code Acceleration} with respect + * to time. + * + * @see Velocity + * @see Position + */ +public class Acceleration +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + /** The (nominal) acceleration due to Earth's gravity + * The units are in m/s^2 + */ + public static final double earthGravity = 9.80665; + + /** + * The distance units in which this acceleration is expressed. The time unit is always "per second per second". + */ + public DistanceUnit unit; + + public double xAccel; + public double yAccel; + public double zAccel; + + /** + * the time on the System.nanoTime() clock at which the data was acquired. If no + * timestamp is associated with this particular set of data, this value is zero. + */ + public long acquisitionTime; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + public Acceleration() + { + this(DistanceUnit.MM, 0, 0, 0, 0); + } + + public Acceleration(DistanceUnit unit, double xAccel, double yAccel, double zAccel, long acquisitionTime) + { + this.unit = unit; + this.xAccel = xAccel; + this.yAccel = yAccel; + this.zAccel = zAccel; + this.acquisitionTime = acquisitionTime; + } + + /** + * Returns an acceleration constructed from measures in units of earth's gravity + * rather than explicit distance units. + */ + public static Acceleration fromGravity(double gx, double gy, double gz, long acquisitionTime) + { + return new Acceleration(DistanceUnit.METER, gx * earthGravity, gy * earthGravity, gz * earthGravity, acquisitionTime); + } + + public Acceleration toUnit(DistanceUnit distanceUnit) + { + if (distanceUnit != this.unit) + { + return new Acceleration(distanceUnit, + distanceUnit.fromUnit(this.unit, xAccel), + distanceUnit.fromUnit(this.unit, yAccel), + distanceUnit.fromUnit(this.unit, zAccel), + this.acquisitionTime); + } + else + return this; + } + + //---------------------------------------------------------------------------------------------- + // Formatting + //---------------------------------------------------------------------------------------------- + + @Override public String toString() + { + return String.format(Locale.getDefault(), "(%.3f %.3f %.3f)%s/s^2", xAccel, yAccel, zAccel, unit.toString()); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java new file mode 100644 index 00000000..39150c91 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AngleUnit.java @@ -0,0 +1,239 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +/** + * An {@link AngleUnit} represents angles in different units of measure and + * provides utility methods to convert across units. {@link AngleUnit} does not + * maintain angle information information internally, but only helps organize + * and use angle measures that may be maintained separately across various contexts. + *

+ * Angles can be distinguished along (at least) two axes: + *

    + *
  1. the fundamental unit (radians vs degrees)
  2. + *
  3. whether the angular quantity is normalized or not to the range of [-180,+180) degrees
  4. + *
+ * Normalized angles are of most utility when dealing with physical angles, as normalization + * removes ambiguity of representation. In particular, two angles can be compared for equality + * by subtracting them, normalizing, and testing whether the absolute value of the result is + * smaller than some tolerance threshold. This approach neatly handles all cases of cyclical + * wrapping without unexpected discontinuities. + *

+ * Unnormalized angles can be handy when the angular quantity is not a physical angle but some + * related quantity such as an angular velocity or acceleration, where the + * quantity in question lacks the 360-degree cyclical equivalence of a physical angle. + *

+ * {@link AngleUnit} expresses normalized angles, while {@link UnnormalizedAngleUnit} expresses unnormalized ones + *

+ */ +@SuppressWarnings("WeakerAccess") +public enum AngleUnit +{ + DEGREES(0), RADIANS(1); + public final byte bVal; + + protected static final double TwoPi = 2 * Math.PI; + public static final float Pif = (float) Math.PI; + + AngleUnit(int i) + { + bVal = (byte) i; + } + + //---------------------------------------------------------------------------------------------- + // Primitive operations + //---------------------------------------------------------------------------------------------- + + public double fromDegrees(double degrees) + { + switch (this) + { + default: + case RADIANS: return this.normalize(degrees / 180.0 * Math.PI); + case DEGREES: return this.normalize(degrees); + } + } + + public float fromDegrees(float degrees) + { + switch (this) + { + default: + case RADIANS: return this.normalize(degrees / 180.0f * Pif); + case DEGREES: return this.normalize(degrees); + } + } + + public double fromRadians(double radians) + { + switch (this) + { + default: + case RADIANS: return this.normalize(radians); + case DEGREES: return this.normalize(radians / Math.PI * 180.0); + } + } + + public float fromRadians(float radians) + { + switch (this) + { + default: + case RADIANS: return this.normalize(radians); + case DEGREES: return this.normalize(radians / Pif * 180.0f); + } + } + + public double fromUnit(AngleUnit them, double theirs) + { + switch (them) + { + default: + case RADIANS: return this.fromRadians(theirs); + case DEGREES: return this.fromDegrees(theirs); + } + } + + public float fromUnit(AngleUnit them, float theirs) + { + switch (them) + { + default: + case RADIANS: return this.fromRadians(theirs); + case DEGREES: return this.fromDegrees(theirs); + } + } + + //---------------------------------------------------------------------------------------------- + // Derived operations + //---------------------------------------------------------------------------------------------- + + public double toDegrees(double inOurUnits) + { + switch (this) + { + default: + case RADIANS: return DEGREES.fromRadians(inOurUnits); + case DEGREES: return DEGREES.fromDegrees(inOurUnits); + } + } + + public float toDegrees(float inOurUnits) + { + switch (this) + { + default: + case RADIANS: return DEGREES.fromRadians(inOurUnits); + case DEGREES: return DEGREES.fromDegrees(inOurUnits); + } + } + + public double toRadians(double inOurUnits) + { + switch (this) + { + default: + case RADIANS: return RADIANS.fromRadians(inOurUnits); + case DEGREES: return RADIANS.fromDegrees(inOurUnits); + } + } + + public float toRadians(float inOurUnits) + { + switch (this) + { + default: + case RADIANS: return RADIANS.fromRadians(inOurUnits); + case DEGREES: return RADIANS.fromDegrees(inOurUnits); + } + } + + //---------------------------------------------------------------------------------------------- + // Normalization + //---------------------------------------------------------------------------------------------- + + public double normalize(double mine) + { + switch (this) + { + default: + case RADIANS: return normalizeRadians(mine); + case DEGREES: return normalizeDegrees(mine); + } + } + + public float normalize(float mine) + { + switch (this) + { + default: + case RADIANS: return normalizeRadians(mine); + case DEGREES: return normalizeDegrees(mine); + } + } + + public static double normalizeDegrees(double degrees) + { + while (degrees >= 180.0) degrees -= 360.0; + while (degrees < -180.0) degrees += 360.0; + return degrees; + } + + public static float normalizeDegrees(float degrees) + { + return (float)normalizeDegrees((double)degrees); + } + + public static double normalizeRadians(double radians) + { + while (radians >= Math.PI) radians -= TwoPi; + while (radians < -Math.PI) radians += TwoPi; + return radians; + } + + public static float normalizeRadians(float radians) + { + return (float)normalizeRadians((double)radians); + } + + public UnnormalizedAngleUnit getUnnormalized() + { + switch (this) + { + default: + case RADIANS: return UnnormalizedAngleUnit.RADIANS; + case DEGREES: return UnnormalizedAngleUnit.DEGREES; + } + } + +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java new file mode 100644 index 00000000..4bb3f743 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesOrder.java @@ -0,0 +1,103 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +/** + * {@link AxesOrder} indicates the chronological order of axes about which the three rotations + * of an {@link Orientation} take place. The geometry of three space is such that there are + * exactly twelve distinct rotational orders. + * + * @see Orientation + * @see AxesReference + * @see Euler Angles + */ +public enum AxesOrder +{ + XZX(new int[]{0, 2, 0}), XYX(new int[]{0, 1, 0}), YXY(new int[]{1, 0, 1}), + YZY(new int[]{1, 2, 1}), ZYZ(new int[]{2, 1, 2}), ZXZ(new int[]{2, 0, 2}), + XZY(new int[]{0, 2, 1}), XYZ(new int[]{0, 1, 2}), YXZ(new int[]{1, 0, 2}), + YZX(new int[]{1, 2, 0}), ZYX(new int[]{2, 1, 0}), ZXY(new int[]{2, 0, 1}); + + private final int[] indices; + + AxesOrder(int[] indices) + { + this.indices = indices; + } + + /** + * Returns the numerical axes indices associated with this {@link AxesOrder}. + * @return the numerical axes indices associated with this {@link AxesOrder}. + */ + public int[] indices() + { + return this.indices; + } + + /** + * Returns the {@link Axis axes} associated with this {@link AxesOrder}. + * @return the {@link Axis axes} associated with this {@link AxesOrder}. + */ + public Axis[] axes() + { + Axis[] result = new Axis[3]; + result[0] = Axis.fromIndex(this.indices[0]); + result[1] = Axis.fromIndex(this.indices[1]); + result[2] = Axis.fromIndex(this.indices[2]); + return result; + } + + /** + * Returns the {@link AxesOrder} which is the chronological reverse of the receiver. + * @return the {@link AxesOrder} which is the chronological reverse of the receiver. + */ + public AxesOrder reverse() + { + switch (this) + { + default: + case XZX: return XZX; + case XYX: return XYX; + case YXY: return YXY; + case YZY: return YZY; + case ZYZ: return ZYZ; + case ZXZ: return ZXZ; + case XZY: return YZX; + case XYZ: return ZYX; + case YXZ: return ZXY; + case YZX: return XZY; + case ZYX: return XYZ; + case ZXY: return YXZ; + } + } +} \ No newline at end of file diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java new file mode 100644 index 00000000..bc34ce26 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/AxesReference.java @@ -0,0 +1,57 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +/** + * {@link AxesReference} indicates whether we have intrinsic rotations, where the axes + * move with the object that is rotating, or extrinsic rotations, where they remain fixed + * in the world around the object. + * + * @see Orientation + * @see AxesOrder + * @see Euler Angles + */ +public enum AxesReference +{ + EXTRINSIC, INTRINSIC; + + public AxesReference reverse() + { + switch (this) + { + default: + case EXTRINSIC: return INTRINSIC; + case INTRINSIC: return EXTRINSIC; + } + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java new file mode 100644 index 00000000..a8064ecb --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Axis.java @@ -0,0 +1,59 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +/** + * {@link Axis} enumerates the common X,Y,Z three-dimensional orthogonal axes. + */ +public enum Axis +{ + X(0), + Y(1), + Z(2), + UNKNOWN(-1); + + public int index; + + Axis(int index) { this.index = index; } + + public static Axis fromIndex(int index) + { + switch (index) + { + case 0: return X; + case 1: return Y; + case 2: return Z; + default: return UNKNOWN; + } + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java new file mode 100644 index 00000000..e44675c3 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/DistanceUnit.java @@ -0,0 +1,202 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +import java.util.Locale; + +/** + * {@link DistanceUnit} represents a unit of measure of distance. + */ +public enum DistanceUnit +{ + METER(0), CM(1), MM(2), INCH(3); + public final byte bVal; + + public static final double infinity = Double.MAX_VALUE; + public static final double mmPerInch = 25.4; + public static final double mPerInch = mmPerInch * 0.001; + + DistanceUnit(int i) + { + this.bVal = (byte)i; + } + + //---------------------------------------------------------------------------------------------- + // Primitive operations + //---------------------------------------------------------------------------------------------- + + public double fromMeters(double meters) + { + if (meters==infinity) return infinity; + switch (this) + { + default: + case METER: return meters; + case CM: return meters * 100; + case MM: return meters * 1000; + case INCH: return meters / mPerInch; + } + } + + public double fromInches(double inches) + { + if (inches==infinity) return infinity; + switch (this) + { + default: + case METER: return inches * mPerInch; + case CM: return inches * mPerInch * 100; + case MM: return inches * mPerInch * 1000; + case INCH: return inches; + } + } + + public double fromCm(double cm) + { + if (cm==infinity) return infinity; + switch (this) + { + default: + case METER: return cm / 100; + case CM: return cm; + case MM: return cm * 10; + case INCH: return fromMeters(METER.fromCm(cm)); + } + } + + public double fromMm(double mm) + { + if (mm==infinity) return infinity; + switch (this) + { + default: + case METER: return mm / 1000; + case CM: return mm / 10; + case MM: return mm; + case INCH: return fromMeters(METER.fromMm(mm)); + } + } + + public double fromUnit(DistanceUnit him, double his) + { + switch (him) + { + default: + case METER: return this.fromMeters(his); + case CM: return this.fromCm(his); + case MM: return this.fromMm(his); + case INCH: return this.fromInches(his); + } + } + + //---------------------------------------------------------------------------------------------- + // Derived operations + //---------------------------------------------------------------------------------------------- + + public double toMeters(double inOurUnits) + { + switch (this) + { + default: + case METER: return METER.fromMeters(inOurUnits); + case CM: return METER.fromCm(inOurUnits); + case MM: return METER.fromMm(inOurUnits); + case INCH: return METER.fromInches(inOurUnits); + } + } + + public double toInches(double inOurUnits) + { + switch (this) + { + default: + case METER: return INCH.fromMeters(inOurUnits); + case CM: return INCH.fromCm(inOurUnits); + case MM: return INCH.fromMm(inOurUnits); + case INCH: return INCH.fromInches(inOurUnits); + } + } + + public double toCm(double inOurUnits) + { + switch (this) + { + default: + case METER: return CM.fromMeters(inOurUnits); + case CM: return CM.fromCm(inOurUnits); + case MM: return CM.fromMm(inOurUnits); + case INCH: return CM.fromInches(inOurUnits); + } + } + + public double toMm(double inOurUnits) + { + switch (this) + { + default: + case METER: return MM.fromMeters(inOurUnits); + case CM: return MM.fromCm(inOurUnits); + case MM: return MM.fromMm(inOurUnits); + case INCH: return MM.fromInches(inOurUnits); + } + } + + //---------------------------------------------------------------------------------------------- + // Formatting + //---------------------------------------------------------------------------------------------- + + public String toString(double inOurUnits) + { + switch (this) + { + default: + case METER: return String.format(Locale.getDefault(), "%.3fm", inOurUnits); + case CM: return String.format(Locale.getDefault(), "%.1fcm", inOurUnits); + case MM: return String.format(Locale.getDefault(), "%.0fmm", inOurUnits); + case INCH: return String.format(Locale.getDefault(), "%.2fin", inOurUnits); + } + } + + @Override public String toString() + { + switch (this) + { + default: + case METER: return "m"; + case CM: return "cm"; + case MM: return "mm"; + case INCH: return "in"; + } + } +} + diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java new file mode 100644 index 00000000..c5899e5b --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Orientation.java @@ -0,0 +1,887 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +import org.firstinspires.ftc.robotcore.external.matrices.MatrixF; +import org.firstinspires.ftc.robotcore.external.matrices.OpenGLMatrix; +import org.firstinspires.ftc.robotcore.external.matrices.VectorF; +import org.firstinspires.ftc.robotcore.internal.system.Assert; + +import static java.lang.Math.sin; +import static java.lang.Math.cos; +import static java.lang.Math.asin; +import static java.lang.Math.acos; +import static java.lang.Math.atan2; +import static java.lang.Math.PI; + +/** + * Instances of {@link Orientation} represent a rotated stance in three-dimensional space + * by way of a set of three successive rotations. + * + *

There are several ways that a particular orientation in three-space can be represented. + * One way is by specifying a (unit) directional vector about which the orientation is to occur, + * together with a rotation angle about that axis. This representation is unique up to the sign of the + * direction and angle; that is a rotation {@code a} about a vector {@code v} produces the same + * rotation as a rotation {@code -a} about the vector {@code -v}. While this manner of specifying a + * rotation is easy to visualize if the vector in question is one of the cardinal axes (ie: X,Y, or Z), + * many find it more difficult to visualize more complex rotations in this manner.

+ * + *

An alternative, more common, way to represent a particular orientation in three-space is by means + * of indicating three angles of rotation about three successive axes. You might for example be familiar + * with the notions of heading, elevation, and bank angles for aircraft. Unfortunately, there are 24 + * different yet equivalent ways that a set of three rotational angles about three axes can represent + * the same effective rotation. As might be expected, this can be the source of much confusion. The + * 24 different representations break down as follows.

+ * + *

First is the matter of the axes reference: is the coordinate system in which the referred-to rotational + * axes reside a coordinate system that moves with (and so remains fixed relative to) the object being rotated, + * or do the axes remain fixed relative to the world around the object and are unaffected by the + * object's rotational motion? The former situation is referred to as an {@link AxesReference#INTRINSIC intrinsic} + * reference perspective while the latter is an {@link AxesReference#EXTRINSIC extrinsic} perspective. + * Both points of view are equally valid methodologies, but one or the other may be more understandable + * or useful in a given application situation. + *

+ * + *

The extrinsic-vs-intrinsic difference accounts for a factor of two in our list of 24 different + * representations. The remaining factor of 12 breaks down into whether the three rotations all use + * different axes (and so are a permutation of X, Y, and Z, of which there are six in number), or whether + * the first and last axes are the same and the middle one different (e.g. Z-Y-Z); this has three + * choices for the first axis (which is also used for the last) and two remaining choices for the + * second axis, for a total, again, of six possibilities. The geometry of three-space is such that these + * twelve choices are the only distinct representational possibilities. As with the extrinsic-vs- + * intrinsic difference, all twelve of these axis {@link AxesOrder order}s are equally valid ways of + * indicating orientation, but in any given application, one way may be more useful or easier to + * understand than another. + *

+ * + *

Even on top of all that, for a given intrinsic-vs-extrinsic distinction, and a given axes + * ordering, there are two sets of angle rotation that will produce the same orientation. For example, + * an extrinsic, XZX rotation of (in degrees) 90, -90, 0 is equivalent to an extrinsic, XZX rotation + * of -90, 90, -180.

+ * + *

As was mentioned, much confusion has historically arisen from talking about an orientation as + * a set of three angles without also clearly indicating which of the 24 representational possibilities + * one is working within. One aim of {@link Orientation} is to reduce that confusion by being explicitly + * clear about this issue: an {@link Orientation} always carries along with it the indication of the + * {@link AxesReference} and {@link AxesOrder} of the orientation. Methods are provided for converting + * an {@link Orientation} to and from its associated rotation matrix.

+ * + * @see Euler Angles + * @see Axis-Angle Representation + * @see Axes Conventions + * @see Rotation Matrix + */ +public class Orientation +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + /** + * whether we have extrinsic or intrinsic rotations + * + * @see #axesOrder + */ + public AxesReference axesReference; + + /** + * the order of axes around which our three rotations occur + * + * @see #axesReference + */ + public AxesOrder axesOrder; + + /** + * the unit in which the angles are expressed + */ + public AngleUnit angleUnit; + + /** + * the chronologically first rotation made in the {@link AxesOrder} + */ + public float firstAngle; + /** + * the chronologically second rotation made in the {@link AxesOrder} + */ + public float secondAngle; + /** + * the chronologically third rotation made in the {@link AxesOrder} + */ + public float thirdAngle; + + /** + * the time on the System.nanoTime() clock at which the data was acquired. If no + * timestamp is associated with this particular set of data, this value is zero. + */ + public long acquisitionTime; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + public Orientation() + { + this(AxesReference.EXTRINSIC, AxesOrder.XYZ, AngleUnit.RADIANS, 0, 0, 0, 0); + } + + public Orientation(AxesReference axesReference, AxesOrder axesOrder, AngleUnit angleUnit, float firstAngle, float secondAngle, float thirdAngle, long acquisitionTime) + { + this.axesReference = axesReference; + this.axesOrder = axesOrder; + this.angleUnit = angleUnit; + this.firstAngle = firstAngle; + this.secondAngle = secondAngle; + this.thirdAngle = thirdAngle; + this.acquisitionTime = acquisitionTime; + } + + /** + * Converts this {@link Orientation} to one with the indicated angular units. + * + * @param angleUnit the units to use in the returned [@link Orientation} + * @return a new [@link Orientation} with the same data but in the indicated units + */ + public Orientation toAngleUnit(AngleUnit angleUnit) + { + if (angleUnit != this.angleUnit) + { + return new Orientation(this.axesReference, this.axesOrder, angleUnit, + angleUnit.fromUnit(this.angleUnit, firstAngle), + angleUnit.fromUnit(this.angleUnit, secondAngle), + angleUnit.fromUnit(this.angleUnit, thirdAngle), + this.acquisitionTime); + } + else + return this; + } + + /** + * Converts the {@link Orientation} to an equivalent one with the indicted point of view. + * + * @param axesReference whether we wish to consider rotations from an extrinsic or intrinsic point of view + * @return an equivalent orientation but with the indicated point of view. + */ + public Orientation toAxesReference(AxesReference axesReference) + { + if (this.axesReference != axesReference) + { + /** + * Theorem: Any extrinsic rotation is equivalent to an intrinsic rotation by + * the same angles but with inverted order of elemental orientations, and vice versa. + * @see Euler Angles + */ + Assert.assertTrue(axesReference == this.axesReference.reverse()); + return new Orientation(this.axesReference.reverse(), this.axesOrder.reverse(), this.angleUnit, + this.thirdAngle, this.secondAngle, this.firstAngle, this.acquisitionTime); + } + else + return this; + } + + /** + * Converts the {@link Orientation} to an equivalent one with the indicated ordering of axes + * @param axesOrder the desired ordering of axes + * @return an equivalent orientation with the indicated axes order + */ + public Orientation toAxesOrder(AxesOrder axesOrder) + { + if (this.axesOrder != axesOrder) + { + return Orientation.getOrientation(this.getRotationMatrix(), this.axesReference, axesOrder, this.angleUnit); + } + else + return this; + } + + //---------------------------------------------------------------------------------------------- + // Accessing + //---------------------------------------------------------------------------------------------- + + @Override public String toString() + { + if (this.angleUnit == AngleUnit.DEGREES) + return String.format("{%s %s %.0f %.0f %.0f}", this.axesReference.toString(), this.axesOrder.toString(), this.firstAngle, this.secondAngle, this.thirdAngle); + else + return String.format("{%s %s %.3f %.3f %.3f}", this.axesReference.toString(), this.axesOrder.toString(), this.firstAngle, this.secondAngle, this.thirdAngle); + } + + //---------------------------------------------------------------------------------------------- + // Rotation Matrices + //---------------------------------------------------------------------------------------------- + + /** + * Returns the rotation matrix associated with the receiver {@link Orientation}. + * + * @return the rotation matrix associated with the receiver {@link Orientation}. + * @see #getRotationMatrix(AxesReference, AxesOrder, AngleUnit, float, float, float) + * @see Rotation Matrix + */ + public OpenGLMatrix getRotationMatrix() + { + return getRotationMatrix(this.axesReference, this.axesOrder, this.angleUnit, this.firstAngle, this.secondAngle, this.thirdAngle); + } + + /** + * Returns the rotation matrix associated with a particular set of three rotational angles. + * + * @return the rotation matrix associated with a particular set of three rotational angles. + * @see #getRotationMatrix() + * @see Rotation Matrix + */ + public static OpenGLMatrix getRotationMatrix(AxesReference axesReference, AxesOrder axesOrder, AngleUnit unit, float firstAngle, float secondAngle, float thirdAngle) + { + if (axesReference == AxesReference.INTRINSIC) + { + /** + * Theorem: Any extrinsic rotation is equivalent to an intrinsic rotation by the same + * angles but with inverted order of elemental orientations, and vice versa. + * @see Euler Angles + */ + return getRotationMatrix(axesReference.reverse(), axesOrder.reverse(), unit, thirdAngle, secondAngle, firstAngle); + } + + /** + * The extrinsic case takes some work. + * + * Implementation note: these computations were created automatically from symbolic Mathematica expressions. + * Each computes the intrinsic rotation matrix of a given {@link AxesOrder}, where the axes in the {@link AxesOrder} + * are used left to right chronologically and the angles are applied chronologically as well. + * + * For example, the entry for YXZ is rotation matrix of the extrinsic rotation which rotates first + * Y(firstAngle), then X(secondAngle), and finally Z(thirdAngle). The rotation matrix in this case is + * Z(thirdAngle).X(secondAngle).Y(firstAngle) + * as the matrix order (when *post*-multiplying vectors, as is always done in modern computer graphics systems), + * which you will notice has the matrices in reverse order from the chronological sequence of extrinsic rotations. + */ + + firstAngle = unit.toRadians(firstAngle); + secondAngle = unit.toRadians(secondAngle); + thirdAngle = unit.toRadians(thirdAngle); + + float m00, m01, m02; + float m10, m11, m12; + float m20, m21, m22; + + switch (axesOrder) + { + default: + case XZX: + m00 = ((float) (cos(secondAngle))); + m01 = ((float) (-(cos(firstAngle) * sin(secondAngle)))); + m02 = ((float) (sin(firstAngle) * sin(secondAngle))); + m10 = ((float) (cos(thirdAngle) * sin(secondAngle))); + m11 = ((float) (cos(firstAngle) * cos(secondAngle) * cos(thirdAngle) - sin(firstAngle) * sin(thirdAngle))); + m12 = ((float) (-(cos(firstAngle) * sin(thirdAngle)) - cos(secondAngle) * cos(thirdAngle) * sin(firstAngle))); + m20 = ((float) (sin(secondAngle) * sin(thirdAngle))); + m21 = ((float) (cos(thirdAngle) * sin(firstAngle) + cos(firstAngle) * cos(secondAngle) * sin(thirdAngle))); + m22 = ((float) (cos(firstAngle) * cos(thirdAngle) - cos(secondAngle) * sin(firstAngle) * sin(thirdAngle))); + break; + case XYX: + m00 = ((float) (cos(secondAngle))); + m01 = ((float) (sin(firstAngle) * sin(secondAngle))); + m02 = ((float) (cos(firstAngle) * sin(secondAngle))); + m10 = ((float) (sin(secondAngle) * sin(thirdAngle))); + m11 = ((float) (cos(firstAngle) * cos(thirdAngle) - cos(secondAngle) * sin(firstAngle) * sin(thirdAngle))); + m12 = ((float) (-(cos(firstAngle) * cos(secondAngle) * sin(thirdAngle)) - cos(thirdAngle) * sin(firstAngle))); + m20 = ((float) (-(cos(thirdAngle) * sin(secondAngle)))); + m21 = ((float) (cos(firstAngle) * sin(thirdAngle) + cos(secondAngle) * cos(thirdAngle) * sin(firstAngle))); + m22 = ((float) (cos(firstAngle) * cos(secondAngle) * cos(thirdAngle) - sin(firstAngle) * sin(thirdAngle))); + break; + case YXY: + m00 = ((float) (cos(firstAngle) * cos(thirdAngle) - cos(secondAngle) * sin(firstAngle) * sin(thirdAngle))); + m01 = ((float) (sin(secondAngle) * sin(thirdAngle))); + m02 = ((float) (cos(thirdAngle) * sin(firstAngle) + cos(firstAngle) * cos(secondAngle) * sin(thirdAngle))); + m10 = ((float) (sin(firstAngle) * sin(secondAngle))); + m11 = ((float) (cos(secondAngle))); + m12 = ((float) (-(cos(firstAngle) * sin(secondAngle)))); + m20 = ((float) (-(cos(firstAngle) * sin(thirdAngle)) - cos(secondAngle) * cos(thirdAngle) * sin(firstAngle))); + m21 = ((float) (cos(thirdAngle) * sin(secondAngle))); + m22 = ((float) (cos(firstAngle) * cos(secondAngle) * cos(thirdAngle) - sin(firstAngle) * sin(thirdAngle))); + break; + case YZY: + m00 = ((float) (cos(firstAngle) * cos(secondAngle) * cos(thirdAngle) - sin(firstAngle) * sin(thirdAngle))); + m01 = ((float) (-(cos(thirdAngle) * sin(secondAngle)))); + m02 = ((float) (cos(firstAngle) * sin(thirdAngle) + cos(secondAngle) * cos(thirdAngle) * sin(firstAngle))); + m10 = ((float) (cos(firstAngle) * sin(secondAngle))); + m11 = ((float) (cos(secondAngle))); + m12 = ((float) (sin(firstAngle) * sin(secondAngle))); + m20 = ((float) (-(cos(firstAngle) * cos(secondAngle) * sin(thirdAngle)) - cos(thirdAngle) * sin(firstAngle))); + m21 = ((float) (sin(secondAngle) * sin(thirdAngle))); + m22 = ((float) (cos(firstAngle) * cos(thirdAngle) - cos(secondAngle) * sin(firstAngle) * sin(thirdAngle))); + break; + case ZYZ: + m00 = ((float) (cos(firstAngle) * cos(secondAngle) * cos(thirdAngle) - sin(firstAngle) * sin(thirdAngle))); + m01 = ((float) (-(cos(firstAngle) * sin(thirdAngle)) - cos(secondAngle) * cos(thirdAngle) * sin(firstAngle))); + m02 = ((float) (cos(thirdAngle) * sin(secondAngle))); + m10 = ((float) (cos(thirdAngle) * sin(firstAngle) + cos(firstAngle) * cos(secondAngle) * sin(thirdAngle))); + m11 = ((float) (cos(firstAngle) * cos(thirdAngle) - cos(secondAngle) * sin(firstAngle) * sin(thirdAngle))); + m12 = ((float) (sin(secondAngle) * sin(thirdAngle))); + m20 = ((float) (-(cos(firstAngle) * sin(secondAngle)))); + m21 = ((float) (sin(firstAngle) * sin(secondAngle))); + m22 = ((float) (cos(secondAngle))); + break; + case ZXZ: + m00 = ((float) (cos(firstAngle) * cos(thirdAngle) - cos(secondAngle) * sin(firstAngle) * sin(thirdAngle))); + m01 = ((float) (-(cos(firstAngle) * cos(secondAngle) * sin(thirdAngle)) - cos(thirdAngle) * sin(firstAngle))); + m02 = ((float) (sin(secondAngle) * sin(thirdAngle))); + m10 = ((float) (cos(firstAngle) * sin(thirdAngle) + cos(secondAngle) * cos(thirdAngle) * sin(firstAngle))); + m11 = ((float) (cos(firstAngle) * cos(secondAngle) * cos(thirdAngle) - sin(firstAngle) * sin(thirdAngle))); + m12 = ((float) (-(cos(thirdAngle) * sin(secondAngle)))); + m20 = ((float) (sin(firstAngle) * sin(secondAngle))); + m21 = ((float) (cos(firstAngle) * sin(secondAngle))); + m22 = ((float) (cos(secondAngle))); + break; + case XZY: + m00 = ((float) (cos(secondAngle) * cos(thirdAngle))); + m01 = ((float) (sin(firstAngle) * sin(thirdAngle) - cos(firstAngle) * cos(thirdAngle) * sin(secondAngle))); + m02 = ((float) (cos(firstAngle) * sin(thirdAngle) + cos(thirdAngle) * sin(firstAngle) * sin(secondAngle))); + m10 = ((float) (sin(secondAngle))); + m11 = ((float) (cos(firstAngle) * cos(secondAngle))); + m12 = ((float) (-(cos(secondAngle) * sin(firstAngle)))); + m20 = ((float) (-(cos(secondAngle) * sin(thirdAngle)))); + m21 = ((float) (cos(thirdAngle) * sin(firstAngle) + cos(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + m22 = ((float) (cos(firstAngle) * cos(thirdAngle) - sin(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + break; + case XYZ: + m00 = ((float) (cos(secondAngle) * cos(thirdAngle))); + m01 = ((float) (cos(thirdAngle) * sin(firstAngle) * sin(secondAngle) - cos(firstAngle) * sin(thirdAngle))); + m02 = ((float) (sin(firstAngle) * sin(thirdAngle) + cos(firstAngle) * cos(thirdAngle) * sin(secondAngle))); + m10 = ((float) (cos(secondAngle) * sin(thirdAngle))); + m11 = ((float) (cos(firstAngle) * cos(thirdAngle) + sin(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + m12 = ((float) (cos(firstAngle) * sin(secondAngle) * sin(thirdAngle) - cos(thirdAngle) * sin(firstAngle))); + m20 = ((float) (-sin(secondAngle))); + m21 = ((float) (cos(secondAngle) * sin(firstAngle))); + m22 = ((float) (cos(firstAngle) * cos(secondAngle))); + break; + case YXZ: + m00 = ((float) (cos(firstAngle) * cos(thirdAngle) - sin(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + m01 = ((float) (-(cos(secondAngle) * sin(thirdAngle)))); + m02 = ((float) (cos(thirdAngle) * sin(firstAngle) + cos(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + m10 = ((float) (cos(firstAngle) * sin(thirdAngle) + cos(thirdAngle) * sin(firstAngle) * sin(secondAngle))); + m11 = ((float) (cos(secondAngle) * cos(thirdAngle))); + m12 = ((float) (sin(firstAngle) * sin(thirdAngle) - cos(firstAngle) * cos(thirdAngle) * sin(secondAngle))); + m20 = ((float) (-(cos(secondAngle) * sin(firstAngle)))); + m21 = ((float) (sin(secondAngle))); + m22 = ((float) (cos(firstAngle) * cos(secondAngle))); + break; + case YZX: + m00 = ((float) (cos(firstAngle) * cos(secondAngle))); + m01 = ((float) (-sin(secondAngle))); + m02 = ((float) (cos(secondAngle) * sin(firstAngle))); + m10 = ((float) (sin(firstAngle) * sin(thirdAngle) + cos(firstAngle) * cos(thirdAngle) * sin(secondAngle))); + m11 = ((float) (cos(secondAngle) * cos(thirdAngle))); + m12 = ((float) (cos(thirdAngle) * sin(firstAngle) * sin(secondAngle) - cos(firstAngle) * sin(thirdAngle))); + m20 = ((float) (cos(firstAngle) * sin(secondAngle) * sin(thirdAngle) - cos(thirdAngle) * sin(firstAngle))); + m21 = ((float) (cos(secondAngle) * sin(thirdAngle))); + m22 = ((float) (cos(firstAngle) * cos(thirdAngle) + sin(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + break; + case ZYX: + m00 = ((float) (cos(firstAngle) * cos(secondAngle))); + m01 = ((float) (-(cos(secondAngle) * sin(firstAngle)))); + m02 = ((float) (sin(secondAngle))); + m10 = ((float) (cos(thirdAngle) * sin(firstAngle) + cos(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + m11 = ((float) (cos(firstAngle) * cos(thirdAngle) - sin(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + m12 = ((float) (-(cos(secondAngle) * sin(thirdAngle)))); + m20 = ((float) (sin(firstAngle) * sin(thirdAngle) - cos(firstAngle) * cos(thirdAngle) * sin(secondAngle))); + m21 = ((float) (cos(firstAngle) * sin(thirdAngle) + cos(thirdAngle) * sin(firstAngle) * sin(secondAngle))); + m22 = ((float) (cos(secondAngle) * cos(thirdAngle))); + break; + case ZXY: + m00 = ((float) (cos(firstAngle) * cos(thirdAngle) + sin(firstAngle) * sin(secondAngle) * sin(thirdAngle))); + m01 = ((float) (cos(firstAngle) * sin(secondAngle) * sin(thirdAngle) - cos(thirdAngle) * sin(firstAngle))); + m02 = ((float) (cos(secondAngle) * sin(thirdAngle))); + m10 = ((float) (cos(secondAngle) * sin(firstAngle))); + m11 = ((float) (cos(firstAngle) * cos(secondAngle))); + m12 = ((float) (-sin(secondAngle))); + m20 = ((float) (cos(thirdAngle) * sin(firstAngle) * sin(secondAngle) - cos(firstAngle) * sin(thirdAngle))); + m21 = ((float) (sin(firstAngle) * sin(thirdAngle) + cos(firstAngle) * cos(thirdAngle) * sin(secondAngle))); + m22 = ((float) (cos(secondAngle) * cos(thirdAngle))); + break; + } + + OpenGLMatrix result = new OpenGLMatrix(); + result.put(0, 0, m00); + result.put(0, 1, m01); + result.put(0, 2, m02); + result.put(1, 0, m10); + result.put(1, 1, m11); + result.put(1, 2, m12); + result.put(2, 0, m20); + result.put(2, 1, m21); + result.put(2, 2, m22); + return result; + } + + /** + * Given a rotation matrix, and an {@link AxesReference} and {@link AxesOrder}, returns an orientation + * that would produce that rotation matrix. + * + * @param rot the matrix whose orientation is to be determined + * @param axesReference whether wish an extrinsic or intrinsic reference for the axes + * @param axesOrder the order in which the axes are to be rotated + * @param unit the angle units in which the orientation is to be returned + * @return an orientation that will produce the given rotation matrix + * @see Orientation + * @see #getOrientation(MatrixF, AxesReference, AxesOrder, AngleUnit, AngleSet) + * @see Rotation Matrix + */ + public static Orientation getOrientation(MatrixF rot, AxesReference axesReference, AxesOrder axesOrder, AngleUnit unit) + { + /** + * Run both choices and return the one that uses smaller sets of angles. This is just a heuristic + * to choose which angle set is the most aesthetically pleasing. Both angle sets are equally valid. + */ + Orientation one = getOrientation(rot, axesReference, axesOrder, unit, AngleSet.THEONE); + Orientation theOther = getOrientation(rot, axesReference, axesOrder, unit, AngleSet.THEOTHER); + + VectorF vOne = new VectorF(one.firstAngle, one.secondAngle, one.thirdAngle); + VectorF vOther = new VectorF(theOther.firstAngle, theOther.secondAngle, theOther.thirdAngle); + + return vOne.magnitude() <= vOther.magnitude() ? one : theOther; + } + + /** + * {@link AngleSet} is used to distinguish between the two sets of angles that will produce + * a given rotation in a given axes reference and a given axes order + */ + public enum AngleSet { THEONE, THEOTHER }; + + /** + * Given a rotation matrix, and an {@link AxesReference} and {@link AxesOrder}, returns an orientation + * that would produce that rotation matrix. + * + * @param rot the matrix whose orientation is to be determined + * @param axesReference whether wish an extrinsic or intrinsic reference for the axes + * @param axesOrder the order in which the axes are to be rotated + * @param unit the angle units in which the orientation is to be returned + * @param angleSet which of the two sets angles which can produce the rotation matrix is desired + * @return an orientation that will produce the given rotation matrix + * @see #getRotationMatrix(AxesReference, AxesOrder, AngleUnit, float, float, float) + * @see Rotation Matrix + */ + public static Orientation getOrientation(MatrixF rot, AxesReference axesReference, AxesOrder axesOrder, AngleUnit unit, AngleSet angleSet) + { + float firstAngle, secondAngle, thirdAngle; + + if (axesReference == AxesReference.INTRINSIC) + { + return getOrientation(rot, axesReference.reverse(), axesOrder.reverse(), unit, angleSet).toAxesReference(axesReference); + } + + /** + * The extrinsic case takes some work. + * + * Implementation note: these computations contained in this 'switch' were derived automatically + * from symbolic Mathematica representations of the corresponding twelve forms of rotation + * matrix. The output of that automatic processing was literally copied and pasted from Mathematica + * into this Java source without intervention of human editing (save for reformatting of the code) + * thus significantly reducing the chance that errors might be inadvertently introduced. + * + * The cases labelled here as "arbitrary" are situations in which + * gimbal lock occurs. In those situations, + * the three angles are not uniquely specified by the rotation. Instead, only the sum or difference + * (as the case may be) of two of the axes is determined. In those situations, we make an + * arbitrary choice for the value of one of those two axes, then appropriately compute the other. + */ + float test; + switch (axesOrder) + { + default: + case XZX: + test = rot.get(0, 0); /* cos(secondAngle) */ + if (test == 1) + { + secondAngle = (float) 0; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(2, 1) == sin(firstAngle + thirdAngle) */ + /* rot.get(1, 1) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(2, 1), rot.get(1, 1)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) PI; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 2) == sin(firstAngle - thirdAngle) */ + /* rot.get(2, 2) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(1, 2), rot.get(2, 2))); + } + else + { + /* rot.get(0, 0) == cos(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? acos(rot.get(0, 0)) : -acos(rot.get(0, 0))); + /* rot.get(0, 2) == sin(firstAngle) * sin(secondAngle) */ + /* rot.get(0, 1) == -(cos(firstAngle) * sin(secondAngle)) */ + firstAngle = (float) atan2(rot.get(0, 2) / sin(secondAngle), -rot.get(0, 1) / sin(secondAngle)); + /* rot.get(2, 0) == sin(secondAngle) * sin(thirdAngle) */ + /* rot.get(1, 0) == cos(thirdAngle) * sin(secondAngle) */ + thirdAngle = (float) atan2(rot.get(2, 0) / sin(secondAngle), rot.get(1, 0) / sin(secondAngle)); + } + break; + case XYX: + test = rot.get(0, 0); /* cos(secondAngle) */ + if (test == 1) + { + secondAngle = (float) 0; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(2, 1) == sin(firstAngle + thirdAngle) */ + /* rot.get(1, 1) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(2, 1), rot.get(1, 1)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) PI; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 2) == -sin(firstAngle - thirdAngle) */ + /* rot.get(1, 1) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(-rot.get(1, 2), rot.get(1, 1))); + } + else + { + /* rot.get(0, 0) == cos(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? acos(rot.get(0, 0)) : -acos(rot.get(0, 0))); + /* rot.get(0, 1) == sin(firstAngle) * sin(secondAngle) */ + /* rot.get(0, 2) == cos(firstAngle) * sin(secondAngle) */ + firstAngle = (float) atan2(rot.get(0, 1) / sin(secondAngle), rot.get(0, 2) / sin(secondAngle)); + /* rot.get(1, 0) == sin(secondAngle) * sin(thirdAngle) */ + /* rot.get(2, 0) == -(cos(thirdAngle) * sin(secondAngle)) */ + thirdAngle = (float) atan2(rot.get(1, 0) / sin(secondAngle), -rot.get(2, 0) / sin(secondAngle)); + } + break; + case YXY: + test = rot.get(1, 1); /* cos(secondAngle) */ + if (test == 1) + { + secondAngle = (float) 0; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 2) == sin(firstAngle + thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(0, 2), rot.get(0, 0)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) PI; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 2) == sin(firstAngle - thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(0, 2), rot.get(0, 0))); + } + else + { + /* rot.get(1, 1) == cos(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? acos(rot.get(1, 1)) : -acos(rot.get(1, 1))); + /* rot.get(1, 0) == sin(firstAngle) * sin(secondAngle) */ + /* rot.get(1, 2) == -(cos(firstAngle) * sin(secondAngle)) */ + firstAngle = (float) atan2(rot.get(1, 0) / sin(secondAngle), -rot.get(1, 2) / sin(secondAngle)); + /* rot.get(0, 1) == sin(secondAngle) * sin(thirdAngle) */ + /* rot.get(2, 1) == cos(thirdAngle) * sin(secondAngle) */ + thirdAngle = (float) atan2(rot.get(0, 1) / sin(secondAngle), rot.get(2, 1) / sin(secondAngle)); + } + break; + case YZY: + test = rot.get(1, 1); /* cos(secondAngle) */ + if (test == 1) + { + secondAngle = (float) 0; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 2) == sin(firstAngle + thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(0, 2), rot.get(0, 0)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) PI; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 2) == -sin(firstAngle - thirdAngle) */ + /* rot.get(2, 2) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(-rot.get(0, 2), rot.get(2, 2))); + } + else + { + /* rot.get(1, 1) == cos(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? acos(rot.get(1, 1)) : -acos(rot.get(1, 1))); + /* rot.get(1, 2) == sin(firstAngle) * sin(secondAngle) */ + /* rot.get(1, 0) == cos(firstAngle) * sin(secondAngle) */ + firstAngle = (float) atan2(rot.get(1, 2) / sin(secondAngle), rot.get(1, 0) / sin(secondAngle)); + /* rot.get(2, 1) == sin(secondAngle) * sin(thirdAngle) */ + /* rot.get(0, 1) == -(cos(thirdAngle) * sin(secondAngle)) */ + thirdAngle = (float) atan2(rot.get(2, 1) / sin(secondAngle), -rot.get(0, 1) / sin(secondAngle)); + } + break; + case ZYZ: + test = rot.get(2, 2); /* cos(secondAngle) */ + if (test == 1) + { + secondAngle = (float) 0; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 0) == sin(firstAngle + thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(1, 0), rot.get(0, 0)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) PI; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 1) == sin(firstAngle - thirdAngle) */ + /* rot.get(1, 1) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(0, 1), rot.get(1, 1))); + } + else + { + /* rot.get(2, 2) == cos(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? acos(rot.get(2, 2)) : -acos(rot.get(2, 2))); + /* rot.get(2, 1) == sin(firstAngle) * sin(secondAngle) */ + /* rot.get(2, 0) == -(cos(firstAngle) * sin(secondAngle)) */ + firstAngle = (float) atan2(rot.get(2, 1) / sin(secondAngle), -rot.get(2, 0) / sin(secondAngle)); + /* rot.get(1, 2) == sin(secondAngle) * sin(thirdAngle) */ + /* rot.get(0, 2) == cos(thirdAngle) * sin(secondAngle) */ + thirdAngle = (float) atan2(rot.get(1, 2) / sin(secondAngle), rot.get(0, 2) / sin(secondAngle)); + } + break; + case ZXZ: + test = rot.get(2, 2); /* cos(secondAngle) */ + if (test == 1) + { + secondAngle = (float) 0; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 0) == sin(firstAngle + thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(1, 0), rot.get(0, 0)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) PI; + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 1) == -sin(firstAngle - thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(-rot.get(0, 1), rot.get(0, 0))); + } + else + { + /* rot.get(2, 2) == cos(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? acos(rot.get(2, 2)) : -acos(rot.get(2, 2))); + /* rot.get(2, 0) == sin(firstAngle) * sin(secondAngle) */ + /* rot.get(2, 1) == cos(firstAngle) * sin(secondAngle) */ + firstAngle = (float) atan2(rot.get(2, 0) / sin(secondAngle), rot.get(2, 1) / sin(secondAngle)); + /* rot.get(0, 2) == sin(secondAngle) * sin(thirdAngle) */ + /* rot.get(1, 2) == -(cos(thirdAngle) * sin(secondAngle)) */ + thirdAngle = (float) atan2(rot.get(0, 2) / sin(secondAngle), -rot.get(1, 2) / sin(secondAngle)); + } + break; + case XZY: + test = rot.get(1, 0); /* sin(secondAngle) */ + if (test == 1) + { + secondAngle = (float) (PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 2) == sin(firstAngle + thirdAngle) */ + /* rot.get(2, 2) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(0, 2), rot.get(2, 2)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) (-PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(2, 1) == sin(firstAngle - thirdAngle) */ + /* rot.get(0, 1) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(2, 1), rot.get(0, 1))); + } + else + { + /* rot.get(1, 0) == sin(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? asin(rot.get(1, 0)) : PI - asin(rot.get(1, 0))); + /* rot.get(1, 2) == -(cos(secondAngle) * sin(firstAngle)) */ + /* rot.get(1, 1) == cos(firstAngle) * cos(secondAngle) */ + firstAngle = (float) atan2(-rot.get(1, 2) / cos(secondAngle), rot.get(1, 1) / cos(secondAngle)); + /* rot.get(2, 0) == -(cos(secondAngle) * sin(thirdAngle)) */ + /* rot.get(0, 0) == cos(secondAngle) * cos(thirdAngle) */ + thirdAngle = (float) atan2(-rot.get(2, 0) / cos(secondAngle), rot.get(0, 0) / cos(secondAngle)); + } + break; + case XYZ: + test = rot.get(2, 0); /* -sin(secondAngle) */ + if (test == -1) + { + secondAngle = (float) (PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 1) == sin(firstAngle - thirdAngle) */ + /* rot.get(0, 2) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(0, 1), rot.get(0, 2))); + } + else if (test == 1) + { + secondAngle = (float) (-PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 1) == -sin(firstAngle + thirdAngle) */ + /* rot.get(1, 1) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(-rot.get(0, 1), rot.get(1, 1)) - firstAngle); + } + else + { + /* rot.get(2, 0) == -sin(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? -asin(rot.get(2, 0)) : PI + asin(rot.get(2, 0))); + /* rot.get(2, 1) == cos(secondAngle) * sin(firstAngle) */ + /* rot.get(2, 2) == cos(firstAngle) * cos(secondAngle) */ + firstAngle = (float) atan2(rot.get(2, 1) / cos(secondAngle), rot.get(2, 2) / cos(secondAngle)); + /* rot.get(1, 0) == cos(secondAngle) * sin(thirdAngle) */ + /* rot.get(0, 0) == cos(secondAngle) * cos(thirdAngle) */ + thirdAngle = (float) atan2(rot.get(1, 0) / cos(secondAngle), rot.get(0, 0) / cos(secondAngle)); + } + break; + case YXZ: + test = rot.get(2, 1); /* sin(secondAngle) */ + if (test == 1) + { + secondAngle = (float) (PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 2) == sin(firstAngle + thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(0, 2), rot.get(0, 0)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) (-PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 2) == sin(firstAngle - thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(0, 2), rot.get(0, 0))); + } + else + { + /* rot.get(2, 1) == sin(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? asin(rot.get(2, 1)) : PI - asin(rot.get(2, 1))); + /* rot.get(2, 0) == -(cos(secondAngle) * sin(firstAngle)) */ + /* rot.get(2, 2) == cos(firstAngle) * cos(secondAngle) */ + firstAngle = (float) atan2(-rot.get(2, 0) / cos(secondAngle), rot.get(2, 2) / cos(secondAngle)); + /* rot.get(0, 1) == -(cos(secondAngle) * sin(thirdAngle)) */ + /* rot.get(1, 1) == cos(secondAngle) * cos(thirdAngle) */ + thirdAngle = (float) atan2(-rot.get(0, 1) / cos(secondAngle), rot.get(1, 1) / cos(secondAngle)); + } + break; + case YZX: + test = rot.get(0, 1); /* -sin(secondAngle) */ + if (test == -1) + { + secondAngle = (float) (PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 2) == sin(firstAngle - thirdAngle) */ + /* rot.get(1, 0) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(1, 2), rot.get(1, 0))); + } + else if (test == 1) + { + secondAngle = (float) (-PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 2) == -sin(firstAngle + thirdAngle) */ + /* rot.get(2, 2) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(-rot.get(1, 2), rot.get(2, 2)) - firstAngle); + } + else + { + /* rot.get(0, 1) == -sin(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? -asin(rot.get(0, 1)) : PI + asin(rot.get(0, 1))); + /* rot.get(0, 2) == cos(secondAngle) * sin(firstAngle) */ + /* rot.get(0, 0) == cos(firstAngle) * cos(secondAngle) */ + firstAngle = (float) atan2(rot.get(0, 2) / cos(secondAngle), rot.get(0, 0) / cos(secondAngle)); + /* rot.get(2, 1) == cos(secondAngle) * sin(thirdAngle) */ + /* rot.get(1, 1) == cos(secondAngle) * cos(thirdAngle) */ + thirdAngle = (float) atan2(rot.get(2, 1) / cos(secondAngle), rot.get(1, 1) / cos(secondAngle)); + } + break; + case ZYX: + test = rot.get(0, 2); /* sin(secondAngle) */ + if (test == 1) + { + secondAngle = (float) (PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 0) == sin(firstAngle + thirdAngle) */ + /* rot.get(1, 1) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(rot.get(1, 0), rot.get(1, 1)) - firstAngle); + } + else if (test == -1) + { + secondAngle = (float) (-PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(1, 0) == sin(firstAngle - thirdAngle) */ + /* rot.get(1, 1) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(1, 0), rot.get(1, 1))); + } + else + { + /* rot.get(0, 2) == sin(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? asin(rot.get(0, 2)) : PI - asin(rot.get(0, 2))); + /* rot.get(0, 1) == -(cos(secondAngle) * sin(firstAngle)) */ + /* rot.get(0, 0) == cos(firstAngle) * cos(secondAngle) */ + firstAngle = (float) atan2(-rot.get(0, 1) / cos(secondAngle), rot.get(0, 0) / cos(secondAngle)); + /* rot.get(1, 2) == -(cos(secondAngle) * sin(thirdAngle)) */ + /* rot.get(2, 2) == cos(secondAngle) * cos(thirdAngle) */ + thirdAngle = (float) atan2(-rot.get(1, 2) / cos(secondAngle), rot.get(2, 2) / cos(secondAngle)); + } + break; + case ZXY: + test = rot.get(1, 2); /* -sin(secondAngle) */ + if (test == -1) + { + secondAngle = (float) (PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(2, 0) == sin(firstAngle - thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle - thirdAngle) */ + thirdAngle = (float) (firstAngle - atan2(rot.get(2, 0), rot.get(0, 0))); + } + else if (test == 1) + { + secondAngle = (float) (-PI / 2); + firstAngle = (float) 0; /* arbitrary */ + /* rot.get(0, 1) == -sin(firstAngle + thirdAngle) */ + /* rot.get(0, 0) == cos(firstAngle + thirdAngle) */ + thirdAngle = (float) (atan2(-rot.get(0, 1), rot.get(0, 0)) - firstAngle); + } + else + { + /* rot.get(1, 2) == -sin(secondAngle) */ + secondAngle = (float) (angleSet == AngleSet.THEONE ? -asin(rot.get(1, 2)) : PI + asin(rot.get(1, 2))); + /* rot.get(1, 0) == cos(secondAngle) * sin(firstAngle) */ + /* rot.get(1, 1) == cos(firstAngle) * cos(secondAngle) */ + firstAngle = (float) atan2(rot.get(1, 0) / cos(secondAngle), rot.get(1, 1) / cos(secondAngle)); + /* rot.get(0, 2) == cos(secondAngle) * sin(thirdAngle) */ + /* rot.get(2, 2) == cos(secondAngle) * cos(thirdAngle) */ + thirdAngle = (float) atan2(rot.get(0, 2) / cos(secondAngle), rot.get(2, 2) / cos(secondAngle)); + } + break; + } + + return new Orientation(axesReference, axesOrder, unit, + unit.fromRadians(firstAngle), unit.fromRadians(secondAngle), unit.fromRadians(thirdAngle), + 0); + } + +} \ No newline at end of file diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java new file mode 100644 index 00000000..9e68bedd --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Pose3D.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 Dryw Wade + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted (subject to the limitations in the disclaimer below) provided that + * the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * Neither the name of FIRST nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior + * written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS + * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.firstinspires.ftc.robotcore.external.navigation; + +import androidx.annotation.NonNull; + +import org.firstinspires.ftc.robotcore.external.navigation.Position; +import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; + +import java.util.Locale; + +/** + * Pose3D represents the position and orientation of an object in 3D space. + */ +public class Pose3D +{ + protected final Position position; + protected final YawPitchRollAngles orientation; + + public Pose3D(Position position, YawPitchRollAngles orientation) + { + this.position = position; + this.orientation = orientation; + } + + //---------------------------------------------------------------------------------------------- + // Formatting + //---------------------------------------------------------------------------------------------- + + @NonNull + @Override public String toString() + { + return String.format(Locale.getDefault(), + "position=%s, orientation=%s", + position.toString(), + orientation.toString()); + } + + /** + * A 3D orientation. + * + * The axis mapping is defined by the code that creates objects from this class. One should not assume that + * pitch, for example, is along the x axis. Consult the documentation of the API that returns + * a Pose3D for its axis mapping. + */ + public YawPitchRollAngles getOrientation() + { + return orientation; + } + + /** + * A 3D position. + */ + public Position getPosition() + { + return position; + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java new file mode 100644 index 00000000..6d69652a --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Position.java @@ -0,0 +1,101 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +import java.util.Locale; + +/** + * Instances of {@link Position} represent a three-dimensional distance in a particular distance unit. + * + * @see Acceleration + * @see Position + */ +public class Position +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + public DistanceUnit unit; + + public double x; + public double y; + public double z; + + /** + * the time on the System.nanoTime() clock at which the data was acquired. If no + * timestamp is associated with this particular set of data, this value is zero. + */ + public long acquisitionTime; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + public Position() + { + this(DistanceUnit.MM, 0, 0, 0, 0); + } + + public Position(DistanceUnit unit, double x, double y, double z, long acquisitionTime) + { + this.unit = unit; + this.x = x; + this.y = y; + this.z = z; + this.acquisitionTime = acquisitionTime; + } + + public Position toUnit(DistanceUnit distanceUnit) + { + if (distanceUnit != this.unit) + { + return new Position(distanceUnit, + distanceUnit.fromUnit(this.unit, x), + distanceUnit.fromUnit(this.unit, y), + distanceUnit.fromUnit(this.unit, z), + this.acquisitionTime); + } + else + return this; + } + + //---------------------------------------------------------------------------------------------- + // Formatting + //---------------------------------------------------------------------------------------------- + + @Override public String toString() + { + return String.format(Locale.getDefault(), "(%.3f %.3f %.3f)%s", x, y, z, unit.toString()); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java new file mode 100644 index 00000000..5679223c --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/UnnormalizedAngleUnit.java @@ -0,0 +1,197 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +/** + * An {@link UnnormalizedAngleUnit} represents angles in different units of measure and + * provides utility methods to convert across units. {@link UnnormalizedAngleUnit} does not + * maintain angle information internally, but only helps organize + * and use angle measures that may be maintained separately across various contexts. + *

+ * Angles can be distinguished along (at least) two axes: + * Normalized angles are of most utility when dealing with physical angles, as normalization + * removes ambiguity of representation. In particular, two angles can be compared for equality + * by subtracting them, normalizing, and testing whether the absolute value of the result is + * smaller than some tolerance threshold. This approach neatly handles all cases of cyclical + * wrapping without unexpected discontinuities. + *

+ *

+ * Unnormalized angles can be handy when the angular quantity is not a physical angle but some + * related quantity such as an angular velocity or acceleration, where the + * quantity in question lacks the 360-degree cyclical equivalence of a physical angle. + *

+ *

+ * {@link AngleUnit} expresses normalized angles, while {@link UnnormalizedAngleUnit} expresses unnormalized ones + *

+ */ +@SuppressWarnings("WeakerAccess") +public enum UnnormalizedAngleUnit +{ + DEGREES(0), RADIANS(1); + public final byte bVal; + + UnnormalizedAngleUnit(int i) + { + bVal = (byte) i; + } + + //---------------------------------------------------------------------------------------------- + // Primitive operations + //---------------------------------------------------------------------------------------------- + + public double fromDegrees(double degrees) + { + switch (this) + { + default: + case RADIANS: return (degrees / 180.0 * Math.PI); + case DEGREES: return (degrees); + } + } + + public float fromDegrees(float degrees) + { + switch (this) + { + default: + case RADIANS: return (degrees / 180.0f * AngleUnit.Pif); + case DEGREES: return (degrees); + } + } + + public double fromRadians(double radians) + { + switch (this) + { + default: + case RADIANS: return (radians); + case DEGREES: return (radians / Math.PI * 180.0); + } + } + + public float fromRadians(float radians) + { + switch (this) + { + default: + case RADIANS: return (radians); + case DEGREES: return (radians / AngleUnit.Pif * 180.0f); + } + } + + public double fromUnit(UnnormalizedAngleUnit them, double theirs) + { + switch (them) + { + default: + case RADIANS: return this.fromRadians(theirs); + case DEGREES: return this.fromDegrees(theirs); + } + } + + public float fromUnit(UnnormalizedAngleUnit them, float theirs) + { + switch (them) + { + default: + case RADIANS: return this.fromRadians(theirs); + case DEGREES: return this.fromDegrees(theirs); + } + } + + public double fromUnit(AngleUnit them, double theirs) { + return this.fromUnit(them.getUnnormalized(), theirs); + } + + public float fromUnit(AngleUnit them, float theirs) { + return this.fromUnit(them.getUnnormalized(), theirs); + } + + //---------------------------------------------------------------------------------------------- + // Derived operations + //---------------------------------------------------------------------------------------------- + + public double toDegrees(double inOurUnits) + { + switch (this) + { + default: + case RADIANS: return DEGREES.fromRadians(inOurUnits); + case DEGREES: return DEGREES.fromDegrees(inOurUnits); + } + } + + public float toDegrees(float inOurUnits) + { + switch (this) + { + default: + case RADIANS: return DEGREES.fromRadians(inOurUnits); + case DEGREES: return DEGREES.fromDegrees(inOurUnits); + } + } + + public double toRadians(double inOurUnits) + { + switch (this) + { + default: + case RADIANS: return RADIANS.fromRadians(inOurUnits); + case DEGREES: return RADIANS.fromDegrees(inOurUnits); + } + } + + public float toRadians(float inOurUnits) + { + switch (this) + { + default: + case RADIANS: return RADIANS.fromRadians(inOurUnits); + case DEGREES: return RADIANS.fromDegrees(inOurUnits); + } + } + + //---------------------------------------------------------------------------------------------- + // Normalization + //---------------------------------------------------------------------------------------------- + + public AngleUnit getNormalized() + { + switch (this) + { + default: + case RADIANS: return AngleUnit.RADIANS; + case DEGREES: return AngleUnit.DEGREES; + } + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java new file mode 100644 index 00000000..e8b0009b --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/Velocity.java @@ -0,0 +1,104 @@ +/* +Copyright (c) 2016 Robert Atkinson + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Robert Atkinson nor the names of his contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +import java.util.Locale; + +/** + * Instances of {@link Velocity} represent the derivative of {@link Position} over time. + * + * @see Position + * @see Acceleration + */ +public class Velocity +{ + //---------------------------------------------------------------------------------------------- + // State + //---------------------------------------------------------------------------------------------- + + /** + * The distance units in which this velocity is expressed. The time unit is always "per second". + */ + public DistanceUnit unit; + + public double xVeloc; + public double yVeloc; + public double zVeloc; + + /** + * the time on the System.nanoTime() clock at which the data was acquired. If no + * timestamp is associated with this particular set of data, this value is zero. + */ + public long acquisitionTime; + + //---------------------------------------------------------------------------------------------- + // Construction + //---------------------------------------------------------------------------------------------- + + public Velocity() + { + this(DistanceUnit.MM, 0, 0, 0, 0); + } + + public Velocity(DistanceUnit unit, double xVeloc, double yVeloc, double zVeloc, long acquisitionTime) + { + this.unit = unit; + this.xVeloc = xVeloc; + this.yVeloc = yVeloc; + this.zVeloc = zVeloc; + this.acquisitionTime = acquisitionTime; + } + + public Velocity toUnit(DistanceUnit distanceUnit) + { + if (distanceUnit != this.unit) + { + return new Velocity(distanceUnit, + distanceUnit.fromUnit(this.unit, xVeloc), + distanceUnit.fromUnit(this.unit, yVeloc), + distanceUnit.fromUnit(this.unit, zVeloc), + this.acquisitionTime); + } + else + return this; + } + + //---------------------------------------------------------------------------------------------- + // Formatting + //---------------------------------------------------------------------------------------------- + + @Override public String toString() + { + return String.format(Locale.getDefault(), "(%.3f %.3f %.3f)%s/s", xVeloc, yVeloc, zVeloc, unit.toString()); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java new file mode 100644 index 00000000..328362fa --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/navigation/YawPitchRollAngles.java @@ -0,0 +1,144 @@ +/* +Copyright (c) 2022 REV Robotics + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted (subject to the limitations in the disclaimer below) provided that +the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of REV Robotics nor the names of its contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS +LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.firstinspires.ftc.robotcore.external.navigation; + +import java.util.Locale; + +/** + * A simplified view of the orientation of an object in 3D space. + *

+ * Yaw is side-to-side lateral rotation, where the object remains flat, but turns left and right. + * Sometimes yaw is also referred to as "heading". + *

+ * Pitch is front-to-back rotation, where the front of the object moves upwards while the rear of the + * object moves downwards, or vice versa. + *

+ * Roll is side-to-side tilt, where the left side of the object moves upwards while the right side of + * the object moves downwards, or vice versa. + *

+ * All angles are in the range of -180 degrees to 180 degrees. + *

+ * The angles are applied intrinsically, in the order of yaw, then pitch, then roll. "Intrinsically" + * means that the axes move along with the object as you perform the rotations. As an example using + * a robot, if the yaw is 30 degrees, the pitch is 40 degrees, and the roll is 10 degrees, that means + * that you would reach the described orientation by first rotating the object 30 degrees counter-clockwise + * from the starting point, with all wheels continuing to touch the ground (rotation around the Z + * axis, as defined in the Robot Coordinate System). Then, you make your robot point 40 degrees upward + * (rotate it 40 degrees around the X axis, as defined in the Robot Coordinate System). Because the X + * axis moved with the robot, the pitch is not affected by the yaw value. Then from that position, the + * robot is tilted 10 degrees to the right, around the newly positioned Y axis, to produce the actual + * position of the robot. + */ +public class YawPitchRollAngles { + private final AngleUnit angleUnit; + private final double yaw; + private final double pitch; + private final double roll; + private final long acquisitionTime; + + /** + * See the top-level class Javadoc for the format that these angles need to be in. + */ + public YawPitchRollAngles(AngleUnit angleUnit, double yaw, double pitch, double roll, long acquisitionTime) { + this.angleUnit = angleUnit; + this.yaw = yaw; + this.pitch = pitch; + this.roll = roll; + this.acquisitionTime = acquisitionTime; + } + + /** + * @return The side-to-side lateral rotation of the object, + * normalized to the range of [-180,+180) degrees. + */ + public double getYaw() { + return yaw; + } + + /** + * @param angleUnit The unit that will be used for the result. + * @return The side-to-side lateral rotation of the object, + * normalized to the range of [-180,+180) degrees. + */ + public double getYaw(AngleUnit angleUnit) { + return angleUnit.fromUnit(this.angleUnit, yaw); + } + + /** + * @return The front-to-back rotation of the object, normalized to + * the range of [-180,+180) degrees + */ + public double getPitch() { + return pitch; + } + + /** + * @param angleUnit The unit that will be used for the result. + * @return The front-to-back rotation of the object, normalized to + * the range of [-180,+180) degrees + */ + public double getPitch(AngleUnit angleUnit) { + return angleUnit.fromUnit(this.angleUnit, pitch); + } + + /** + * @return The side-to-side tilt of the object, normalized to + * the range of [-180,+180) degrees + */ + public double getRoll() { + return roll; + } + + /** + * @param angleUnit The unit that will be used for the result. + * @return The side-to-side tilt of the object, normalized to + * the range of [-180,+180) degrees + */ + public double getRoll(AngleUnit angleUnit) { + return angleUnit.fromUnit(this.angleUnit, roll); + } + + /** + * @return The time on the System.nanoTime() clock at which the data was acquired. If no + * timestamp is associated with this particular set of data, this value is zero. + */ + public long getAcquisitionTime() { + return acquisitionTime; + } + + @Override + public String toString() { + return String.format(Locale.US, "{yaw=%.3f, pitch=%.3f, roll=%.3f}", + angleUnit.toDegrees(yaw), angleUnit.toDegrees(pitch), angleUnit.toDegrees(roll)); + } +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java index 9e85951b..a2180236 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/internal/camera/calibration/CameraCalibration.java @@ -55,6 +55,7 @@ public class CameraCalibration extends CameraIntrinsics implements Cloneable protected Size size; protected boolean remove; protected final boolean isFake; + public Size resolutionScaledFrom; @Override public String toString() { @@ -148,6 +149,7 @@ public CameraCalibration scaledTo(Size newSize) result.focalLengthY *= factor; result.principalPointX *= factor; result.principalPointY *= factor; + result.resolutionScaledFrom = size; return result; } @@ -169,4 +171,4 @@ protected static double getAspectRatio(Size size) return (double)size.getWidth() / (double)size.getHeight(); } -} \ No newline at end of file +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt index 9870d02a..d76ec1d1 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/util/ClasspathScan.kt @@ -50,6 +50,7 @@ class ClasspathScan { "io.github.classgraph", "io.github.deltacv", "com.github.serivesmejia.eocvsim.pipeline", + "org.firstinspires.ftc.vision", "org.lwjgl", "org.apache", "org.codehaus", diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt index 48037d28..2c45e07a 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/DefaultWorkspaceTemplate.kt @@ -40,7 +40,7 @@ object DefaultWorkspaceTemplate : WorkspaceTemplate() { if(!folder.isDirectory) return false val templateZipFile = SysUtil.copyFileIsTemp( - templateZipResource, "default_workspace.zip", false + templateZipResource, "default_workspace.zip", true ).file return try { diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt index bcf86329..5fc99be7 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/workspace/util/template/GradleWorkspaceTemplate.kt @@ -43,7 +43,7 @@ object GradleWorkspaceTemplate : WorkspaceTemplate() { if(!folder.isDirectory) return false val templateZipFile = SysUtil.copyFileIsTemp( - templateZipResource, "gradle_workspace.zip", false + templateZipResource, "gradle_workspace.zip", true ).file return try { diff --git a/EOCV-Sim/src/main/resources/templates/default_workspace.zip b/EOCV-Sim/src/main/resources/templates/default_workspace.zip index 2afb2646e454bea19b0d96e49fb6c7fb0c9ef4c2..f7e41a86ca994c71cd7d36f9bb6dde905c3bc42f 100644 GIT binary patch delta 4398 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 K(5Bnf?tcJ#CS_^> delta 157 zcmezKgt7B5Sn)xpg4wRBkJX3e0N+Q6P=4@?t?u&-`i-bu51}h>9-g2T_Xt5|dd9L%|}29U`o3 Q3_!pSgx`Ry+9(hY028n-cmMzZ diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java new file mode 100644 index 00000000..e0f65f3a --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java @@ -0,0 +1,240 @@ +/* Copyright (c) 2024 Dryw Wade. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted (subject to the limitations in the disclaimer below) provided that + * the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * Neither the name of FIRST nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS + * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.firstinspires.ftc.robotcontroller.external.samples; + +import com.qualcomm.robotcore.eventloop.opmode.Disabled; +import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; + +import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; +import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; +import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; +import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; +import org.firstinspires.ftc.robotcore.external.navigation.Position; +import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; +import org.firstinspires.ftc.vision.VisionPortal; +import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; +import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; + +import java.util.List; + +/* + * This OpMode illustrates the basics of AprilTag based localization. + * + * For an introduction to AprilTags, see the FTC-DOCS link below: + * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html + * + * In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default + * "TagLibrary" will be used to compute the robot's location and orientation. This default TagLibrary contains + * the current Season's AprilTags and a small set of "test Tags" in the high number range. + * + * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the robot, relative to the field origin. + * This information is provided in the "robotPose" member of the returned "detection". + * + * To learn about the Field Coordinate System that is defined for FTC (and used by this OpMode), see the FTC-DOCS link below: + * https://ftc-docs.firstinspires.org/en/latest/game_specific_resources/field_coordinate_system/field-coordinate-system.html + * + * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. + * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. + */ +@TeleOp(name = "Concept: AprilTag Localization", group = "Concept") +@Disabled +public class ConceptAprilTagLocalization extends LinearOpMode { + + private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera + + /** + * Variables to store the position and orientation of the camera on the robot. Setting these + * values requires a definition of the axes of the camera and robot: + * + * Camera axes: + * Origin location: Center of the lens + * Axes orientation: +x right, +y down, +z forward (from camera's perspective) + * + * Robot axes (this is typical, but you can define this however you want): + * Origin location: Center of the robot at field height + * Axes orientation: +x right, +y forward, +z upward + * + * Position: + * If all values are zero (no translation), that implies the camera is at the center of the + * robot. Suppose your camera is positioned 5 inches to the left, 7 inches forward, and 12 + * inches above the ground - you would need to set the position to (-5, 7, 12). + * + * Orientation: + * If all values are zero (no rotation), that implies the camera is pointing straight up. In + * most cases, you'll need to set the pitch to -90 degrees (rotation about the x-axis), meaning + * the camera is horizontal. Use a yaw of 0 if the camera is pointing forwards, +90 degrees if + * it's pointing straight left, -90 degrees for straight right, etc. You can also set the roll + * to +/-90 degrees if it's vertical, or 180 degrees if it's upside-down. + */ + private Position cameraPosition = new Position(DistanceUnit.INCH, + 0, 0, 0, 0); + private YawPitchRollAngles cameraOrientation = new YawPitchRollAngles(AngleUnit.DEGREES, + 0, -90, 0, 0); + + /** + * The variable to store our instance of the AprilTag processor. + */ + private AprilTagProcessor aprilTag; + + /** + * The variable to store our instance of the vision portal. + */ + private VisionPortal visionPortal; + + @Override + public void runOpMode() { + + initAprilTag(); + + // Wait for the DS start button to be touched. + telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); + telemetry.addData(">", "Touch START to start OpMode"); + telemetry.update(); + waitForStart(); + + while (opModeIsActive()) { + + telemetryAprilTag(); + + // Push telemetry to the Driver Station. + telemetry.update(); + + // Share the CPU. + sleep(20); + } + + // Save more CPU resources when camera is no longer needed. + visionPortal.close(); + + } // end method runOpMode() + + /** + * Initialize the AprilTag processor. + */ + private void initAprilTag() { + + // Create the AprilTag processor. + aprilTag = new AprilTagProcessor.Builder() + + // The following default settings are available to un-comment and edit as needed. + //.setDrawAxes(false) + //.setDrawCubeProjection(false) + //.setDrawTagOutline(true) + //.setTagFamily(AprilTagProcessor.TagFamily.TAG_36h11) + //.setTagLibrary(AprilTagGameDatabase.getCenterStageTagLibrary()) + //.setOutputUnits(DistanceUnit.INCH, AngleUnit.DEGREES) + .setCameraPose(cameraPosition, cameraOrientation) + + // == CAMERA CALIBRATION == + // If you do not manually specify calibration parameters, the SDK will attempt + // to load a predefined calibration for your camera. + //.setLensIntrinsics(578.272, 578.272, 402.145, 221.506) + // ... these parameters are fx, fy, cx, cy. + + .build(); + + // Adjust Image Decimation to trade-off detection-range for detection-rate. + // eg: Some typical detection data using a Logitech C920 WebCam + // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second + // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second + // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second (default) + // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second (default) + // Note: Decimation can be changed on-the-fly to adapt during a match. + //aprilTag.setDecimation(3); + + // Create the vision portal by using a builder. + VisionPortal.Builder builder = new VisionPortal.Builder(); + + // Set the camera (webcam vs. built-in RC phone camera). + if (USE_WEBCAM) { + builder.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")); + } else { + builder.setCamera(BuiltinCameraDirection.BACK); + } + + // Choose a camera resolution. Not all cameras support all resolutions. + //builder.setCameraResolution(new Size(640, 480)); + + // Enable the RC preview (LiveView). Set "false" to omit camera monitoring. + builder.enableLiveView(true); + + // Set the stream format; MJPEG uses less bandwidth than default YUY2. + //builder.setStreamFormat(VisionPortal.StreamFormat.YUY2); + + // Choose whether or not LiveView stops if no processors are enabled. + // If set "true", monitor shows solid orange screen if no processors enabled. + // If set "false", monitor shows camera view without annotations. + //builder.setAutoStopLiveView(false); + + // Set and enable the processor. + builder.addProcessor(aprilTag); + + // Build the Vision Portal, using the above settings. + visionPortal = builder.build(); + + // Disable or re-enable the aprilTag processor at any time. + //visionPortal.setProcessorEnabled(aprilTag, true); + + } // end method initAprilTag() + + /** + * Add telemetry about AprilTag detections. + */ + private void telemetryAprilTag() { + + List currentDetections = aprilTag.getDetections(); + telemetry.addData("# AprilTags Detected", currentDetections.size()); + + // Step through the list of detections and display info for each one. + for (AprilTagDetection detection : currentDetections) { + if (detection.metadata != null) { + telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); + telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", + detection.robotPose.getPosition().x, + detection.robotPose.getPosition().y, + detection.robotPose.getPosition().z)); + telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", + detection.robotPose.getOrientation().getPitch(AngleUnit.DEGREES), + detection.robotPose.getOrientation().getRoll(AngleUnit.DEGREES), + detection.robotPose.getOrientation().getYaw(AngleUnit.DEGREES))); + } else { + telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); + telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); + } + } // end for() loop + + // Add "key" information to telemetry + telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); + telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); + + } // end method telemetryAprilTag() + +} // end class \ No newline at end of file diff --git a/Vision/src/main/java/android/graphics/FontCache.java b/Vision/src/main/java/android/graphics/FontCache.java index 7b238e98..71bd9256 100644 --- a/Vision/src/main/java/android/graphics/FontCache.java +++ b/Vision/src/main/java/android/graphics/FontCache.java @@ -34,15 +34,15 @@ class FontCache { private static HashMap> cache = new HashMap<>(); - static Font makeFont(Typeface theTypeface, float textSize) { - if(!cache.containsKey(theTypeface)) { - cache.put(theTypeface, new HashMap<>()); + static Font makeFont(Typeface typeface, float textSize) { + if(!cache.containsKey(typeface)) { + cache.put(typeface, new HashMap<>()); } - HashMap sizeCache = cache.get(theTypeface); + HashMap sizeCache = cache.get(typeface); if(!sizeCache.containsKey((int) (textSize * 1000))) { - sizeCache.put((int) (textSize * 1000), new Font(theTypeface.theTypeface, textSize)); + sizeCache.put((int) (textSize * 1000), new Font(typeface.theTypeface, textSize)); } return sizeCache.get((int) (textSize * 1000)); diff --git a/Vision/src/main/java/android/graphics/TemporaryBuffer.java b/Vision/src/main/java/android/graphics/TemporaryBuffer.java new file mode 100644 index 00000000..b98e3d9b --- /dev/null +++ b/Vision/src/main/java/android/graphics/TemporaryBuffer.java @@ -0,0 +1,27 @@ +package android.graphics; + +import com.android.internal.util.ArrayUtils; + +/** + * @hide + */ +public class TemporaryBuffer { + public static char[] obtain(int len) { + char[] buf; + synchronized (TemporaryBuffer.class) { + buf = sTemp; + sTemp = null; + } + if (buf == null || buf.length < len) { + buf = new char[ArrayUtils.idealCharArraySize(len)]; + } + return buf; + } + public static void recycle(char[] temp) { + if (temp.length > 1000) return; + synchronized (TemporaryBuffer.class) { + sTemp = temp; + } + } + private static char[] sTemp = null; +} \ No newline at end of file diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java index 484aca1e..8e159b15 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagCanvasAnnotator.java @@ -36,8 +36,8 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; - import android.graphics.Typeface; + import org.opencv.calib3d.Calib3d; import org.opencv.core.Mat; import org.opencv.core.MatOfDouble; @@ -117,9 +117,9 @@ void drawAxisMarker(AprilTagDetection detection, Canvas canvas, double tagsize) // The origin of the coordinate space is assumed to be in the center of the detection. MatOfPoint3f axis = new MatOfPoint3f( new Point3(0,0,0), - new Point3(-axisLength,0,0), - new Point3(0,-axisLength,0), - new Point3(0,0,-axisLength) + new Point3(axisLength,0,0), + new Point3(0,axisLength,0), + new Point3(0,0,axisLength) ); // Project those points onto the image @@ -267,7 +267,11 @@ void drawTagID(AprilTagDetection detection, Canvas canvas) { float cornerRound = 5 * canvasDensityScale; - float tag_id_width = 120*canvasDensityScale; + String text = String.format("ID %d", detection.id); + + // Implementing measureText is a bit of a pain, so we'll just leave it out for now ... + // (EOCV-Sim doesn't support measureText) + float tag_id_width = /*textPaint.measureText(text) +*/ (text.length() * textPaint.getTextSize() * 0.5f)+ 20*canvasDensityScale; float tag_id_height = 50*canvasDensityScale; float id_x = (float) detection.center.x * bmpPxToCanvasPx - tag_id_width/2; @@ -283,7 +287,7 @@ void drawTagID(AprilTagDetection detection, Canvas canvas) canvas.rotate((float) Math.toDegrees(Math.atan2(lowerRight.y - lowerLeft.y, lowerRight.x-lowerLeft.x)), (float) detection.center.x*bmpPxToCanvasPx, (float) detection.center.y*bmpPxToCanvasPx); canvas.drawRoundRect(id_x, id_y, id_x+tag_id_width, id_y+tag_id_height, cornerRound, cornerRound, rectPaint); - canvas.drawText(String.format("ID %02d", detection.id), tag_id_text_x, tag_id_text_y, textPaint); + canvas.drawText(text, tag_id_text_x, tag_id_text_y, textPaint); canvas.restore(); } diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java index 251fe4ee..3f0bbb8c 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagDetection.java @@ -33,6 +33,7 @@ package org.firstinspires.ftc.vision.apriltag; +import org.firstinspires.ftc.robotcore.external.navigation.Pose3D; import org.opencv.core.Point; public class AprilTagDetection @@ -78,13 +79,18 @@ public class AprilTagDetection */ public final AprilTagPoseRaw rawPose; + /* + * Robot pose data returned by the pose solver + */ + public final Pose3D robotPose; + /* * Timestamp of when the image in which this detection was found was acquired */ public final long frameAcquisitionNanoTime; public AprilTagDetection(int id, int hamming, float decisionMargin, Point center, Point[] corners, - AprilTagMetadata metadata, AprilTagPoseFtc ftcPose, AprilTagPoseRaw rawPose, long frameAcquisitionNanoTime) + AprilTagMetadata metadata, AprilTagPoseFtc ftcPose, AprilTagPoseRaw rawPose, Pose3D robotPose, long frameAcquisitionNanoTime) { this.id = id; this.hamming = hamming; @@ -94,6 +100,7 @@ public AprilTagDetection(int id, int hamming, float decisionMargin, Point center this.metadata = metadata; this.ftcPose = ftcPose; this.rawPose = rawPose; + this.robotPose = robotPose; this.frameAcquisitionNanoTime = frameAcquisitionNanoTime; } -} \ No newline at end of file +} diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java index a7a0737f..9ed97a89 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessor.java @@ -33,8 +33,14 @@ package org.firstinspires.ftc.vision.apriltag; +import org.firstinspires.ftc.robotcore.external.matrices.OpenGLMatrix; import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; +import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; +import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; +import org.firstinspires.ftc.robotcore.external.navigation.Orientation; +import org.firstinspires.ftc.robotcore.external.navigation.Position; +import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; import org.firstinspires.ftc.vision.VisionProcessor; import org.opencv.calib3d.Calib3d; import org.openftc.apriltag.AprilTagDetectorJNI; @@ -67,18 +73,35 @@ public static AprilTagProcessor easyCreateWithDefaults() public static class Builder { + private Position cameraPosition = new Position(); + private YawPitchRollAngles cameraOrientation = new YawPitchRollAngles(AngleUnit.DEGREES, 0, 0, 0, 0); private double fx, fy, cx, cy; private TagFamily tagFamily = TagFamily.TAG_36h11; private AprilTagLibrary tagLibrary = AprilTagGameDatabase.getCurrentGameTagLibrary(); private DistanceUnit outputUnitsLength = DistanceUnit.INCH; private AngleUnit outputUnitsAngle = AngleUnit.DEGREES; private int threads = THREADS_DEFAULT; + private boolean suppressCalibrationWarnings; private boolean drawAxes = false; private boolean drawCube = false; private boolean drawOutline = true; private boolean drawTagId = true; + /** + * Set the camera pose relative to the robot origin. + * @param position Position of camera relative to the robot origin + * @param orientation Orientation of camera relative to the robot origin + * @return the {@link Builder} object, to allow for method chaining + */ + public Builder setCameraPose(Position position, YawPitchRollAngles orientation) + { + cameraPosition = position; + cameraOrientation = orientation; + + return this; + } + /** * Set the camera calibration parameters (needed for accurate 6DOF pose unless the * SDK has a built in calibration for your camera) @@ -97,6 +120,17 @@ public Builder setLensIntrinsics(double fx, double fy, double cx, double cy) return this; } + /** + * Set whether any warnings about camera calibration should be suppressed + * @param suppressCalibrationWarnings whether to suppress calibration warnings + * @return the {@link Builder} object, to allow for method chaining + */ + public Builder setSuppressCalibrationWarnings(boolean suppressCalibrationWarnings) + { + this.suppressCalibrationWarnings = suppressCalibrationWarnings; + return this; + } + /** * Set the tag family this detector will be used to detect (it can only be used * for one tag family at a time) @@ -193,8 +227,8 @@ public Builder setNumThreads(int threads) /** * Create a {@link VisionProcessor} object which may be attached to - * a {link org.firstinspires.ftc.vision.VisionPortal} using - * {link org.firstinspires.ftc.vision.VisionPortal.Builder#addProcessor(VisionProcessor)} + * a {@link org.firstinspires.ftc.vision.VisionPortal} using + * {@link org.firstinspires.ftc.vision.VisionPortal.Builder#addProcessor(VisionProcessor)} * @return a {@link VisionProcessor} object */ public AprilTagProcessor build() @@ -209,11 +243,27 @@ public AprilTagProcessor build() throw new RuntimeException("Cannot create AprilTagProcessor without setting tag family!"); } + OpenGLMatrix cameraRotationMatrix = new Orientation( + AxesReference.INTRINSIC, AxesOrder.ZXZ, AngleUnit.DEGREES, + (float) cameraOrientation.getYaw(AngleUnit.DEGREES), + (float) cameraOrientation.getPitch(AngleUnit.DEGREES), + (float) cameraOrientation.getRoll(AngleUnit.DEGREES), + cameraOrientation.getAcquisitionTime()) + .getRotationMatrix(); + + OpenGLMatrix robotInCameraFrame = OpenGLMatrix.identityMatrix() + .translated( + (float) cameraPosition.toUnit(DistanceUnit.INCH).x, + (float) cameraPosition.toUnit(DistanceUnit.INCH).y, + (float) cameraPosition.toUnit(DistanceUnit.INCH).z) + .multiplied(cameraRotationMatrix) + .inverted(); + return new AprilTagProcessorImpl( - fx, fy, cx, cy, + robotInCameraFrame, fx, fy, cx, cy, outputUnitsLength, outputUnitsAngle, tagLibrary, drawAxes, drawCube, drawOutline, drawTagId, - tagFamily, threads + tagFamily, threads, suppressCalibrationWarnings ); } } diff --git a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java index ccd58a8f..023284ed 100644 --- a/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java +++ b/Vision/src/main/java/org/firstinspires/ftc/vision/apriltag/AprilTagProcessorImpl.java @@ -35,16 +35,20 @@ import android.graphics.Canvas; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; import com.qualcomm.robotcore.util.MovingStatistics; import com.qualcomm.robotcore.util.RobotLog; import org.firstinspires.ftc.robotcore.external.matrices.GeneralMatrixF; +import org.firstinspires.ftc.robotcore.external.matrices.OpenGLMatrix; +import org.firstinspires.ftc.robotcore.external.matrices.VectorF; import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; +import org.firstinspires.ftc.robotcore.external.navigation.Pose3D; import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; import org.firstinspires.ftc.robotcore.external.navigation.Orientation; +import org.firstinspires.ftc.robotcore.external.navigation.Position; +import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; import org.firstinspires.ftc.robotcore.internal.camera.calibration.CameraCalibration; import org.opencv.calib3d.Calib3d; import org.opencv.core.CvType; @@ -57,16 +61,16 @@ import org.opencv.imgproc.Imgproc; import org.openftc.apriltag.AprilTagDetectorJNI; import org.openftc.apriltag.ApriltagDetectionJNI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.logging.Logger; -@Disabled public class AprilTagProcessorImpl extends AprilTagProcessor { public static final String TAG = "AprilTagProcessorImpl"; - private Logger logger = Logger.getLogger(TAG); + Logger logger = LoggerFactory.getLogger(TAG); private long nativeApriltagPtr; private Mat grey = new Mat(); @@ -85,6 +89,7 @@ public class AprilTagProcessorImpl extends AprilTagProcessor private double fy; private double cx; private double cy; + private final boolean suppressCalibrationWarnings; private final AprilTagLibrary tagLibrary; @@ -97,10 +102,15 @@ public class AprilTagProcessorImpl extends AprilTagProcessor private final DistanceUnit outputUnitsLength; private final AngleUnit outputUnitsAngle; - private volatile PoseSolver poseSolver = PoseSolver.OPENCV_ITERATIVE; + private volatile PoseSolver poseSolver = PoseSolver.APRILTAG_BUILTIN; - public AprilTagProcessorImpl(double fx, double fy, double cx, double cy, DistanceUnit outputUnitsLength, AngleUnit outputUnitsAngle, AprilTagLibrary tagLibrary, boolean drawAxes, boolean drawCube, boolean drawOutline, boolean drawTagID, TagFamily tagFamily, int threads) + private OpenGLMatrix robotInCameraFrame; + + public AprilTagProcessorImpl(OpenGLMatrix robotInCameraFrame, double fx, double fy, double cx, double cy, DistanceUnit outputUnitsLength, AngleUnit outputUnitsAngle, AprilTagLibrary tagLibrary, + boolean drawAxes, boolean drawCube, boolean drawOutline, boolean drawTagID, TagFamily tagFamily, int threads, boolean suppressCalibrationWarnings) { + this.robotInCameraFrame = robotInCameraFrame; + this.fx = fx; this.fy = fy; this.cx = cx; @@ -109,6 +119,7 @@ public AprilTagProcessorImpl(double fx, double fy, double cx, double cy, Distanc this.tagLibrary = tagLibrary; this.outputUnitsLength = outputUnitsLength; this.outputUnitsAngle = outputUnitsAngle; + this.suppressCalibrationWarnings = suppressCalibrationWarnings; this.drawAxes = drawAxes; this.drawCube = drawCube; this.drawOutline = drawOutline; @@ -137,39 +148,84 @@ protected void finalize() @Override public void init(int width, int height, CameraCalibration calibration) { - // If the user didn't give us a calibration, but we have one built in, - // then go ahead and use it!! - if (calibration != null && fx == 0 && fy == 0 && cx == 0 && cy == 0 - && !(calibration.focalLengthX == 0 && calibration.focalLengthY == 0 && calibration.principalPointX == 0 && calibration.principalPointY == 0)) // needed because we may get an all zero calibration to indicate none, instead of null + // ATTEMPT 1 - If the user provided their own calibration, use that + if (fx != 0 && fy != 0 && cx != 0 && cy != 0) + { + logger.debug(String.format("User provided their own camera calibration fx=%7.3f fy=%7.3f cx=%7.3f cy=%7.3f", + fx, fy, cx, cy)); + } + + // ATTEMPT 2 - If we have valid calibration we can use, use it + else if (calibration != null && !calibration.isDegenerate()) // needed because we may get an all zero calibration to indicate none, instead of null { fx = calibration.focalLengthX; fy = calibration.focalLengthY; cx = calibration.principalPointX; cy = calibration.principalPointY; - logger.info(String.format("User did not provide a camera calibration; but we DO have a built in calibration we can use.\n [%dx%d] (may be scaled) %s\nfx=%7.3f fy=%7.3f cx=%7.3f cy=%7.3f", - calibration.getSize().getWidth(), calibration.getSize().getHeight(), calibration.getIdentity().toString(), fx, fy, cx, cy)); + // Note that this might have been a scaled calibration - inform the user if so + if (calibration.resolutionScaledFrom != null) + { + String msg = String.format("Camera has not been calibrated for [%dx%d]; applying a scaled calibration from [%dx%d].", width, height, calibration.resolutionScaledFrom.getWidth(), calibration.resolutionScaledFrom.getHeight()); + + if (!suppressCalibrationWarnings) + { + logger.warn(msg); + } + } + // Nope, it was a full up proper calibration - no need to pester the user about anything + else + { + logger.debug(String.format("User did not provide a camera calibration; but we DO have a built in calibration we can use.\n [%dx%d] (NOT scaled) %s\nfx=%7.3f fy=%7.3f cx=%7.3f cy=%7.3f", + calibration.getSize().getWidth(), calibration.getSize().getHeight(), calibration.getIdentity().toString(), fx, fy, cx, cy)); + } } - else if (fx == 0 && fy == 0 && cx == 0 && cy == 0) + + // Okay, we aren't going to have any calibration data we can use, but there are 2 cases to check + else { - // set it to *something* so we don't crash the native code + // NO-OP, we cannot implement this for EOCV-Sim in the same way as the FTC SDK + + /* + // If we have a calibration on file, but with a wrong aspect ratio, + // we can't use it, but hey at least we can let the user know about it. + if (calibration instanceof PlaceholderCalibratedAspectRatioMismatch) + { + StringBuilder supportedResBuilder = new StringBuilder(); + + for (CameraCalibration cal : CameraCalibrationHelper.getInstance().getCalibrations(calibration.getIdentity())) + { + supportedResBuilder.append(String.format("[%dx%d],", cal.getSize().getWidth(), cal.getSize().getHeight())); + } - String warning = "User did not provide a camera calibration, nor was a built-in calibration found for this camera; 6DOF pose data will likely be inaccurate."; - logger.warning(warning); + String msg = String.format("Camera has not been calibrated for [%dx%d]. Pose estimates will likely be inaccurate. However, there are built in calibrations for resolutions: %s", + width, height, supportedResBuilder.toString()); + if (!suppressCalibrationWarnings) + { + logger.warn(msg); + } + + + // Nah, we got absolutely nothing + else*/ + { + String warning = "User did not provide a camera calibration, nor was a built-in calibration found for this camera. Pose estimates will likely be inaccurate."; + + if (!suppressCalibrationWarnings) + { + logger.warn(warning); + } + } + + // IN EITHER CASE, set it to *something* so we don't crash the native code fx = 578.272; fy = 578.272; cx = width/2; cy = height/2; } - else - { - logger.info(String.format("User provided their own camera calibration fx=%7.3f fy=%7.3f cx=%7.3f cy=%7.3f", - fx, fy, cx, cy)); - } constructMatrix(); - canvasAnnotator = new AprilTagCanvasAnnotator(cameraMatrix); } @@ -225,6 +281,7 @@ ArrayList runAprilTagDetectorForMultipleTagSizes(long capture AprilTagPoseRaw rawPose; AprilTagPoseFtc ftcPose; + Pose3D robotPose; if (metadata != null) { @@ -294,10 +351,13 @@ ArrayList runAprilTagDetectorForMultipleTagSizes(long capture Math.hypot(rawPose.x, rawPose.z), // range outputUnitsAngle.fromUnit(AngleUnit.RADIANS, Math.atan2(-rawPose.x, rawPose.z)), // bearing outputUnitsAngle.fromUnit(AngleUnit.RADIANS, Math.atan2(-rawPose.y, rawPose.z))); // elevation + + robotPose = computeRobotPose(rawPose, metadata, captureTimeNanos); } else { ftcPose = null; + robotPose = null; } double[] center = ApriltagDetectionJNI.getCenterpoint(ptrDetection); @@ -306,7 +366,7 @@ ArrayList runAprilTagDetectorForMultipleTagSizes(long capture ApriltagDetectionJNI.getId(ptrDetection), ApriltagDetectionJNI.getHamming(ptrDetection), ApriltagDetectionJNI.getDecisionMargin(ptrDetection), - new Point(center[0], center[1]), cornerPts, metadata, ftcPose, rawPose, captureTimeNanos)); + new Point(center[0], center[1]), cornerPts, metadata, ftcPose, rawPose, robotPose, captureTimeNanos)); } ApriltagDetectionJNI.freeDetectionList(ptrDetectionArray); @@ -316,6 +376,53 @@ ArrayList runAprilTagDetectorForMultipleTagSizes(long capture return new ArrayList<>(); } + private Pose3D computeRobotPose(AprilTagPoseRaw rawPose, AprilTagMetadata metadata, long acquisitionTime) + { + // Compute transformation matrix of tag pose in field reference frame + float tagInFieldX = metadata.fieldPosition.get(0); + float tagInFieldY = metadata.fieldPosition.get(1); + float tagInFieldZ = metadata.fieldPosition.get(2); + OpenGLMatrix tagInFieldR = new OpenGLMatrix(metadata.fieldOrientation.toMatrix()); + OpenGLMatrix tagInFieldFrame = OpenGLMatrix.identityMatrix() + .translated(tagInFieldX, tagInFieldY, tagInFieldZ) + .multiplied(tagInFieldR); + + // Compute transformation matrix of camera pose in tag reference frame + float tagInCameraX = (float) DistanceUnit.INCH.fromUnit(outputUnitsLength, rawPose.x); + float tagInCameraY = (float) DistanceUnit.INCH.fromUnit(outputUnitsLength, rawPose.y); + float tagInCameraZ = (float) DistanceUnit.INCH.fromUnit(outputUnitsLength, rawPose.z); + OpenGLMatrix tagInCameraR = new OpenGLMatrix((rawPose.R)); + OpenGLMatrix cameraInTagFrame = OpenGLMatrix.identityMatrix() + .translated(tagInCameraX, tagInCameraY, tagInCameraZ) + .multiplied(tagInCameraR) + .inverted(); + + // Compute transformation matrix of robot pose in field frame + OpenGLMatrix robotInFieldFrame = + tagInFieldFrame + .multiplied(cameraInTagFrame) + .multiplied(robotInCameraFrame); + + // Extract robot location + VectorF robotInFieldTranslation = robotInFieldFrame.getTranslation(); + Position robotPosition = new Position(DistanceUnit.INCH, + robotInFieldTranslation.get(0), + robotInFieldTranslation.get(1), + robotInFieldTranslation.get(2), + acquisitionTime).toUnit(outputUnitsLength); + + // Extract robot orientation + Orientation robotInFieldOrientation = Orientation.getOrientation(robotInFieldFrame, + AxesReference.INTRINSIC, AxesOrder.ZXY, outputUnitsAngle); + YawPitchRollAngles robotOrientation = new YawPitchRollAngles(outputUnitsAngle, + robotInFieldOrientation.firstAngle, + robotInFieldOrientation.secondAngle, + robotInFieldOrientation.thirdAngle, + acquisitionTime); + + return new Pose3D(robotPosition, robotOrientation); + } + private final Object drawSync = new Object(); @Override diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java index f417bd0c..896881af 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvCameraFactory.java @@ -10,7 +10,6 @@ public static OpenCvCameraFactory getInstance() { return instance; } - /* * Internal */ From f0d877caa4c57a98b1492814f835f891c6db024c Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Fri, 27 Sep 2024 00:54:39 -0600 Subject: [PATCH 07/12] Add ConceptAprilTagLocalization to gradle workspace --- .../resources/templates/gradle_workspace.zip | Bin 86686 -> 91053 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip b/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip index 59480ce0d5cfc820f8dd1369cfecdb58380deb4a..209e6f648468c4e79e5fbfb571717eb6fb9cc833 100644 GIT binary patch delta 4478 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 LM5JR1_+$SA7Qa5=Yr^o zaf}OLa$fO_(IBzq@r?Z-dX502#Pom!#%d7fYyx8sh>lKVoCcylCo;|e(Fra Date: Fri, 27 Sep 2024 01:00:41 -0600 Subject: [PATCH 08/12] Remove pipeline max active contexts checkg --- .../github/serivesmejia/eocvsim/EOCVSim.kt | 42 +++---------------- .../eocvsim/pipeline/PipelineManager.kt | 4 -- 2 files changed, 5 insertions(+), 41 deletions(-) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt index ffb1e9e9..85ae0517 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/EOCVSim.kt @@ -388,43 +388,11 @@ class EOCVSim(val params: Parameters = Parameters()) { inputSourceManager.update(pipelineManager.paused) tunerManager.update() - try { - pipelineManager.update( - if (inputSourceManager.lastMatFromSource != null && !inputSourceManager.lastMatFromSource.empty()) { - inputSourceManager.lastMatFromSource - } else null - ) - } catch (ex: MaxActiveContextsException) { //handles when a lot of pipelines are stuck in the background - visualizer.asyncPleaseWaitDialog( - "There are many pipelines stuck in processFrame running in the background", - "To avoid further issues, EOCV-Sim will exit now.", - "Ok", - Dimension(450, 150), - true, - true - ).onCancel { - destroy(DestroyReason.CRASH) //destroy eocv sim when pressing "exit" - } - - //print exception - logger.error( - "Please note that the following exception is likely to be caused by one or more of the user pipelines", - ex - ) - - //block the current thread until the user closes the dialog - try { - //using sleep for avoiding burning cpu cycles - Thread.sleep(Long.MAX_VALUE) - } catch (ignored: InterruptedException) { - //reinterrupt once user closes the dialog - Thread.currentThread().interrupt() - } - - break //bye bye - } catch (ex: InterruptedException) { - break // bye bye - } + pipelineManager.update( + if (inputSourceManager.lastMatFromSource != null && !inputSourceManager.lastMatFromSource.empty()) { + inputSourceManager.lastMatFromSource + } else null + ) //limit FPG fpsLimiter.maxFPS = config.pipelineMaxFps.fps.toDouble() diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt index e3b3757f..d6eabacc 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt @@ -275,10 +275,6 @@ class PipelineManager( } } - if(activePipelineContexts.size > MAX_ALLOWED_ACTIVE_PIPELINE_CONTEXTS) { - throw MaxActiveContextsException("Current amount of active pipeline coroutine contexts (${activePipelineContexts.size}) is more than the maximum allowed. This generally means that there are multiple pipelines stuck in processFrame() running in the background, check for any lengthy operations in your pipelines.") - } - if(telemetry is TelemetryImpl) { if (compiledPipelineManager.isBuildRunning) { telemetry.infoItem.caption = "[>]" From 3610615b101ace33d2cd01ef5bfe93b340fe266b Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Fri, 27 Sep 2024 17:03:08 -0600 Subject: [PATCH 09/12] Add missing Color methods, SDK color samples & Gamepad stub in OpModes --- .../ftc/robotcore/external/Telemetry.java | 74 ++- .../robotcore/robocol/TelemetryMessage.java | 22 + .../eocvsim/pipeline/PipelineManager.kt | 14 +- ...tryImpl.java => EOCVSimTelemetryImpl.java} | 101 +++-- .../resources/templates/default_workspace.zip | Bin 29308 -> 37207 bytes .../resources/templates/gradle_workspace.zip | Bin 91053 -> 99008 bytes .../samples/ConceptVisionColorLocator.java | 191 ++++++++ .../samples/ConceptVisionColorSensor.java | 136 ++++++ .../src/main/java/android/graphics/Color.java | 72 ++- .../robotcore/eventloop/opmode/OpMode.java | 5 + .../qualcomm/robotcore/hardware/Gamepad.java | 424 ++++++++++++++++++ .../robotcore/hardware/HardwareMap.java | 2 +- 12 files changed, 984 insertions(+), 57 deletions(-) create mode 100644 Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java rename EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/{TelemetryImpl.java => EOCVSimTelemetryImpl.java} (93%) create mode 100644 TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java create mode 100644 TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java create mode 100644 Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java index d862ac22..b15b12e9 100644 --- a/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/external/Telemetry.java @@ -1,17 +1,23 @@ /* Copyright (c) 2016 Robert Atkinson + All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of Robert Atkinson nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. + NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, @@ -26,6 +32,7 @@ are permitted (subject to the limitations in the disclaimer below) provided that */ package org.firstinspires.ftc.robotcore.external; +import androidx.annotation.Nullable; import java.util.Locale; @@ -43,12 +50,18 @@ are permitted (subject to the limitations in the disclaimer below) provided that * telemetry.update(); * * + *

In the 2015/16 season, the call to {@link #update()} was not required; now, however, + * in a LinearOpMode, unless {@link #update()} is called, nothing will appear on the + * driver station screen. In other, loop-based OpModes, {@link #update()} continues to be called + * automatically at the end of OpMode#loop() and OpMode#init_loop(); no call to + * {@link #update()} is required in loop-based OpModes.

* *
- *     // loop-based opmode
+ *     // loop-based OpMode
  *     telemetry.addData("count", currentCount);
  *     telemetry.addData("elapsedTime", "%.3f", elapsedSeconds);
  * 
+ *

By default (but see {@link #setAutoClear(boolean) setAutoClear()}), data is cleared from the * telemetry after each call to {@link #update()}; thus, you need to issue {@link #addData(String, * Object) addData()} for the entire contents of the telemetry screen on each update cycle. @@ -69,6 +82,7 @@ are permitted (subject to the limitations in the disclaimer below) provided that * telemetry.update(); * ... * } + * void anotherPartOfYourCode() { * ... * elapsedItem.setValue("%.3f", elapsedSeconds); @@ -268,6 +282,30 @@ public interface Telemetry */ boolean removeAction(Object token); + //---------------------------------------------------------------------------------------------- + // Text to Speech + //---------------------------------------------------------------------------------------------- + + /** + * Directs the Driver Station device to speak the given text using TextToSpeech functionality, + * with the same language and country codes that were previously used, or the default language + * and country. + * + * @param text the text to be spoken + */ + void speak(String text); + + /** + * Directs the Driver Station device to speak the given text using TextToSpeech functionality, + * with the given language and country codes. + * + * @param text the text to be spoken + * @param languageCode an ISO 639 alpha-2 or alpha-3 language code, or a language subtag up to + * 8 characters in length + * @param countryCode an ISO 3166 alpha-2 country code, or a UN M.49 numeric-3 area code + */ + void speak(String text, String languageCode, String countryCode); + //---------------------------------------------------------------------------------------------- // Transmission //---------------------------------------------------------------------------------------------- @@ -339,7 +377,7 @@ interface Line /** * Instances of {@link Item} represent an item of data on the drive station telemetry display. * - * @see {@link #addData(String, Object)} + * @see #addData(String, Object) */ interface Item { @@ -404,7 +442,7 @@ interface Item * @see #clear() * @see #isRetained() */ - Item setRetained(Boolean retained); + Item setRetained(@Nullable Boolean retained); /** * Returns whether the item is to be retained in a clear() operation. @@ -498,6 +536,30 @@ interface Item */ void setCaptionValueSeparator(String captionValueSeparator); + enum DisplayFormat + { + CLASSIC, // What you've all come to know and love (or not) since 2015 + MONOSPACE, // Same as classic, except uses a monospaced font so you can column align data + HTML; // Allows use of a subset of HTML tags, enabling "rich text" display (e.g. color & size) + } + + /** + * Sets the telemetry display format on the Driver Station. See the comments on {@link DisplayFormat}. + * + * @param displayFormat the telemetry display format the Driver Station should use + */ + void setDisplayFormat(DisplayFormat displayFormat); + + /** + * Sets the number of decimal places for Double and Float + * + * @param minDecimalPlaces - the minimum number of places to show when Double or Float is passed in without a Format + * @param maxDecimalPlaces - the maximum number of places to show when Double or Float is passed in without a Format + */ + default void setNumDecimalPlaces(int minDecimalPlaces, int maxDecimalPlaces){ + // does nothing just so we don't break existing Telemetry + } + //---------------------------------------------------------------------------------------------- // Properties //---------------------------------------------------------------------------------------------- @@ -516,9 +578,9 @@ interface Log enum DisplayOrder { NEWEST_FIRST, OLDEST_FIRST } /** - * Returns the maximum number of lines which will be retained in a {@link #log()()} and + * Returns the maximum number of lines which will be retained in a {@link #log()} and * shown on the driver station display. - * @return the maximum number of lines which will be retained in a {@link #log()()} + * @return the maximum number of lines which will be retained in a {@link #log()} * @see #setCapacity(int) */ int getCapacity(); @@ -567,4 +629,4 @@ enum DisplayOrder { NEWEST_FIRST, OLDEST_FIRST } * @see Log#addData(String, Object) */ Log log(); -} \ No newline at end of file +} diff --git a/Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java b/Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java new file mode 100644 index 00000000..41a62006 --- /dev/null +++ b/Common/src/main/java/org/firstinspires/ftc/robotcore/robocol/TelemetryMessage.java @@ -0,0 +1,22 @@ +package org.firstinspires.ftc.robotcore.robocol; + +/** + * Placeholder for telemetry message constants + */ +public class TelemetryMessage { + + static final int cbTimestamp = 8; + static final int cbSorted = 1; + static final int cbRobotState = 1; + static final int cbTagLen = 1; + static final int cbCountLen = 1; + static final int cbKeyLen = 2; + static final int cbValueLen = 2; + static final int cbFloat = 4; + + public final static int cbTagMax = (1 << (cbTagLen*8)) - 1; + public final static int cCountMax = (1 << (cbCountLen*8)) - 1; + public final static int cbKeyMax = (1 << (cbKeyLen*8)) - 1; + public final static int cbValueMax = (1 << (cbValueLen*8)) - 1; + +} diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt index d6eabacc..c9a910dd 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/PipelineManager.kt @@ -35,26 +35,22 @@ import com.github.serivesmejia.eocvsim.pipeline.util.PipelineSnapshot import com.github.serivesmejia.eocvsim.util.ReflectUtil import com.github.serivesmejia.eocvsim.util.StrUtil import com.github.serivesmejia.eocvsim.util.event.EventHandler -import com.github.serivesmejia.eocvsim.util.exception.MaxActiveContextsException import com.github.serivesmejia.eocvsim.util.fps.FpsCounter import com.github.serivesmejia.eocvsim.util.loggerForThis import io.github.deltacv.common.image.MatPoster import io.github.deltacv.common.pipeline.util.PipelineStatisticsCalculator import io.github.deltacv.eocvsim.virtualreflect.VirtualField -import io.github.deltacv.eocvsim.virtualreflect.VirtualReflectContext import io.github.deltacv.eocvsim.virtualreflect.VirtualReflection import io.github.deltacv.eocvsim.virtualreflect.jvm.JvmVirtualReflection import kotlinx.coroutines.* import org.firstinspires.ftc.robotcore.external.Telemetry -import org.firstinspires.ftc.robotcore.internal.opmode.TelemetryImpl +import org.firstinspires.ftc.robotcore.internal.opmode.EOCVSimTelemetryImpl import org.firstinspires.ftc.vision.VisionProcessor import org.opencv.core.Mat import org.openftc.easyopencv.OpenCvPipeline import org.openftc.easyopencv.OpenCvViewport import org.openftc.easyopencv.processFrameInternal import java.lang.RuntimeException -import java.lang.reflect.Constructor -import java.lang.reflect.Field import java.util.* import kotlin.coroutines.EmptyCoroutineContext import kotlin.math.roundToLong @@ -202,7 +198,7 @@ class PipelineManager( openedPipelineOutputCount++ } - if(telemetry is TelemetryImpl) { + if(telemetry is EOCVSimTelemetryImpl) { telemetry.errItem.caption = "[/!\\]" telemetry.errItem.setValue("Uncaught exception thrown, check Workspace -> Output.") telemetry.forceTelemetryTransmission() @@ -212,7 +208,7 @@ class PipelineManager( pipelineExceptionTracker.onPipelineExceptionClear { val telemetry = currentTelemetry - if(telemetry is TelemetryImpl) { + if(telemetry is EOCVSimTelemetryImpl) { telemetry.errItem.caption = "" telemetry.errItem.setValue("") telemetry.forceTelemetryTransmission() @@ -275,7 +271,7 @@ class PipelineManager( } } - if(telemetry is TelemetryImpl) { + if(telemetry is EOCVSimTelemetryImpl) { if (compiledPipelineManager.isBuildRunning) { telemetry.infoItem.caption = "[>]" telemetry.infoItem.setValue("Building java files in workspace...") @@ -581,7 +577,7 @@ class PipelineManager( val instantiator = getInstantiatorFor(pipelineClass) try { - nextTelemetry = TelemetryImpl().apply { + nextTelemetry = EOCVSimTelemetryImpl().apply { // send telemetry updates to the ui addTransmissionReceiver(eocvSim.visualizer.telemetryPanel) } diff --git a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryImpl.java b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/EOCVSimTelemetryImpl.java similarity index 93% rename from EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryImpl.java rename to EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/EOCVSimTelemetryImpl.java index 8e21e920..1af5a2f2 100644 --- a/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/TelemetryImpl.java +++ b/EOCV-Sim/src/main/java/org/firstinspires/ftc/robotcore/internal/opmode/EOCVSimTelemetryImpl.java @@ -1,18 +1,23 @@ /* Copyright (c) 2016 Robert Atkinson + All rights reserved. -Adapted for EOCV-Sim (c) 2021 Sebastian Erives + Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of Robert Atkinson nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. + NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, @@ -27,24 +32,29 @@ are permitted (subject to the limitations in the disclaimer below) provided that */ package org.firstinspires.ftc.robotcore.internal.opmode; -import com.github.serivesmejia.eocvsim.gui.component.visualizer.TelemetryPanel; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.qualcomm.robotcore.eventloop.opmode.OpMode; import com.qualcomm.robotcore.util.ElapsedTime; import org.firstinspires.ftc.robotcore.external.Func; import org.firstinspires.ftc.robotcore.external.Predicate; import org.firstinspires.ftc.robotcore.external.Telemetry; +import org.firstinspires.ftc.robotcore.robocol.TelemetryMessage; -import javax.swing.*; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; /** - * {@link TelemetryImpl} is the system-provided implementation of the {@link Telemetry} interface. + * {@link EOCVSimTelemetryImpl} is the system-provided implementation of the {@link Telemetry} interface. */ -public class TelemetryImpl implements Telemetry, TelemetryInternal +public class EOCVSimTelemetryImpl implements Telemetry, TelemetryInternal { + //---------------------------------------------------------------------------------------------- // Types //---------------------------------------------------------------------------------------------- @@ -78,9 +88,12 @@ protected class Value this.valueProducer = valueProducer; } - Value(Object value) - { - this.value = value; + Value(Object value) { + if ((value instanceof Double) || (value instanceof Float)) { + this.value = decimalFormat.format(value); + } else { + this.value = value; + } } Value(Func valueProducer) @@ -97,7 +110,7 @@ boolean isProducer() return this.valueProducer != null; } - String getComposed(boolean recompose) + @NonNull String getComposed(boolean recompose) { if (recompose || composed==null) { @@ -142,13 +155,7 @@ void boundedAddToList(int index, Lineable data) // Using the max # of data items that can be actually transmitted, ever, seems like // a practical choice as an upper bound. - - // 255 was inlined from the original calculations in robocol's TelemetryMessage.cCountMax: - // - // static final int cbCountLen = 1; - // ... - // public final static int cCountMax = (1 << (cbCountLen*8)) - 1; - if (list.size() < 255) + if (list.size() < TelemetryMessage.cCountMax) { list.add(index, data); } @@ -270,6 +277,7 @@ protected class ItemImpl implements Item, Lineable String caption = null; Value value = null; Boolean retained = null; + boolean showIfEmpty = true; //------------------------------------------------------------------------------------------ @@ -294,7 +302,7 @@ protected class ItemImpl implements Item, Lineable { String composed = this.value.getComposed(recompose); - if(!showIfEmpty && this.caption.trim().equals("") && composed.trim().equals("")) { + if(!showIfEmpty && this.caption.trim().isEmpty() && composed.trim().isEmpty()) { return ""; } @@ -323,7 +331,7 @@ protected class ItemImpl implements Item, Lineable } } - @Override public Item setRetained(Boolean retained) + @Override public Item setRetained(@Nullable Boolean retained) { synchronized (theLock) { @@ -418,14 +426,14 @@ protected class LineImpl implements Line, Lineable // Operations //------------------------------------------------------------------------------------------ - public String getComposed(boolean recompose, boolean appendSeparator) + @Override public String getComposed(boolean recompose) { StringBuilder result = new StringBuilder(); result.append(this.lineCaption); boolean firstTime = true; for (Lineable lineable : lineables) { - if (!firstTime && appendSeparator) + if (!firstTime) { result.append(getItemSeparator()); } @@ -435,11 +443,6 @@ public String getComposed(boolean recompose, boolean appendSeparator) return result.toString(); } - @Override - public String getComposed(boolean recompose) { - return getComposed(recompose, true); - } - @Override public Item addData(String caption, String format, Object... args) { return lineables.addItemAfter(null, caption, new Value(format, args)); @@ -459,10 +462,6 @@ public String getComposed(boolean recompose) { { return lineables.addItemAfter(null, caption, new Value(format, valueProducer)); } - - public boolean isEmpty() { - return getComposed(false, false).trim().equals(""); - } } protected class LogImpl implements Log @@ -510,7 +509,7 @@ boolean isDirty() // Use the outer class to mindlessly avoid any potential deadlocks Object getLock() { - return TelemetryImpl.this; + return EOCVSimTelemetryImpl.this; } int size() @@ -614,12 +613,16 @@ void reset() protected ElapsedTime transmissionTimer; protected boolean isDirty; protected boolean clearOnAdd; + protected OpMode opMode; protected boolean isAutoClear; protected int msTransmissionInterval; protected String captionValueSeparator; protected String itemSeparator; - protected StringBuilder currentSb; + protected DecimalFormat decimalFormat = new DecimalFormat("0.####"); + /* + * EOCV-Sim + */ protected ArrayList transmissionReceivers = new ArrayList<>(); public Item errItem; @@ -629,7 +632,7 @@ void reset() // Construction //---------------------------------------------------------------------------------------------- - public TelemetryImpl() + public EOCVSimTelemetryImpl() { this.log = new LogImpl(); resetTelemetryForOpMode(); @@ -649,8 +652,7 @@ public void resetTelemetryForOpMode() this.msTransmissionInterval = 250; this.captionValueSeparator = " : "; this.itemSeparator = " | "; - this.currentSb = new StringBuilder(); - this.transmissionReceivers = new ArrayList<>(); + errItem = addData("", "").setRetained(true); ((ItemImpl)errItem).showIfEmpty = false; @@ -678,6 +680,11 @@ boolean isDirty() return this.isDirty; } + public void setNumDecimalPlaces(int minDecimalPlaces, int maxDecimalPlaces){ + decimalFormat.setMinimumFractionDigits(minDecimalPlaces); + decimalFormat.setMaximumFractionDigits(maxDecimalPlaces); + } + //---------------------------------------------------------------------------------------------- // Updating //---------------------------------------------------------------------------------------------- @@ -753,11 +760,14 @@ else if (updateReason==UpdateReason.USER) return result; } + + } + protected void saveToTransmitter(boolean recompose) { - currentSb = new StringBuilder(); + StringBuilder currentSb = new StringBuilder(); // When we recompose, we save the composed lines. Thus, they will stick around // even after we might get clear()'d. In that way, they'll still be there to @@ -870,6 +880,11 @@ public boolean removeTransmissionReceiver(TelemetryTransmissionReceiver transmis } } + @Override public void setDisplayFormat(DisplayFormat displayFormat) + { + // no-op in eocv-sim + } + //---------------------------------------------------------------------------------------------- // Adding and removing data //---------------------------------------------------------------------------------------------- @@ -984,9 +999,17 @@ protected void onAddData() } } - @Override - public String toString() { - return currentSb.toString(); + //---------------------------------------------------------------------------------------------- + // Text to Speech + //---------------------------------------------------------------------------------------------- + + @Override public void speak(String text) + { + speak(text, null, null); } -} \ No newline at end of file + @Override public void speak(String text, String languageCode, String countryCode) + { + // no-op + } +} diff --git a/EOCV-Sim/src/main/resources/templates/default_workspace.zip b/EOCV-Sim/src/main/resources/templates/default_workspace.zip index f7e41a86ca994c71cd7d36f9bb6dde905c3bc42f..9d41330a33c9c598bad420dbcb2a416c0cc26c5c 100644 GIT binary patch delta 7755 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 diff --git a/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip b/EOCV-Sim/src/main/resources/templates/gradle_workspace.zip index 209e6f648468c4e79e5fbfb571717eb6fb9cc833..29a5cc7d28ae2391d8f246b1fb251079f3517655 100644 GIT binary patch delta 7784 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 MAdsLl!avjh0FBfX_y7O^ delta 38 pcmX@m%C`16Yr_^s@6heG#f*M@BCKo-K%fAGetZlJo}r9D9stzU2e<$L diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java new file mode 100644 index 00000000..312120c6 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2024 Phil Malone + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.robotcontroller.external.samples; + +import android.util.Size; + +import com.qualcomm.robotcore.eventloop.opmode.Disabled; +import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; +import com.qualcomm.robotcore.util.SortOrder; + +import org.firstinspires.ftc.robotcore.external.Telemetry; +import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; +import org.firstinspires.ftc.vision.VisionPortal; +import org.firstinspires.ftc.vision.opencv.ColorBlobLocatorProcessor; +import org.firstinspires.ftc.vision.opencv.ColorRange; +import org.firstinspires.ftc.vision.opencv.ImageRegion; +import org.opencv.core.RotatedRect; + +import java.util.List; + +/* + * This OpMode illustrates how to use a video source (camera) to locate specifically colored regions + * + * Unlike a "color sensor" which determines the color of an object in the field of view, this "color locator" + * will search the Region Of Interest (ROI) in a camera image, and find any "blobs" of color that match the requested color range. + * These blobs can be further filtered and sorted to find the one most likely to be the item the user is looking for. + * + * To perform this function, a VisionPortal runs a ColorBlobLocatorProcessor process. + * The ColorBlobLocatorProcessor process is created first, and then the VisionPortal is built to use this process. + * The ColorBlobLocatorProcessor analyses the ROI and locates pixels that match the ColorRange to form a "mask". + * The matching pixels are then collected into contiguous "blobs" of pixels. The outer boundaries of these blobs are called its "contour". + * For each blob, the process then creates the smallest possible rectangle "boxFit" that will fully encase the contour. + * The user can then call getBlobs() to retrieve the list of Blobs, where each Blob contains the contour and the boxFit data. + * Note: The default sort order for Blobs is ContourArea, in descending order, so the biggest contours are listed first. + * + * To aid the user, a colored boxFit rectangle is drawn on the camera preview to show the location of each Blob + * The original Blob contour can also be added to the preview. This is helpful when configuring the ColorBlobLocatorProcessor parameters. + * + * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. + * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list + */ + +@Disabled +@TeleOp(name = "Concept: Vision Color-Locator", group = "Concept") +public class ConceptVisionColorLocator extends LinearOpMode +{ + @Override + public void runOpMode() + { + /* Build a "Color Locator" vision processor based on the ColorBlobLocatorProcessor class. + * - Specify the color range you are looking for. You can use a predefined color, or create you own color range + * .setTargetColorRange(ColorRange.BLUE) // use a predefined color match + * Available predefined colors are: RED, BLUE YELLOW GREEN + * .setTargetColorRange(new ColorRange(ColorSpace.YCrCb, // or define your own color match + * new Scalar( 32, 176, 0), + * new Scalar(255, 255, 132))) + * + * - Focus the color locator by defining a RegionOfInterest (ROI) which you want to search. + * This can be the entire frame, or a sub-region defined using: + * 1) standard image coordinates or 2) a normalized +/- 1.0 coordinate system. + * Use one form of the ImageRegion class to define the ROI. + * ImageRegion.entireFrame() + * ImageRegion.asImageCoordinates(50, 50, 150, 150) 100x100 pixel square near the upper left corner + * ImageRegion.asUnityCenterCoordinates(-0.5, 0.5, 0.5, -0.5) 50% width/height square centered on screen + * + * - Define which contours are included. + * You can get ALL the contours, or you can skip any contours that are completely inside another contour. + * .setContourMode(ColorBlobLocatorProcessor.ContourMode.ALL_FLATTENED_HIERARCHY) // return all contours + * .setContourMode(ColorBlobLocatorProcessor.ContourMode.EXTERNAL_ONLY) // exclude contours inside other contours + * note: EXTERNAL_ONLY helps to avoid bright reflection spots from breaking up areas of solid color. + * + * - turn the display of contours ON or OFF. Turning this on helps debugging but takes up valuable CPU time. + * .setDrawContours(true) + * + * - include any pre-processing of the image or mask before looking for Blobs. + * There are some extra processing you can include to improve the formation of blobs. Using these features requires + * an understanding of how they may effect the final blobs. The "pixels" argument sets the NxN kernel size. + * .setBlurSize(int pixels) Blurring an image helps to provide a smooth color transition between objects, and smoother contours. + * The higher the number of pixels, the more blurred the image becomes. + * Note: Even "pixels" values will be incremented to satisfy the "odd number" requirement. + * Blurring too much may hide smaller features. A "pixels" size of 5 is good for a 320x240 image. + * .setErodeSize(int pixels) Erosion removes floating pixels and thin lines so that only substantive objects remain. + * Erosion can grow holes inside regions, and also shrink objects. + * "pixels" in the range of 2-4 are suitable for low res images. + * .setDilateSize(int pixels) Dilation makes objects more visible by filling in small holes, making lines appear thicker, + * and making filled shapes appear larger. Dilation is useful for joining broken parts of an + * object, such as when removing noise from an image. + * "pixels" in the range of 2-4 are suitable for low res images. + */ + ColorBlobLocatorProcessor colorLocator = new ColorBlobLocatorProcessor.Builder() + .setTargetColorRange(ColorRange.BLUE) // use a predefined color match + .setContourMode(ColorBlobLocatorProcessor.ContourMode.EXTERNAL_ONLY) // exclude blobs inside blobs + .setRoi(ImageRegion.asUnityCenterCoordinates(-0.5, 0.5, 0.5, -0.5)) // search central 1/4 of camera view + .setDrawContours(true) // Show contours on the Stream Preview + .setBlurSize(5) // Smooth the transitions between different colors in image + .build(); + + /* + * Build a vision portal to run the Color Locator process. + * + * - Add the colorLocator process created above. + * - Set the desired video resolution. + * Since a high resolution will not improve this process, choose a lower resolution that is + * supported by your camera. This will improve overall performance and reduce latency. + * - Choose your video source. This may be + * .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) ..... for a webcam + * or + * .setCamera(BuiltinCameraDirection.BACK) ... for a Phone Camera + */ + VisionPortal portal = new VisionPortal.Builder() + .addProcessor(colorLocator) + .setCameraResolution(new Size(320, 240)) + .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) + .build(); + + telemetry.setMsTransmissionInterval(50); // Speed up telemetry updates, Just use for debugging. + telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); + + // WARNING: To be able to view the stream preview on the Driver Station, this code runs in INIT mode. + while (opModeIsActive() || opModeInInit()) + { + telemetry.addData("preview on/off", "... Camera Stream\n"); + + // Read the current list + List blobs = colorLocator.getBlobs(); + + /* + * The list of Blobs can be filtered to remove unwanted Blobs. + * Note: All contours will be still displayed on the Stream Preview, but only those that satisfy the filter + * conditions will remain in the current list of "blobs". Multiple filters may be used. + * + * Use any of the following filters. + * + * ColorBlobLocatorProcessor.Util.filterByArea(minArea, maxArea, blobs); + * A Blob's area is the number of pixels contained within the Contour. Filter out any that are too big or small. + * Start with a large range and then refine the range based on the likely size of the desired object in the viewfinder. + * + * ColorBlobLocatorProcessor.Util.filterByDensity(minDensity, maxDensity, blobs); + * A blob's density is an indication of how "full" the contour is. + * If you put a rubber band around the contour you would get the "Convex Hull" of the contour. + * The density is the ratio of Contour-area to Convex Hull-area. + * + * ColorBlobLocatorProcessor.Util.filterByAspectRatio(minAspect, maxAspect, blobs); + * A blob's Aspect ratio is the ratio of boxFit long side to short side. + * A perfect Square has an aspect ratio of 1. All others are > 1 + */ + ColorBlobLocatorProcessor.Util.filterByArea(50, 20000, blobs); // filter out very small blobs. + + /* + * The list of Blobs can be sorted using the same Blob attributes as listed above. + * No more than one sort call should be made. Sorting can use ascending or descending order. + * ColorBlobLocatorProcessor.Util.sortByArea(SortOrder.DESCENDING, blobs); // Default + * ColorBlobLocatorProcessor.Util.sortByDensity(SortOrder.DESCENDING, blobs); + * ColorBlobLocatorProcessor.Util.sortByAspectRatio(SortOrder.DESCENDING, blobs); + */ + + telemetry.addLine(" Area Density Aspect Center"); + + // Display the size (area) and center location for each Blob. + for(ColorBlobLocatorProcessor.Blob b : blobs) + { + RotatedRect boxFit = b.getBoxFit(); + telemetry.addLine(String.format("%5d %4.2f %5.2f (%3d,%3d)", + b.getContourArea(), b.getDensity(), b.getAspectRatio(), (int) boxFit.center.x, (int) boxFit.center.y)); + } + + telemetry.update(); + sleep(50); + } + } +} \ No newline at end of file diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java new file mode 100644 index 00000000..33afccf1 --- /dev/null +++ b/TeamCode/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024 Phil Malone + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.firstinspires.ftc.robotcontroller.external.samples; + +import android.graphics.Color; +import android.util.Size; + +import com.qualcomm.robotcore.eventloop.opmode.Disabled; +import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; +import com.qualcomm.robotcore.eventloop.opmode.TeleOp; + +import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; +import org.firstinspires.ftc.vision.VisionPortal; +import org.firstinspires.ftc.vision.opencv.ImageRegion; +import org.firstinspires.ftc.vision.opencv.PredominantColorProcessor; + +/* + * This OpMode illustrates how to use a video source (camera) as a color sensor + * + * A "color sensor" will typically determine the color of the object that it is pointed at. + * + * This sample performs the same function, except it uses a video camera to inspect an object or scene. + * The user may choose to inspect all, or just a Region of Interest (ROI), of the active camera view. + * The user must also provide a list of "acceptable colors" (Swatches) from which the closest matching color will be selected. + * + * To perform this function, a VisionPortal runs a PredominantColorProcessor process. + * The PredominantColorProcessor process is created first, and then the VisionPortal is built to use this process. + * The PredominantColorProcessor analyses the ROI and splits the colored pixels into several color-clusters. + * The largest of these clusters is then considered to be the "Predominant Color" + * The process then matches the Predominant Color with the closest Swatch and returns that match. + * + * To aid the user, a colored rectangle is drawn on the camera preview to show the RegionOfInterest, + * The Predominant Color is used to paint the rectangle border, so the user can verify that the color is reasonable. + * + * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. + * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list + */ + +@Disabled +@TeleOp(name = "Concept: Vision Color-Sensor", group = "Concept") +public class ConceptVisionColorSensor extends LinearOpMode +{ + @Override + public void runOpMode() + { + /* Build a "Color Sensor" vision processor based on the PredominantColorProcessor class. + * + * - Focus the color sensor by defining a RegionOfInterest (ROI) which you want to inspect. + * This can be the entire frame, or a sub-region defined using: + * 1) standard image coordinates or 2) a normalized +/- 1.0 coordinate system. + * Use one form of the ImageRegion class to define the ROI. + * ImageRegion.entireFrame() + * ImageRegion.asImageCoordinates(50, 50, 150, 150) 100x100 pixel square near the upper left corner + * ImageRegion.asUnityCenterCoordinates(-0.1, 0.1, 0.1, -0.1) 10% width/height square centered on screen + * + * - Set the list of "acceptable" color swatches (matches). + * Only colors that you assign here will be returned. + * If you know the sensor will be pointing to one of a few specific colors, enter them here. + * Or, if the sensor may be pointed randomly, provide some additional colors that may match the surrounding. + * Possible choices are: + * RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE, MAGENTA, BLACK, WHITE; + * + * Note that in the example shown below, only some of the available colors are included. + * This will force any other colored region into one of these colors. + * eg: Green may be reported as YELLOW, as this may be the "closest" match. + */ + PredominantColorProcessor colorSensor = new PredominantColorProcessor.Builder() + .setRoi(ImageRegion.asUnityCenterCoordinates(-0.1, 0.1, 0.1, -0.1)) + .setSwatches( + PredominantColorProcessor.Swatch.RED, + PredominantColorProcessor.Swatch.BLUE, + PredominantColorProcessor.Swatch.YELLOW, + PredominantColorProcessor.Swatch.BLACK, + PredominantColorProcessor.Swatch.WHITE) + .build(); + + /* + * Build a vision portal to run the Color Sensor process. + * + * - Add the colorSensor process created above. + * - Set the desired video resolution. + * Since a high resolution will not improve this process, choose a lower resolution that is + * supported by your camera. This will improve overall performance and reduce latency. + * - Choose your video source. This may be + * .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) ..... for a webcam + * or + * .setCamera(BuiltinCameraDirection.BACK) ... for a Phone Camera + */ + VisionPortal portal = new VisionPortal.Builder() + .addProcessor(colorSensor) + .setCameraResolution(new Size(320, 240)) + .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) + .build(); + + telemetry.setMsTransmissionInterval(50); // Speed up telemetry updates, Just use for debugging. + + // WARNING: To be able to view the stream preview on the Driver Station, this code runs in INIT mode. + while (opModeIsActive() || opModeInInit()) + { + telemetry.addData("DS preview on/off", "3 dots, Camera Stream\n"); + + // Request the most recent color analysis. + // This will return the closest matching colorSwatch and the predominant RGB color. + // Note: to take actions based on the detected color, simply use the colorSwatch in a comparison or switch. + // eg: + // if (result.closestSwatch == PredominantColorProcessor.Swatch.RED) {... some code ...} + PredominantColorProcessor.Result result = colorSensor.getAnalysis(); + + // Display the Color Sensor result. + telemetry.addData("Best Match:", result.closestSwatch); + telemetry.addLine(String.format("R %3d, G %3d, B %3d", Color.red(result.rgb), Color.green(result.rgb), Color.blue(result.rgb))); + telemetry.update(); + + sleep(20); + } + } +} \ No newline at end of file diff --git a/Vision/src/main/java/android/graphics/Color.java b/Vision/src/main/java/android/graphics/Color.java index 7460ec3e..02243cc7 100644 --- a/Vision/src/main/java/android/graphics/Color.java +++ b/Vision/src/main/java/android/graphics/Color.java @@ -1383,9 +1383,77 @@ public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) f } return nativeHSVToColor(alpha, hsv); } - private static native void nativeRGBToHSV(int red, int greed, int blue, float hsv[]); - private static native int nativeHSVToColor(int alpha, float hsv[]); + private static void nativeRGBToHSV(int red, int green, int blue, float hsv[]) { + int min = Math.min(red, Math.min(green, blue)); + int max = Math.max(red, Math.max(green, blue)); + int delta = max - min; + + float v = (float) max / 255; + if (v < 0 || v > 1.0f) + throw new RuntimeException("RGB max is out of range"); + + if (delta == 0) { // we're a shade of gray + hsv[0] = 0; + hsv[1] = 0; + hsv[2] = v; + return; + } + + float s = (float) delta / max; + if (s < 0 || s > 1.0f) + throw new RuntimeException("RGB delta is out of range"); + + float h; + if (red == max) + h = (float) (green - blue) / delta; + else if (green == max) + h = 2.0f + (float) (blue - red) / delta; + else // blue == max + h = 4.0f + (float) (red - green) / delta; + + h *= 60; + if (h < 0) + h += 360.0f; + + if (h < 0 || h >= 360.0f) + throw new RuntimeException("h is out of range"); + + hsv[0] = h; + hsv[1] = s; + hsv[2] = v; + } + + private static int nativeHSVToColor(int alpha, float hsv[]) { + int s = hsv[1] < 0 ? 0 : hsv[1] >= 1.0f ? 255 : ((int) (hsv[1] * (1 << 16))) >> 8; + int v = hsv[2] < 0 ? 0 : hsv[2] >= 1.0f ? 255 : ((int) (hsv[2] * (1 << 16))) >> 8; + + if (s == 0) // shade of gray + return (alpha << 24) | (v << 16) | (v << 8) | v; + int hx = (hsv[0] < 0 || hsv[0] >= 360.0f) ? 0 : ((int) (hsv[0]/60 * (1 << 16))); + int f = hx & 0xFFFF; + + int v_scale = v+1; + int p = ((255 - s) * v_scale) >> 8; + int q = ((255 - (s * f >> 16)) * v_scale) >> 8; + int t = ((255 - (s * (1 << 16 - f) >> 16)) * v_scale) >> 8; + + int r, g, b; + + if (hx >> 16 >= 6) + throw new RuntimeException("hx is out of range"); + switch (hx >> 16) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + default: r = v; g = p; b = q; break; + } + return (alpha << 24) | (r << 16) | (g << 8) | b; + } + private static final HashMap sColorNameMap; + static { sColorNameMap = new HashMap<>(); sColorNameMap.put("black", BLACK); diff --git a/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java b/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java index 779bfe32..ec861393 100644 --- a/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java +++ b/Vision/src/main/java/com/qualcomm/robotcore/eventloop/opmode/OpMode.java @@ -31,6 +31,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE package com.qualcomm.robotcore.eventloop.opmode; +import com.qualcomm.robotcore.hardware.Gamepad; import com.qualcomm.robotcore.hardware.HardwareMap; import io.github.deltacv.vision.external.util.FrameQueue; import io.github.deltacv.vision.internal.opmode.OpModeNotification; @@ -53,6 +54,10 @@ public abstract class OpMode extends TimestampedOpenCvPipeline { // never in my volatile boolean isStarted = false; volatile boolean stopRequested = false; + // Stubs! + public Gamepad gamepad1 = new Gamepad(); + public Gamepad gamepad2 = new Gamepad(); + protected FrameQueue inputQueue; public HardwareMap hardwareMap; diff --git a/Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java b/Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java new file mode 100644 index 00000000..bfef1dab --- /dev/null +++ b/Vision/src/main/java/com/qualcomm/robotcore/hardware/Gamepad.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2014, 2015 Qualcomm Technologies Inc + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * (subject to the limitations in the disclaimer below) provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Qualcomm Technologies Inc nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS + * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.qualcomm.robotcore.hardware; + +/** + * Monitor a hardware gamepad. In the case of EOCV-Sim, this is a stub class that does nothing. + *

+ * The buttons, analog sticks, and triggers are represented a public + * member variables that can be read from or written to directly. + *

+ * Analog sticks are represented as floats that range from -1.0 to +1.0. They will be 0.0 while at + * rest. The horizontal axis is labeled x, and the vertical axis is labeled y. + *

+ * Triggers are represented as floats that range from 0.0 to 1.0. They will be at 0.0 while at + * rest. + *

+ * Buttons are boolean values. They will be true if the button is pressed, otherwise they will be + * false. + *

+ * The codes KEYCODE_BUTTON_SELECT and KEYCODE_BACK are both be handled as a "back" button event. + * Older Android devices (Kit Kat) map a Logitech F310 "back" button press to a KEYCODE_BUTTON_SELECT event. + * Newer Android devices (Marshmallow or greater) map this "back" button press to a KEYCODE_BACK event. + * Also, the REV Robotics Gamepad (REV-31-1159) has a "select" button instead of a "back" button on the gamepad. + *

+ * The dpad is represented as 4 buttons, dpad_up, dpad_down, dpad_left, and dpad_right + */ +@SuppressWarnings("unused") +public class Gamepad { + + /** + * A gamepad with an ID equal to ID_UNASSOCIATED has not been associated with any device. + */ + public static final int ID_UNASSOCIATED = -1; + + /** + * A gamepad with a phantom id a synthetic one made up by the system + */ + public static final int ID_SYNTHETIC = -2; + + public enum Type { + // Do NOT change the order/names of existing entries, + // you will break backwards compatibility!! + UNKNOWN(LegacyType.UNKNOWN), + LOGITECH_F310(LegacyType.LOGITECH_F310), + XBOX_360(LegacyType.XBOX_360), + SONY_PS4(LegacyType.SONY_PS4), // This indicates a PS4-compatible controller that is being used through our compatibility mode + SONY_PS4_SUPPORTED_BY_KERNEL(LegacyType.SONY_PS4); // This indicates a PS4-compatible controller that is being used through the DualShock 4 Linux kernel driver. + + private final LegacyType correspondingLegacyType; + Type(LegacyType correspondingLegacyType) { + this.correspondingLegacyType = correspondingLegacyType; + } + } + + // LegacyType is necessary because robocol gamepad version 3 was written in a way that was not + // forwards-compatible, so we have to keep sending V3-compatible values. + public enum LegacyType { + // Do NOT change the order or names of existing entries, or add new entries. + // You will break backwards compatibility!! + UNKNOWN, + LOGITECH_F310, + XBOX_360, + SONY_PS4 + } + + @SuppressWarnings("UnusedAssignment") + public volatile Type type = Type.UNKNOWN; // IntelliJ thinks this is redundant, but it is NOT. Must be a bug in the analyzer? + + /** + * left analog stick horizontal axis + */ + public volatile float left_stick_x = 0f; + + /** + * left analog stick vertical axis + */ + public volatile float left_stick_y = 0f; + + /** + * right analog stick horizontal axis + */ + public volatile float right_stick_x = 0f; + + /** + * right analog stick vertical axis + */ + public volatile float right_stick_y = 0f; + + /** + * dpad up + */ + public volatile boolean dpad_up = false; + + /** + * dpad down + */ + public volatile boolean dpad_down = false; + + /** + * dpad left + */ + public volatile boolean dpad_left = false; + + /** + * dpad right + */ + public volatile boolean dpad_right = false; + + /** + * button a + */ + public volatile boolean a = false; + + /** + * button b + */ + public volatile boolean b = false; + + /** + * button x + */ + public volatile boolean x = false; + + /** + * button y + */ + public volatile boolean y = false; + + /** + * button guide - often the large button in the middle of the controller. The OS may + * capture this button before it is sent to the app; in which case you'll never + * receive it. + */ + public volatile boolean guide = false; + + /** + * button start + */ + public volatile boolean start = false; + + /** + * button back + */ + public volatile boolean back = false; + + /** + * button left bumper + */ + public volatile boolean left_bumper = false; + + /** + * button right bumper + */ + public volatile boolean right_bumper = false; + + /** + * left stick button + */ + public volatile boolean left_stick_button = false; + + /** + * right stick button + */ + public volatile boolean right_stick_button = false; + + /** + * left trigger + */ + public volatile float left_trigger = 0f; + + /** + * right trigger + */ + public volatile float right_trigger = 0f; + + /** + * PS4 Support - Circle + */ + public volatile boolean circle = false; + + /** + * PS4 Support - cross + */ + public volatile boolean cross = false; + + /** + * PS4 Support - triangle + */ + public volatile boolean triangle = false; + + /** + * PS4 Support - square + */ + public volatile boolean square = false; + + /** + * PS4 Support - share + */ + public volatile boolean share = false; + + /** + * PS4 Support - options + */ + public volatile boolean options = false; + + /** + * PS4 Support - touchpad + */ + public volatile boolean touchpad = false; + public volatile boolean touchpad_finger_1; + public volatile boolean touchpad_finger_2; + public volatile float touchpad_finger_1_x; + public volatile float touchpad_finger_1_y; + public volatile float touchpad_finger_2_x; + public volatile float touchpad_finger_2_y; + + /** + * PS4 Support - PS Button + */ + public volatile boolean ps = false; + + /** + * ID assigned to this gamepad by the OS. This value can change each time the device is plugged in. + */ + public volatile int id = ID_UNASSOCIATED; // public only for historical reasons + + public void setGamepadId(int id) { + this.id = id; + } + public int getGamepadId() { + return this.id; + } + + /** + * Relative timestamp of the last time an event was detected + */ + public volatile long timestamp = 0; + + public Gamepad() { + this.type = type(); + } + + /** + * Reset this gamepad into its initial state + */ + public void reset() { + left_stick_x = 0f; + left_stick_y = 0f; + right_stick_x = 0f; + right_stick_y = 0f; + dpad_up = false; + dpad_down = false; + dpad_left = false; + dpad_right = false; + a = false; + b = false; + x = false; + y = false; + guide = false; + start = false; + back = false; + left_bumper = false; + right_bumper = false; + left_stick_button = false; + right_stick_button = false; + left_trigger = 0f; + right_trigger = 0f; + circle = false; + cross = false; + triangle = false; + square = false; + share = false; + options = false; + touchpad = false; + touchpad_finger_1 = false; + touchpad_finger_2 = false; + touchpad_finger_1_x = 0f; + touchpad_finger_1_y = 0f; + touchpad_finger_2_x = 0f; + touchpad_finger_2_y = 0f; + ps = false; + timestamp = 0; + } + + /** + * Are all analog sticks and triggers in their rest position? + * @return true if all analog sticks and triggers are at rest; otherwise false + */ + public boolean atRest() { + return ( + left_stick_x == 0f && left_stick_y == 0f && + right_stick_x == 0f && right_stick_y == 0f && + left_trigger == 0f && right_trigger == 0f); + } + + /** + * Get the type of gamepad as a {@link Type}. This method defaults to "UNKNOWN". + * @return gamepad type + */ + public Type type() { + return type; + } + + /** + * Get the type of gamepad as a {@link LegacyType}. This method defaults to "UNKNOWN". + * @return gamepad type + */ + private LegacyType legacyType() { + return type.correspondingLegacyType; + } + + + /** + * Display a summary of this gamepad, including the state of all buttons, analog sticks, and triggers + * @return a summary + */ + @Override + public String toString() { + + switch (type) { + case SONY_PS4: + case SONY_PS4_SUPPORTED_BY_KERNEL: + return ps4ToString(); + + case UNKNOWN: + case LOGITECH_F310: + case XBOX_360: + default: + return genericToString(); + } + } + + + protected String ps4ToString() { + String buttons = new String(); + if (dpad_up) buttons += "dpad_up "; + if (dpad_down) buttons += "dpad_down "; + if (dpad_left) buttons += "dpad_left "; + if (dpad_right) buttons += "dpad_right "; + if (cross) buttons += "cross "; + if (circle) buttons += "circle "; + if (square) buttons += "square "; + if (triangle) buttons += "triangle "; + if (ps) buttons += "ps "; + if (share) buttons += "share "; + if (options) buttons += "options "; + if (touchpad) buttons += "touchpad "; + if (left_bumper) buttons += "left_bumper "; + if (right_bumper) buttons += "right_bumper "; + if (left_stick_button) buttons += "left stick button "; + if (right_stick_button) buttons += "right stick button "; + + return String.format("ID: %2d user: %2d lx: % 1.2f ly: % 1.2f rx: % 1.2f ry: % 1.2f lt: %1.2f rt: %1.2f %s", + id, 0, left_stick_x, left_stick_y, + right_stick_x, right_stick_y, left_trigger, right_trigger, buttons); + } + + protected String genericToString() { + String buttons = new String(); + if (dpad_up) buttons += "dpad_up "; + if (dpad_down) buttons += "dpad_down "; + if (dpad_left) buttons += "dpad_left "; + if (dpad_right) buttons += "dpad_right "; + if (a) buttons += "a "; + if (b) buttons += "b "; + if (x) buttons += "x "; + if (y) buttons += "y "; + if (guide) buttons += "guide "; + if (start) buttons += "start "; + if (back) buttons += "back "; + if (left_bumper) buttons += "left_bumper "; + if (right_bumper) buttons += "right_bumper "; + if (left_stick_button) buttons += "left stick button "; + if (right_stick_button) buttons += "right stick button "; + + return String.format("ID: %2d user: %2d lx: % 1.2f ly: % 1.2f rx: % 1.2f ry: % 1.2f lt: %1.2f rt: %1.2f %s", + id, 0, left_stick_x, left_stick_y, + right_stick_x, right_stick_y, left_trigger, right_trigger, buttons); + } + + /** + * Alias buttons so that XBOX & PS4 native button labels can be used in use code. + * Should allow a team to program with whatever controllers they prefer, but + * be able to swap controllers easily without changing code. + */ + protected void updateButtonAliases(){ + // There is no assignment for touchpad because there is no equivalent on XBOX controllers. + circle = b; + cross = a; + triangle = y; + square = x; + share = back; + options = start; + ps = guide; + } +} diff --git a/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java b/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java index f0c39b30..1a58f1a0 100644 --- a/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java +++ b/Vision/src/main/java/com/qualcomm/robotcore/hardware/HardwareMap.java @@ -21,7 +21,7 @@ public T get(Class classType, String deviceName) { return (T) new SourcedCameraNameImpl(ThreadSourceHander.hand(deviceName)); } - return null; + throw new IllegalArgumentException("Unknown device type " + classType.getName()); } } \ No newline at end of file From cf2bcac8e34a4ac9919ed45114596a87de7b2734 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Fri, 27 Sep 2024 23:43:50 -0700 Subject: [PATCH 10/12] Warnings and discord server in readme --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d277903..209a2de9 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,9 @@ Since OpenCV in Java uses a native library, which is platform specific, the simu Follow the steps in [this page](https://deltacv.gitbook.io/eocv-sim/basics/downloading-eocv-sim) to download the sim. The rest of the documentation can also be found [there](https://deltacv.gitbook.io/eocv-sim/). -## Adding EOCV-Sim as a dependency +## Adding EOCV-Sim as a dependency for plugin development + +### NOT FOR FTC SDK USAGE, please follow the documentation above if you're a normal user not aiming to develop for EOCV-Sim ### Gradle ```groovy @@ -56,7 +58,7 @@ Follow the steps in [this page](https://deltacv.gitbook.io/eocv-sim/basics/downl } dependencies { - implementation 'com.github.deltacv:EOCV-Sim:3.3.2' //add the EOCV-Sim dependency + implementation 'com.github.deltacv:EOCV-Sim:3.3.2' // add the EOCV-Sim dependency, make sure to replace for the latest version } ``` @@ -84,6 +86,8 @@ Follow the steps in [this page](https://deltacv.gitbook.io/eocv-sim/basics/downl # Contact For bug reporting or feature requesting, use the [issues tab](https://github.com/deltacv/EOCV-Sim/issues) in this repository. +Join the [deltacv discord server](https://discord.gg/A3RMYzf6DA) ! + # Change logs ### Formerly, EOCV-Sim was hosted on a [personal account repo](https://github.com/serivesmejia/EOCV-Sim/). Released prior to 3.0.0 can be found there for historic purposes. From 2b17a3f3ca1bbba37e5c80a5e0f108cfba536157 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Sun, 29 Sep 2024 08:27:06 -0600 Subject: [PATCH 11/12] Fix typefaces by manually adding the roboto font --- .../serivesmejia/eocvsim/gui/IconDelegate.kt | 15 ++ .../serivesmejia/eocvsim/gui/IconLibrary.kt | 17 -- .../PipelineOpModeSwitchablePanel.kt | 4 + .../eocvsim/pipeline/DefaultPipeline.java | 2 +- EOCV-Sim/src/main/resources/fonts/LICENSE.txt | 202 ++++++++++++++++++ .../src/main/resources/fonts/Roboto-Bold.ttf | Bin 0 -> 167336 bytes .../resources/fonts/Roboto-BoldItalic.ttf | Bin 0 -> 171508 bytes .../main/resources/fonts/Roboto-Italic.ttf | Bin 0 -> 170504 bytes .../main/resources/fonts/Roboto-Regular.ttf | Bin 0 -> 168260 bytes .../src/main/java/android/graphics/Paint.java | 2 +- .../main/java/android/graphics/Typeface.java | 23 +- .../easyopencv/OpenCvViewRenderer.java | 2 +- 12 files changed, 244 insertions(+), 23 deletions(-) create mode 100644 EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt delete mode 100644 EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconLibrary.kt create mode 100644 EOCV-Sim/src/main/resources/fonts/LICENSE.txt create mode 100644 EOCV-Sim/src/main/resources/fonts/Roboto-Bold.ttf create mode 100644 EOCV-Sim/src/main/resources/fonts/Roboto-BoldItalic.ttf create mode 100644 EOCV-Sim/src/main/resources/fonts/Roboto-Italic.ttf create mode 100644 EOCV-Sim/src/main/resources/fonts/Roboto-Regular.ttf diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt new file mode 100644 index 00000000..b0be894d --- /dev/null +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconDelegate.kt @@ -0,0 +1,15 @@ +package com.github.serivesmejia.eocvsim.gui + +import kotlin.reflect.KProperty + +fun icon(name: String, path: String, allowInvert: Boolean = true) = IconDelegate(name, path, allowInvert) + +class IconDelegate(val name: String, val path: String, allowInvert: Boolean = true) { + init { + Icons.addFutureImage(name, path, allowInvert) + } + + operator fun getValue(any: Any, property: KProperty<*>): Icons.NamedImageIcon { + return Icons.getImage(name) + } +} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconLibrary.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconLibrary.kt deleted file mode 100644 index b5d95097..00000000 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/IconLibrary.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.serivesmejia.eocvsim.gui - -import java.awt.image.BufferedImage -import javax.swing.ImageIcon -import kotlin.reflect.KProperty - -fun icon(name: String, path: String, allowInvert: Boolean = true) = EOCVSimIconDelegate(name, path, allowInvert) - -class EOCVSimIconDelegate(val name: String, val path: String, allowInvert: Boolean = true) { - init { - Icons.addFutureImage(name, path, allowInvert) - } - - operator fun getValue(eocvSimIconLibrary: EOCVSimIconLibrary, property: KProperty<*>): Icons.NamedImageIcon { - return Icons.getImage(name) - } -} \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/PipelineOpModeSwitchablePanel.kt b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/PipelineOpModeSwitchablePanel.kt index f9f4e52d..80d1d651 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/PipelineOpModeSwitchablePanel.kt +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/component/visualizer/PipelineOpModeSwitchablePanel.kt @@ -115,6 +115,10 @@ class PipelineOpModeSwitchablePanel(val eocvSim: EOCVSim) : JTabbedPane() { pipelineSelectorPanel.isActive = false opModeSelectorPanel.isActive = true + } else { + opModeSelectorPanel.reset() + pipelineSelectorPanel.isActive = false + opModeSelectorPanel.isActive = false } } } diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java index 47f0b24d..c4c11323 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/pipeline/DefaultPipeline.java @@ -75,7 +75,7 @@ public Mat processFrame(Mat input) { @Override public void onDrawFrame(Canvas canvas, int onscreenWidth, int onscreenHeight, float scaleBmpPxToCanvasPx, float scaleCanvasDensity, Object userContext) { - canvas.drawRect(new Rect(0, 0, 385, 45), boxPaint); + canvas.drawRect(new Rect(0, 0, 345, 45), boxPaint); canvas.drawText("Default pipeline selected", 5, 33, textPaint); } diff --git a/EOCV-Sim/src/main/resources/fonts/LICENSE.txt b/EOCV-Sim/src/main/resources/fonts/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/EOCV-Sim/src/main/resources/fonts/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/EOCV-Sim/src/main/resources/fonts/Roboto-Bold.ttf b/EOCV-Sim/src/main/resources/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e64db796d1fc0d6f05566dc394fc8640804fcf06 GIT binary patch literal 167336 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

h@1%y)=IK&oxvR~$>Ot<+@7QME(<8>#rq-*| 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ⅆ?hh}6mZ2taXW{BkprwLQLvV*ifLZzGco8#y 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

    %D<49DNZ3Vm!soW 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 TG$5ZE>3uOE+F%)ll literal 0 HcmV?d00001 diff --git a/EOCV-Sim/src/main/resources/fonts/Roboto-Italic.ttf b/EOCV-Sim/src/main/resources/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..65498ee374c89a7c0fb9b1b12d92fcf750fc65cb GIT binary patch literal 170504 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 W-?th8o?_*AR~!A}WQcZ-mH#hI$bohM literal 0 HcmV?d00001 diff --git a/EOCV-Sim/src/main/resources/fonts/Roboto-Regular.ttf b/EOCV-Sim/src/main/resources/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2d116d920580367caf6a179155f344ede856bf31 GIT binary patch literal 168260 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∾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 TwL*XSfJ*Ay literal 0 HcmV?d00001 diff --git a/Vision/src/main/java/android/graphics/Paint.java b/Vision/src/main/java/android/graphics/Paint.java index 201e8116..b6417cda 100644 --- a/Vision/src/main/java/android/graphics/Paint.java +++ b/Vision/src/main/java/android/graphics/Paint.java @@ -358,7 +358,7 @@ public float getStrokeMiter() { public Typeface getTypeface() { if(typeface == null) { - typeface = Typeface.DEFAULT; + return Typeface.DEFAULT; } return typeface; diff --git a/Vision/src/main/java/android/graphics/Typeface.java b/Vision/src/main/java/android/graphics/Typeface.java index a2aa44e4..9a56291e 100644 --- a/Vision/src/main/java/android/graphics/Typeface.java +++ b/Vision/src/main/java/android/graphics/Typeface.java @@ -23,14 +23,18 @@ package android.graphics; +import org.jetbrains.skia.Data; import org.jetbrains.skia.FontMgr; import org.jetbrains.skia.FontStyle; +import java.io.FileNotFoundException; +import java.io.IOException; + public class Typeface { - public static Typeface DEFAULT = new Typeface(FontMgr.Companion.getDefault().matchFamilyStyle(null, FontStyle.Companion.getNORMAL())); - public static Typeface DEFAULT_BOLD = new Typeface(FontMgr.Companion.getDefault().matchFamilyStyle(null, FontStyle.Companion.getBOLD())); - public static Typeface DEFAULT_ITALIC = new Typeface(FontMgr.Companion.getDefault().matchFamilyStyle(null, FontStyle.Companion.getITALIC())); + public static Typeface DEFAULT = new Typeface(FontMgr.Companion.getDefault().makeFromData(loadDataFromResource("/fonts/Roboto-Regular.ttf"), 0)); + public static Typeface DEFAULT_BOLD = new Typeface(FontMgr.Companion.getDefault().makeFromData(loadDataFromResource("/fonts/Roboto-Bold.ttf"), 0)); + public static Typeface DEFAULT_ITALIC = new Typeface(FontMgr.Companion.getDefault().makeFromData(loadDataFromResource("/fonts/Roboto-Italic.ttf"), 0)); /** * Internal: theTypeface represents the underlying skiko Typeface @@ -55,4 +59,17 @@ public Rect getBounds() { ); } + private static Data loadDataFromResource(String resource) { + try { + byte[] bytes = Typeface.class.getResourceAsStream(resource).readAllBytes(); + + return Data.Companion.makeFromBytes( + bytes, + 0, bytes.length + ); + } catch (IOException e) { + throw new IllegalArgumentException("Failed to load from resource: " + resource, e); + } + } + } diff --git a/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java b/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java index 39596a3c..ea186167 100644 --- a/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java +++ b/Vision/src/main/java/org/openftc/easyopencv/OpenCvViewRenderer.java @@ -75,7 +75,7 @@ public OpenCvViewRenderer(boolean renderingOffsceen, String fpsMeterDescriptor) metricsScale = 1.0f; fpsMeterTextSize = 26.2f * metricsScale; - statBoxW = (int) (450 * metricsScale); + statBoxW = (int) (430 * metricsScale); statBoxH = (int) (120 * metricsScale); statBoxTextLineSpacing = (int) (35 * metricsScale); statBoxLTxtMargin = (int) (5 * metricsScale); From 25b3f0f98e1965cdd33b7f321f65d1182c59d2b9 Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Sun, 29 Sep 2024 08:39:54 -0600 Subject: [PATCH 12/12] Changelog for 3.7.1 --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 209a2de9..7c2cb944 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,23 @@ Join the [deltacv discord server](https://discord.gg/A3RMYzf6DA) ! ### Formerly, EOCV-Sim was hosted on a [personal account repo](https://github.com/serivesmejia/EOCV-Sim/). Released prior to 3.0.0 can be found there for historic purposes. +## [v3.7.1 - Better FTC VisionPortal support & Plugin System Fixes](https://github.com/deltacv/EOCV-Sim/releases/tag/v3.7.1) +- This is the 24th release for EOCV-Sim + - Changelog + - Update skiko to 0.8.15 + - Fixes Typeface.DEFAULT_BOLD and Typeface.DEFAULT_ITALIC to actually work + - Adds a stub no-op implementation for the FTC SDK Gamepad class into OpMode + - Adds android.opengl.Matrix implementation and matrices from the FTC SDK + - Adds navigation classes from the FTC SDK (AngleUnit, AxesOrder, AxesReference, etc) + - Adds the ConceptAprilTagLocalization, ConceptVisionColorLocator and ConceptVisionColorSensor samples + - Reimplements Telemetry to EOCVSimTelemetryImpl with stubs for Telemetry#talk + - Internal changes: + - Plugin virtual filesystems now use the name and author in the TOML to generate the fs name + - Allows to specify JVM args in JavaExec + - Rename some internal classes + - Better handling of Pipeline/OpMode tab switching -### [v3.7.0 - FTC SDK 10.1 & Refined Plugin System](https://github.com/deltacv/EOCV-Sim/releases/tag/v3.5.4) +### [v3.7.0 - FTC SDK 10.1 & Refined Plugin System](https://github.com/deltacv/EOCV-Sim/releases/tag/v3.7.0) - This is the 23nd release for EOCV-Sim - Changelog - Addresses the changes made in the FTC SDK 10.1 for the 2024-2025 season: @@ -105,7 +120,7 @@ Join the [deltacv discord server](https://discord.gg/A3RMYzf6DA) ! - Bugfixes: - Fixes exception loop when an exception is thrown from pipeline init -### [v3.6.0 - Plugin System & Into the Deep AprilTags](https://github.com/deltacv/EOCV-Sim/releases/tag/v3.5.4) +### [v3.6.0 - Plugin System & Into the Deep AprilTags](https://github.com/deltacv/EOCV-Sim/releases/tag/v3.6.0) - This is the 22nd release for EOCV-Sim - Changelog - Addresses the changes made in the FTC SDK 10.0 for the 2024-2025 season: