kopia lustrzana https://github.com/jameshball/osci-render
				
				
				
			Make line endings consistent and refresh GUI
							rodzic
							
								
									0c3a3b7319
								
							
						
					
					
						commit
						d65261ca3c
					
				|  | @ -56,7 +56,7 @@ Additional effects can be applied to the image such as: | |||
| 
 | ||||
| ## Screenshots | ||||
| 
 | ||||
| <img width="250px" height="396px" src="gui.png"> | ||||
| <img width="524px" height="330px" src="gui.png"> | ||||
| 
 | ||||
| ## Running | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								gui.png
								
								
								
								
							
							
						
						
									
										
											BIN
										
									
								
								gui.png
								
								
								
								
							
										
											Plik binarny nie jest wyświetlany.
										
									
								
							| Przed Szerokość: | Wysokość: | Rozmiar: 28 KiB Po Szerokość: | Wysokość: | Rozmiar: 58 KiB | 
							
								
								
									
										574
									
								
								pom.xml
								
								
								
								
							
							
						
						
									
										574
									
								
								pom.xml
								
								
								
								
							|  | @ -1,288 +1,288 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <groupId>sh.ball</groupId> | ||||
|     <artifactId>osci-render</artifactId> | ||||
|     <version>1.6.1</version> | ||||
| 
 | ||||
|     <name>osci-render</name> | ||||
| 
 | ||||
|     <properties> | ||||
|         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
|         <maven.compiler.release>15</maven.compiler.release> | ||||
|         <javafx.version>16</javafx.version> | ||||
|         <project.modulePath>${project.build.directory}\modules</project.modulePath> | ||||
|         <appModule>oscirender</appModule> | ||||
|         <appMainClass>sh.ball.gui.Gui</appMainClass> | ||||
|     </properties> | ||||
| 
 | ||||
|     <build> | ||||
|         <plugins> | ||||
|             <plugin> | ||||
|                 <artifactId>maven-clean-plugin</artifactId> | ||||
|                 <version>2.4.1</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>auto-clean</id> | ||||
|                         <phase>initialize</phase> | ||||
|                         <goals> | ||||
|                             <goal>clean</goal> | ||||
|                         </goals> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <artifactId>maven-jar-plugin</artifactId> | ||||
|                 <version>3.2.0</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>default-jar</id> | ||||
|                         <phase>none</phase> | ||||
|                         <configuration> | ||||
|                             <finalName>unwanted</finalName> | ||||
|                             <classifier>unwanted</classifier> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <artifactId>maven-assembly-plugin</artifactId> | ||||
|                 <configuration> | ||||
|                     <archive> | ||||
|                         <manifest> | ||||
|                             <mainClass>sh.ball.gui.Gui</mainClass> | ||||
|                         </manifest> | ||||
|                     </archive> | ||||
|                     <descriptorRefs> | ||||
|                         <descriptorRef>jar-with-dependencies</descriptorRef> | ||||
|                     </descriptorRefs> | ||||
|                     <appendAssemblyId>false</appendAssemblyId> | ||||
|                 </configuration> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>make-assembly</id> | ||||
|                         <phase>package</phase> | ||||
|                         <goals> | ||||
|                             <goal>single</goal> | ||||
|                         </goals> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-dependency-plugin</artifactId> | ||||
|                 <version>3.1.2</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>copy-dependencies</id> | ||||
|                         <phase>generate-sources</phase> | ||||
|                         <goals> | ||||
|                             <goal>copy-dependencies</goal> | ||||
|                         </goals> | ||||
|                         <configuration> | ||||
|                             <outputDirectory>${project.build.directory}/modules</outputDirectory> | ||||
|                             <overWriteReleases>true</overWriteReleases> | ||||
|                             <overWriteSnapshots>true</overWriteSnapshots> | ||||
|                             <overWriteIfNewer>true</overWriteIfNewer> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-compiler-plugin</artifactId> | ||||
|                 <version>3.8.1</version> | ||||
|                 <configuration> | ||||
|                     <source>${maven.compiler.release}</source> | ||||
|                     <target>${maven.compiler.release}</target> | ||||
|                     <compilerArgs> | ||||
|                         <compilerArg>--enable-preview</compilerArg> | ||||
|                         <compilerArg>--module-path</compilerArg> | ||||
|                         <compilerArg>${project.modulePath}</compilerArg> | ||||
|                     </compilerArgs> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.panteleyev</groupId> | ||||
|                 <artifactId>jpackage-maven-plugin</artifactId> | ||||
|                 <version>1.4.0</version> | ||||
| 
 | ||||
|                 <configuration> | ||||
|                     <name>osci-render</name> | ||||
|                     <appVersion>${project.version}</appVersion> | ||||
|                     <vendor>james.ball.sh</vendor> | ||||
|                     <module>${appModule}/${appMainClass}</module> | ||||
|                     <modulePath>${project.build.directory}/modules;${project.build.directory}/classes</modulePath> | ||||
|                     <destination>${project.build.directory}</destination> | ||||
|                     <javaOptions> | ||||
|                         <option>--enable-preview</option> | ||||
|                         <option>-Dfile.encoding=UTF-8</option> | ||||
|                     </javaOptions> | ||||
|                 </configuration> | ||||
| 
 | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>win</id> | ||||
|                         <configuration> | ||||
|                             <icon>src/main/resources/icons/icon.ico</icon> | ||||
|                             <winMenu>true</winMenu> | ||||
|                             <winMenuGroup>osci-render</winMenuGroup> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                     <execution> | ||||
|                         <id>debian</id> | ||||
|                         <configuration> | ||||
|                             <type>deb</type> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.moditect</groupId> | ||||
|                 <artifactId>moditect-maven-plugin</artifactId> | ||||
|                 <version>1.0.0.Beta2</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>add-module-infos</id> | ||||
|                         <phase>generate-resources</phase> | ||||
|                         <goals> | ||||
|                             <goal>add-module-info</goal> | ||||
|                         </goals> | ||||
|                         <configuration> | ||||
|                             <overwriteExistingFiles>true</overwriteExistingFiles> | ||||
|                             <outputDirectory>${project.build.directory}/modules</outputDirectory> | ||||
|                             <modules> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.unbescape</groupId> | ||||
|                                         <artifactId>unbescape</artifactId> | ||||
|                                         <version>1.1.6.RELEASE</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>org.unbescape.html</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>com.github.mokiat</groupId> | ||||
|                                         <artifactId>java-data-front</artifactId> | ||||
|                                         <version>2.0.0</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>java.data.front</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-controls</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.controls.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-fxml</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.fxml.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-graphics</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.graphics.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-base</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.base.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>net.java.dev.jna</groupId> | ||||
|                                         <artifactId>jna</artifactId> | ||||
|                                         <version>5.6.0</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>com.sun.jna</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.jheaps</groupId> | ||||
|                                         <artifactId>jheaps</artifactId> | ||||
|                                         <version>0.13</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>org.jheaps</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                             </modules> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|         </plugins> | ||||
|     </build> | ||||
|     <repositories> | ||||
|         <repository> | ||||
|             <id>jitpack.io</id> | ||||
|             <name>jitpack</name> | ||||
|             <url>https://jitpack.io</url> | ||||
|         </repository> | ||||
|     </repositories> | ||||
|     <dependencies> | ||||
|         <!-- https://sjoerdvankreel.github.io/xt-audio/ --> | ||||
|         <dependency> | ||||
|             <groupId>com.github.sjoerdvankreel</groupId> | ||||
|             <artifactId>xt.audio</artifactId> | ||||
|             <version>1.9</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.openjfx</groupId> | ||||
|             <artifactId>javafx-fxml</artifactId> | ||||
|             <version>${javafx.version}</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.openjfx</groupId> | ||||
|             <artifactId>javafx-controls</artifactId> | ||||
|             <version>${javafx.version}</version> | ||||
|         </dependency> | ||||
|         <!-- https://mvnrepository.com/artifact/org.unbescape/unbescape --> | ||||
|         <dependency> | ||||
|             <groupId>org.unbescape</groupId> | ||||
|             <artifactId>unbescape</artifactId> | ||||
|             <version>1.1.6.RELEASE</version> | ||||
|         </dependency> | ||||
|         <!-- https://mvnrepository.com/artifact/org.jgrapht/jgrapht-core --> | ||||
|         <dependency> | ||||
|             <groupId>org.jgrapht</groupId> | ||||
|             <artifactId>jgrapht-core</artifactId> | ||||
|             <version>1.5.0</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.github.mokiat</groupId> | ||||
|             <artifactId>java-data-front</artifactId> | ||||
|             <version>2.0.0</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|     </dependencies> | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
| 
 | ||||
|     <groupId>sh.ball</groupId> | ||||
|     <artifactId>osci-render</artifactId> | ||||
|     <version>1.6.1</version> | ||||
| 
 | ||||
|     <name>osci-render</name> | ||||
| 
 | ||||
|     <properties> | ||||
|         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
|         <maven.compiler.release>15</maven.compiler.release> | ||||
|         <javafx.version>16</javafx.version> | ||||
|         <project.modulePath>${project.build.directory}\modules</project.modulePath> | ||||
|         <appModule>oscirender</appModule> | ||||
|         <appMainClass>sh.ball.gui.Gui</appMainClass> | ||||
|     </properties> | ||||
| 
 | ||||
|     <build> | ||||
|         <plugins> | ||||
|             <plugin> | ||||
|                 <artifactId>maven-clean-plugin</artifactId> | ||||
|                 <version>2.4.1</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>auto-clean</id> | ||||
|                         <phase>initialize</phase> | ||||
|                         <goals> | ||||
|                             <goal>clean</goal> | ||||
|                         </goals> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <artifactId>maven-jar-plugin</artifactId> | ||||
|                 <version>3.2.0</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>default-jar</id> | ||||
|                         <phase>none</phase> | ||||
|                         <configuration> | ||||
|                             <finalName>unwanted</finalName> | ||||
|                             <classifier>unwanted</classifier> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <artifactId>maven-assembly-plugin</artifactId> | ||||
|                 <configuration> | ||||
|                     <archive> | ||||
|                         <manifest> | ||||
|                             <mainClass>sh.ball.gui.Gui</mainClass> | ||||
|                         </manifest> | ||||
|                     </archive> | ||||
|                     <descriptorRefs> | ||||
|                         <descriptorRef>jar-with-dependencies</descriptorRef> | ||||
|                     </descriptorRefs> | ||||
|                     <appendAssemblyId>false</appendAssemblyId> | ||||
|                 </configuration> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>make-assembly</id> | ||||
|                         <phase>package</phase> | ||||
|                         <goals> | ||||
|                             <goal>single</goal> | ||||
|                         </goals> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-dependency-plugin</artifactId> | ||||
|                 <version>3.1.2</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>copy-dependencies</id> | ||||
|                         <phase>generate-sources</phase> | ||||
|                         <goals> | ||||
|                             <goal>copy-dependencies</goal> | ||||
|                         </goals> | ||||
|                         <configuration> | ||||
|                             <outputDirectory>${project.build.directory}/modules</outputDirectory> | ||||
|                             <overWriteReleases>true</overWriteReleases> | ||||
|                             <overWriteSnapshots>true</overWriteSnapshots> | ||||
|                             <overWriteIfNewer>true</overWriteIfNewer> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-compiler-plugin</artifactId> | ||||
|                 <version>3.8.1</version> | ||||
|                 <configuration> | ||||
|                     <source>${maven.compiler.release}</source> | ||||
|                     <target>${maven.compiler.release}</target> | ||||
|                     <compilerArgs> | ||||
|                         <compilerArg>--enable-preview</compilerArg> | ||||
|                         <compilerArg>--module-path</compilerArg> | ||||
|                         <compilerArg>${project.modulePath}</compilerArg> | ||||
|                     </compilerArgs> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.panteleyev</groupId> | ||||
|                 <artifactId>jpackage-maven-plugin</artifactId> | ||||
|                 <version>1.4.0</version> | ||||
| 
 | ||||
|                 <configuration> | ||||
|                     <name>osci-render</name> | ||||
|                     <appVersion>${project.version}</appVersion> | ||||
|                     <vendor>james.ball.sh</vendor> | ||||
|                     <module>${appModule}/${appMainClass}</module> | ||||
|                     <modulePath>${project.build.directory}/modules;${project.build.directory}/classes</modulePath> | ||||
|                     <destination>${project.build.directory}</destination> | ||||
|                     <javaOptions> | ||||
|                         <option>--enable-preview</option> | ||||
|                         <option>-Dfile.encoding=UTF-8</option> | ||||
|                     </javaOptions> | ||||
|                 </configuration> | ||||
| 
 | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>win</id> | ||||
|                         <configuration> | ||||
|                             <icon>src/main/resources/icons/icon.ico</icon> | ||||
|                             <winMenu>true</winMenu> | ||||
|                             <winMenuGroup>osci-render</winMenuGroup> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                     <execution> | ||||
|                         <id>debian</id> | ||||
|                         <configuration> | ||||
|                             <type>deb</type> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.moditect</groupId> | ||||
|                 <artifactId>moditect-maven-plugin</artifactId> | ||||
|                 <version>1.0.0.Beta2</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>add-module-infos</id> | ||||
|                         <phase>generate-resources</phase> | ||||
|                         <goals> | ||||
|                             <goal>add-module-info</goal> | ||||
|                         </goals> | ||||
|                         <configuration> | ||||
|                             <overwriteExistingFiles>true</overwriteExistingFiles> | ||||
|                             <outputDirectory>${project.build.directory}/modules</outputDirectory> | ||||
|                             <modules> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.unbescape</groupId> | ||||
|                                         <artifactId>unbescape</artifactId> | ||||
|                                         <version>1.1.6.RELEASE</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>org.unbescape.html</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>com.github.mokiat</groupId> | ||||
|                                         <artifactId>java-data-front</artifactId> | ||||
|                                         <version>2.0.0</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>java.data.front</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-controls</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.controls.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-fxml</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.fxml.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-graphics</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.graphics.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.openjfx</groupId> | ||||
|                                         <artifactId>javafx-base</artifactId> | ||||
|                                         <version>${javafx.version}</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>javafx.base.exposed</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>net.java.dev.jna</groupId> | ||||
|                                         <artifactId>jna</artifactId> | ||||
|                                         <version>5.6.0</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>com.sun.jna</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                                 <module> | ||||
|                                     <artifact> | ||||
|                                         <groupId>org.jheaps</groupId> | ||||
|                                         <artifactId>jheaps</artifactId> | ||||
|                                         <version>0.13</version> | ||||
|                                     </artifact> | ||||
|                                     <moduleInfo> | ||||
|                                         <name>org.jheaps</name> | ||||
|                                     </moduleInfo> | ||||
|                                 </module> | ||||
|                             </modules> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|         </plugins> | ||||
|     </build> | ||||
|     <repositories> | ||||
|         <repository> | ||||
|             <id>jitpack.io</id> | ||||
|             <name>jitpack</name> | ||||
|             <url>https://jitpack.io</url> | ||||
|         </repository> | ||||
|     </repositories> | ||||
|     <dependencies> | ||||
|         <!-- https://sjoerdvankreel.github.io/xt-audio/ --> | ||||
|         <dependency> | ||||
|             <groupId>com.github.sjoerdvankreel</groupId> | ||||
|             <artifactId>xt.audio</artifactId> | ||||
|             <version>1.9</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.openjfx</groupId> | ||||
|             <artifactId>javafx-fxml</artifactId> | ||||
|             <version>${javafx.version}</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.openjfx</groupId> | ||||
|             <artifactId>javafx-controls</artifactId> | ||||
|             <version>${javafx.version}</version> | ||||
|         </dependency> | ||||
|         <!-- https://mvnrepository.com/artifact/org.unbescape/unbescape --> | ||||
|         <dependency> | ||||
|             <groupId>org.unbescape</groupId> | ||||
|             <artifactId>unbescape</artifactId> | ||||
|             <version>1.1.6.RELEASE</version> | ||||
|         </dependency> | ||||
|         <!-- https://mvnrepository.com/artifact/org.jgrapht/jgrapht-core --> | ||||
|         <dependency> | ||||
|             <groupId>org.jgrapht</groupId> | ||||
|             <artifactId>jgrapht-core</artifactId> | ||||
|             <version>1.5.0</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.github.mokiat</groupId> | ||||
|             <artifactId>java-data-front</artifactId> | ||||
|             <version>2.0.0</version> | ||||
|         </dependency> | ||||
| 
 | ||||
|     </dependencies> | ||||
| </project> | ||||
|  | @ -1,3 +1,3 @@ | |||
| Manifest-Version: 1.0 | ||||
| Main-Class: sh.ball.gui.Gui | ||||
| 
 | ||||
| Manifest-Version: 1.0 | ||||
| Main-Class: sh.ball.gui.Gui | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,17 +1,17 @@ | |||
| module oscirender { | ||||
|   requires javafx.controls; | ||||
|   requires javafx.controls.exposed; | ||||
|   requires javafx.graphics; | ||||
|   requires javafx.graphics.exposed; | ||||
|   requires javafx.fxml; | ||||
|   requires javafx.fxml.exposed; | ||||
|   requires javafx.base.exposed; | ||||
|   requires xt.audio; | ||||
|   requires java.xml; | ||||
|   requires java.data.front; | ||||
|   requires org.jgrapht.core; | ||||
|   requires org.unbescape.html; | ||||
|   requires java.desktop; | ||||
| 
 | ||||
|   opens sh.ball.gui; | ||||
| module oscirender { | ||||
|   requires javafx.controls; | ||||
|   requires javafx.controls.exposed; | ||||
|   requires javafx.graphics; | ||||
|   requires javafx.graphics.exposed; | ||||
|   requires javafx.fxml; | ||||
|   requires javafx.fxml.exposed; | ||||
|   requires javafx.base.exposed; | ||||
|   requires xt.audio; | ||||
|   requires java.xml; | ||||
|   requires java.data.front; | ||||
|   requires org.jgrapht.core; | ||||
|   requires org.unbescape.html; | ||||
|   requires java.desktop; | ||||
| 
 | ||||
|   opens sh.ball.gui; | ||||
| } | ||||
|  | @ -1,7 +1,7 @@ | |||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public interface Effect { | ||||
|   Vector2 apply(int count, Vector2 vector); | ||||
| } | ||||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public interface Effect { | ||||
|   Vector2 apply(int count, Vector2 vector); | ||||
| } | ||||
|  |  | |||
|  | @ -1,30 +1,30 @@ | |||
| package sh.ball.audio; | ||||
| 
 | ||||
| public class FrameProducer<S, T> implements Runnable { | ||||
| 
 | ||||
|   private final Renderer<S, T> renderer; | ||||
|   private final FrameSet<S> frames; | ||||
| 
 | ||||
|   private boolean running; | ||||
| 
 | ||||
|   public FrameProducer(Renderer<S, T> renderer, FrameSet<S> frames) { | ||||
|     this.renderer = renderer; | ||||
|     this.frames = frames; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void run() { | ||||
|     running = true; | ||||
|     while (running) { | ||||
|       renderer.addFrame(frames.next()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public void stop() { | ||||
|     running = false; | ||||
|   } | ||||
| 
 | ||||
|   public void setFrameSettings(Object settings) { | ||||
|     frames.setFrameSettings(settings); | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio; | ||||
| 
 | ||||
| public class FrameProducer<S, T> implements Runnable { | ||||
| 
 | ||||
|   private final Renderer<S, T> renderer; | ||||
|   private final FrameSet<S> frames; | ||||
| 
 | ||||
|   private boolean running; | ||||
| 
 | ||||
|   public FrameProducer(Renderer<S, T> renderer, FrameSet<S> frames) { | ||||
|     this.renderer = renderer; | ||||
|     this.frames = frames; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void run() { | ||||
|     running = true; | ||||
|     while (running) { | ||||
|       renderer.addFrame(frames.next()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public void stop() { | ||||
|     running = false; | ||||
|   } | ||||
| 
 | ||||
|   public void setFrameSettings(Object settings) { | ||||
|     frames.setFrameSettings(settings); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.parser.obj.Listener; | ||||
| 
 | ||||
| public interface FrameSet<T> { | ||||
| 
 | ||||
|   T next(); | ||||
| 
 | ||||
|   void setFrameSettings(Object settings); | ||||
| 
 | ||||
|   void addListener(Listener listener); | ||||
| } | ||||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.parser.obj.Listener; | ||||
| 
 | ||||
| public interface FrameSet<T> { | ||||
| 
 | ||||
|   T next(); | ||||
| 
 | ||||
|   void setFrameSettings(Object settings); | ||||
| 
 | ||||
|   void addListener(Listener listener); | ||||
| } | ||||
|  |  | |||
|  | @ -1,105 +1,105 @@ | |||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.audio.fft.FFT; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class FrequencyAnalyser<S, T> implements Runnable { | ||||
| 
 | ||||
|   private static final float NORMALIZATION_FACTOR_2_BYTES = Short.MAX_VALUE + 1.0f; | ||||
| 
 | ||||
|   private final Renderer<S, T> renderer; | ||||
|   private final List<FrequencyListener> listeners = new ArrayList<>(); | ||||
|   private final int frameSize; | ||||
|   private final int sampleRate; | ||||
| 
 | ||||
|   public FrequencyAnalyser(Renderer<S, T> renderer, int frameSize, int sampleRate) { | ||||
|     this.renderer = renderer; | ||||
|     this.frameSize = frameSize; | ||||
|     this.sampleRate = sampleRate; | ||||
|   } | ||||
| 
 | ||||
|   public void addListener(FrequencyListener listener) { | ||||
|     listeners.add(listener); | ||||
|   } | ||||
| 
 | ||||
|   private void notifyListeners(double leftFrequency, double rightFrequency) { | ||||
|     for (FrequencyListener listener : listeners) { | ||||
|       listener.updateFrequency(leftFrequency, rightFrequency); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Adapted from https://stackoverflow.com/questions/53997426/java-how-to-get-current-frequency-of-audio-input
 | ||||
|   @Override | ||||
|   public void run() { | ||||
|     byte[] buf = new byte[2 << 18]; // <--- increase this for higher frequency resolution
 | ||||
| 
 | ||||
|     while (true) { | ||||
|       try { | ||||
|         renderer.read(buf); | ||||
|       } catch (InterruptedException e) { | ||||
|         e.printStackTrace(); | ||||
|       } | ||||
|       double[] leftSamples = decode(buf, true); | ||||
|       double[] rightSamples = decode(buf, false); | ||||
| 
 | ||||
|       FFT leftFft = new FFT(leftSamples, null, false, true); | ||||
|       FFT rightFft = new FFT(rightSamples, null, false, true); | ||||
| 
 | ||||
|       double[] leftMags = leftFft.getMagnitudeSpectrum(); | ||||
|       double[] rightMags = rightFft.getMagnitudeSpectrum(); | ||||
|       double[] bins = leftFft.getBinLabels(sampleRate); | ||||
| 
 | ||||
|       int maxLeftIndex = 0; | ||||
|       double maxLeft = Double.NEGATIVE_INFINITY; | ||||
|       int maxRightIndex = 0; | ||||
|       double maxRight = Double.NEGATIVE_INFINITY; | ||||
|       for (int i = 1; i < leftMags.length; i++) { | ||||
|         if (bins[i] < 20 || bins[i] > 20000) { | ||||
|           continue; | ||||
|         } | ||||
|         if (leftMags[i] > maxLeft) { | ||||
|           maxLeftIndex = i; | ||||
|           maxLeft = leftMags[i]; | ||||
|         } | ||||
|         if (rightMags[i] > maxRight) { | ||||
|           maxRightIndex = i; | ||||
|           maxRight = rightMags[i]; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       notifyListeners(bins[maxLeftIndex], bins[maxRightIndex]); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private double[] decode(final byte[] buf, boolean decodeLeft) { | ||||
|     final double[] fbuf = new double[(buf.length / 2) / frameSize]; | ||||
|     int byteNum = 0; | ||||
|     int i = 0; | ||||
|     for (int pos = 0; pos < buf.length; pos += frameSize) { | ||||
|       int sample = byteToIntLittleEndian(buf, pos, frameSize); | ||||
|       // normalize to [0,1] (not strictly necessary, but makes things easier)
 | ||||
|       double normalSample =  sample / NORMALIZATION_FACTOR_2_BYTES; | ||||
|       if (decodeLeft) { | ||||
|         if (byteNum < 2) { | ||||
|           fbuf[i++] = normalSample; | ||||
|         } | ||||
|       } else if (byteNum >= 2) { | ||||
|         fbuf[i++] = normalSample; | ||||
|       } | ||||
|       byteNum++; | ||||
|       byteNum %= 4; | ||||
|     } | ||||
|     return fbuf; | ||||
|   } | ||||
| 
 | ||||
|   private static int byteToIntLittleEndian(final byte[] buf, final int offset, final int bytesPerSample) { | ||||
|     int sample = 0; | ||||
|     for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) { | ||||
|       final int aByte = buf[offset + byteIndex] & 0xff; | ||||
|       sample += aByte << 8 * (byteIndex); | ||||
|     } | ||||
|     return sample; | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.audio.fft.FFT; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class FrequencyAnalyser<S, T> implements Runnable { | ||||
| 
 | ||||
|   private static final float NORMALIZATION_FACTOR_2_BYTES = Short.MAX_VALUE + 1.0f; | ||||
| 
 | ||||
|   private final Renderer<S, T> renderer; | ||||
|   private final List<FrequencyListener> listeners = new ArrayList<>(); | ||||
|   private final int frameSize; | ||||
|   private final int sampleRate; | ||||
| 
 | ||||
|   public FrequencyAnalyser(Renderer<S, T> renderer, int frameSize, int sampleRate) { | ||||
|     this.renderer = renderer; | ||||
|     this.frameSize = frameSize; | ||||
|     this.sampleRate = sampleRate; | ||||
|   } | ||||
| 
 | ||||
|   public void addListener(FrequencyListener listener) { | ||||
|     listeners.add(listener); | ||||
|   } | ||||
| 
 | ||||
|   private void notifyListeners(double leftFrequency, double rightFrequency) { | ||||
|     for (FrequencyListener listener : listeners) { | ||||
|       listener.updateFrequency(leftFrequency, rightFrequency); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Adapted from https://stackoverflow.com/questions/53997426/java-how-to-get-current-frequency-of-audio-input
 | ||||
|   @Override | ||||
|   public void run() { | ||||
|     byte[] buf = new byte[2 << 18]; // <--- increase this for higher frequency resolution
 | ||||
| 
 | ||||
|     while (true) { | ||||
|       try { | ||||
|         renderer.read(buf); | ||||
|       } catch (InterruptedException e) { | ||||
|         e.printStackTrace(); | ||||
|       } | ||||
|       double[] leftSamples = decode(buf, true); | ||||
|       double[] rightSamples = decode(buf, false); | ||||
| 
 | ||||
|       FFT leftFft = new FFT(leftSamples, null, false, true); | ||||
|       FFT rightFft = new FFT(rightSamples, null, false, true); | ||||
| 
 | ||||
|       double[] leftMags = leftFft.getMagnitudeSpectrum(); | ||||
|       double[] rightMags = rightFft.getMagnitudeSpectrum(); | ||||
|       double[] bins = leftFft.getBinLabels(sampleRate); | ||||
| 
 | ||||
|       int maxLeftIndex = 0; | ||||
|       double maxLeft = Double.NEGATIVE_INFINITY; | ||||
|       int maxRightIndex = 0; | ||||
|       double maxRight = Double.NEGATIVE_INFINITY; | ||||
|       for (int i = 1; i < leftMags.length; i++) { | ||||
|         if (bins[i] < 20 || bins[i] > 20000) { | ||||
|           continue; | ||||
|         } | ||||
|         if (leftMags[i] > maxLeft) { | ||||
|           maxLeftIndex = i; | ||||
|           maxLeft = leftMags[i]; | ||||
|         } | ||||
|         if (rightMags[i] > maxRight) { | ||||
|           maxRightIndex = i; | ||||
|           maxRight = rightMags[i]; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       notifyListeners(bins[maxLeftIndex], bins[maxRightIndex]); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private double[] decode(final byte[] buf, boolean decodeLeft) { | ||||
|     final double[] fbuf = new double[(buf.length / 2) / frameSize]; | ||||
|     int byteNum = 0; | ||||
|     int i = 0; | ||||
|     for (int pos = 0; pos < buf.length; pos += frameSize) { | ||||
|       int sample = byteToIntLittleEndian(buf, pos, frameSize); | ||||
|       // normalize to [0,1] (not strictly necessary, but makes things easier)
 | ||||
|       double normalSample =  sample / NORMALIZATION_FACTOR_2_BYTES; | ||||
|       if (decodeLeft) { | ||||
|         if (byteNum < 2) { | ||||
|           fbuf[i++] = normalSample; | ||||
|         } | ||||
|       } else if (byteNum >= 2) { | ||||
|         fbuf[i++] = normalSample; | ||||
|       } | ||||
|       byteNum++; | ||||
|       byteNum %= 4; | ||||
|     } | ||||
|     return fbuf; | ||||
|   } | ||||
| 
 | ||||
|   private static int byteToIntLittleEndian(final byte[] buf, final int offset, final int bytesPerSample) { | ||||
|     int sample = 0; | ||||
|     for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) { | ||||
|       final int aByte = buf[offset + byteIndex] & 0xff; | ||||
|       sample += aByte << 8 * (byteIndex); | ||||
|     } | ||||
|     return sample; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package sh.ball.audio; | ||||
| 
 | ||||
| public interface FrequencyListener { | ||||
| 
 | ||||
|   void updateFrequency(double leftFrequency, double rightFrequency); | ||||
| } | ||||
| package sh.ball.audio; | ||||
| 
 | ||||
| public interface FrequencyListener { | ||||
| 
 | ||||
|   void updateFrequency(double leftFrequency, double rightFrequency); | ||||
| } | ||||
|  |  | |||
|  | @ -1,22 +1,22 @@ | |||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.audio.effect.Effect; | ||||
| 
 | ||||
| public interface Renderer<S, T> extends Runnable { | ||||
| 
 | ||||
|   void stop(); | ||||
| 
 | ||||
|   void setQuality(double quality); | ||||
| 
 | ||||
|   void addFrame(S frame); | ||||
| 
 | ||||
|   void addEffect(Object identifier, Effect effect); | ||||
| 
 | ||||
|   void removeEffect(Object identifier); | ||||
| 
 | ||||
|   void read(byte[] buffer) throws InterruptedException; | ||||
| 
 | ||||
|   void startRecord(); | ||||
| 
 | ||||
|   T stopRecord(); | ||||
| } | ||||
| package sh.ball.audio; | ||||
| 
 | ||||
| import sh.ball.audio.effect.Effect; | ||||
| 
 | ||||
| public interface Renderer<S, T> extends Runnable { | ||||
| 
 | ||||
|   void stop(); | ||||
| 
 | ||||
|   void setQuality(double quality); | ||||
| 
 | ||||
|   void addFrame(S frame); | ||||
| 
 | ||||
|   void addEffect(Object identifier, Effect effect); | ||||
| 
 | ||||
|   void removeEffect(Object identifier); | ||||
| 
 | ||||
|   void read(byte[] buffer) throws InterruptedException; | ||||
| 
 | ||||
|   void startRecord(); | ||||
| 
 | ||||
|   T stopRecord(); | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public interface Effect { | ||||
|   Vector2 apply(int count, Vector2 vector); | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public interface Effect { | ||||
|   Vector2 apply(int count, Vector2 vector); | ||||
| } | ||||
|  |  | |||
|  | @ -1,46 +1,46 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class EffectFactory { | ||||
|   public static Effect vectorCancelling(int frequency) { | ||||
|     return (count, v) -> count % frequency == 0 ? v.scale(-1) : v; | ||||
|   } | ||||
| 
 | ||||
|   public static Effect bitCrush(double value) { | ||||
|     return (count, v) -> { | ||||
|       double x = v.getX(); | ||||
|       double y = v.getY(); | ||||
|       return new Vector2(round(x, value), round(y, value)); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   private static double round(double value, double places) { | ||||
|     if (places < 0) throw new IllegalArgumentException(); | ||||
| 
 | ||||
|     long factor = (long) Math.pow(10, places); | ||||
|     value = value * factor; | ||||
|     long tmp = Math.round(value); | ||||
|     return (double) tmp / factor; | ||||
|   } | ||||
| 
 | ||||
|   public static Effect horizontalDistort(double value) { | ||||
|     return (count, v) -> { | ||||
|       if (count % 2 == 0) { | ||||
|         return v.translate(new Vector2(value, 0)); | ||||
|       } else { | ||||
|         return v.translate(new Vector2(-value, 0)); | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   public static Effect verticalDistort(double value) { | ||||
|     return (count, v) -> { | ||||
|       if (count % 2 == 0) { | ||||
|         return v.translate(new Vector2(0, value)); | ||||
|       } else { | ||||
|         return v.translate(new Vector2(0, -value)); | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class EffectFactory { | ||||
|   public static Effect vectorCancelling(int frequency) { | ||||
|     return (count, v) -> count % frequency == 0 ? v.scale(-1) : v; | ||||
|   } | ||||
| 
 | ||||
|   public static Effect bitCrush(double value) { | ||||
|     return (count, v) -> { | ||||
|       double x = v.getX(); | ||||
|       double y = v.getY(); | ||||
|       return new Vector2(round(x, value), round(y, value)); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   private static double round(double value, double places) { | ||||
|     if (places < 0) throw new IllegalArgumentException(); | ||||
| 
 | ||||
|     long factor = (long) Math.pow(10, places); | ||||
|     value = value * factor; | ||||
|     long tmp = Math.round(value); | ||||
|     return (double) tmp / factor; | ||||
|   } | ||||
| 
 | ||||
|   public static Effect horizontalDistort(double value) { | ||||
|     return (count, v) -> { | ||||
|       if (count % 2 == 0) { | ||||
|         return v.translate(new Vector2(value, 0)); | ||||
|       } else { | ||||
|         return v.translate(new Vector2(-value, 0)); | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   public static Effect verticalDistort(double value) { | ||||
|     return (count, v) -> { | ||||
|       if (count % 2 == 0) { | ||||
|         return v.translate(new Vector2(0, value)); | ||||
|       } else { | ||||
|         return v.translate(new Vector2(0, -value)); | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| public enum EffectType { | ||||
|   VECTOR_CANCELLING, | ||||
|   BIT_CRUSH, | ||||
|   SCALE, | ||||
|   ROTATE, | ||||
|   TRANSLATE, | ||||
|   VERTICAL_DISTORT, | ||||
|   HORIZONTAL_DISTORT, | ||||
|   WOBBLE | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| public enum EffectType { | ||||
|   VECTOR_CANCELLING, | ||||
|   BIT_CRUSH, | ||||
|   SCALE, | ||||
|   ROTATE, | ||||
|   TRANSLATE, | ||||
|   VERTICAL_DISTORT, | ||||
|   HORIZONTAL_DISTORT, | ||||
|   WOBBLE | ||||
| } | ||||
|  |  | |||
|  | @ -1,30 +1,30 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| public abstract class PhaseEffect implements Effect { | ||||
| 
 | ||||
|   private static final double LARGE_VAL = 2 << 20; | ||||
| 
 | ||||
|   protected final int sampleRate; | ||||
| 
 | ||||
|   protected double speed; | ||||
|   private double phase = -LARGE_VAL; | ||||
| 
 | ||||
|   protected PhaseEffect(int sampleRate, double speed) { | ||||
|     this.sampleRate = sampleRate; | ||||
|     this.speed = speed; | ||||
|   } | ||||
| 
 | ||||
|   protected double nextTheta() { | ||||
|     phase += speed / sampleRate; | ||||
| 
 | ||||
|     if (phase >= LARGE_VAL) { | ||||
|       phase = -LARGE_VAL; | ||||
|     } | ||||
| 
 | ||||
|     return phase * Math.PI; | ||||
|   } | ||||
| 
 | ||||
|   public void setSpeed(double speed) { | ||||
|     this.speed = speed; | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| public abstract class PhaseEffect implements Effect { | ||||
| 
 | ||||
|   private static final double LARGE_VAL = 2 << 20; | ||||
| 
 | ||||
|   protected final int sampleRate; | ||||
| 
 | ||||
|   protected double speed; | ||||
|   private double phase = -LARGE_VAL; | ||||
| 
 | ||||
|   protected PhaseEffect(int sampleRate, double speed) { | ||||
|     this.sampleRate = sampleRate; | ||||
|     this.speed = speed; | ||||
|   } | ||||
| 
 | ||||
|   protected double nextTheta() { | ||||
|     phase += speed / sampleRate; | ||||
| 
 | ||||
|     if (phase >= LARGE_VAL) { | ||||
|       phase = -LARGE_VAL; | ||||
|     } | ||||
| 
 | ||||
|     return phase * Math.PI; | ||||
|   } | ||||
| 
 | ||||
|   public void setSpeed(double speed) { | ||||
|     this.speed = speed; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,23 +1,23 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class RotateEffect extends PhaseEffect { | ||||
| 
 | ||||
|   public RotateEffect(int sampleRate, double speed) { | ||||
|     super(sampleRate, speed); | ||||
|   } | ||||
| 
 | ||||
|   public RotateEffect(int sampleRate) { | ||||
|     this(sampleRate, 0); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     if (speed != 0) { | ||||
|       return vector.rotate(nextTheta()); | ||||
|     } | ||||
| 
 | ||||
|     return vector; | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class RotateEffect extends PhaseEffect { | ||||
| 
 | ||||
|   public RotateEffect(int sampleRate, double speed) { | ||||
|     super(sampleRate, speed); | ||||
|   } | ||||
| 
 | ||||
|   public RotateEffect(int sampleRate) { | ||||
|     this(sampleRate, 0); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     if (speed != 0) { | ||||
|       return vector.rotate(nextTheta()); | ||||
|     } | ||||
| 
 | ||||
|     return vector; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,25 +1,25 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class ScaleEffect implements Effect { | ||||
| 
 | ||||
|   private double scale; | ||||
| 
 | ||||
|   public ScaleEffect(double scale) { | ||||
|     this.scale = scale; | ||||
|   } | ||||
| 
 | ||||
|   public ScaleEffect() { | ||||
|     this(1); | ||||
|   } | ||||
| 
 | ||||
|   public void setScale(double scale) { | ||||
|     this.scale = scale; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     return vector.scale(scale); | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class ScaleEffect implements Effect { | ||||
| 
 | ||||
|   private double scale; | ||||
| 
 | ||||
|   public ScaleEffect(double scale) { | ||||
|     this.scale = scale; | ||||
|   } | ||||
| 
 | ||||
|   public ScaleEffect() { | ||||
|     this(1); | ||||
|   } | ||||
| 
 | ||||
|   public void setScale(double scale) { | ||||
|     this.scale = scale; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     return vector.scale(scale); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,30 +1,30 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class TranslateEffect extends PhaseEffect { | ||||
| 
 | ||||
|   private Vector2 translation; | ||||
| 
 | ||||
|   public TranslateEffect(int sampleRate, double speed, Vector2 translation) { | ||||
|     super(sampleRate, speed); | ||||
|     this.translation = translation; | ||||
|   } | ||||
| 
 | ||||
|   public TranslateEffect(int sampleRate) { | ||||
|     this(sampleRate, 0, new Vector2()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     if (speed != 0 && !translation.equals(new Vector2())) { | ||||
|       return vector.translate(translation.scale(Math.sin(nextTheta()))); | ||||
|     } | ||||
| 
 | ||||
|     return vector; | ||||
|   } | ||||
| 
 | ||||
|   public void setTranslation(Vector2 translation) { | ||||
|     this.translation = translation; | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class TranslateEffect extends PhaseEffect { | ||||
| 
 | ||||
|   private Vector2 translation; | ||||
| 
 | ||||
|   public TranslateEffect(int sampleRate, double speed, Vector2 translation) { | ||||
|     super(sampleRate, speed); | ||||
|     this.translation = translation; | ||||
|   } | ||||
| 
 | ||||
|   public TranslateEffect(int sampleRate) { | ||||
|     this(sampleRate, 0, new Vector2()); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     if (speed != 0 && !translation.equals(new Vector2())) { | ||||
|       return vector.translate(translation.scale(Math.sin(nextTheta()))); | ||||
|     } | ||||
| 
 | ||||
|     return vector; | ||||
|   } | ||||
| 
 | ||||
|   public void setTranslation(Vector2 translation) { | ||||
|     this.translation = translation; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,44 +1,44 @@ | |||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.audio.FrequencyListener; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class WobbleEffect extends PhaseEffect implements FrequencyListener { | ||||
| 
 | ||||
|   private static final double DEFAULT_VOLUME = 0.2; | ||||
| 
 | ||||
|   private double frequency; | ||||
|   private double lastFrequency; | ||||
|   private double volume; | ||||
| 
 | ||||
|   public WobbleEffect(int sampleRate, double volume) { | ||||
|     super(sampleRate, 2); | ||||
|     this.volume = Math.max(Math.min(volume, 1), 0); | ||||
|   } | ||||
| 
 | ||||
|   public WobbleEffect(int sampleRate) { | ||||
|     this(sampleRate, DEFAULT_VOLUME); | ||||
|   } | ||||
| 
 | ||||
|   public void update() { | ||||
|     frequency = lastFrequency; | ||||
|   } | ||||
| 
 | ||||
|   public void setVolume(double volume) { | ||||
|     this.volume = volume; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void updateFrequency(double leftFrequency, double rightFrequency) { | ||||
|     lastFrequency = leftFrequency; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     double theta = nextTheta(); | ||||
|     double x = vector.getX() + volume * Math.sin(frequency * theta); | ||||
|     double y = vector.getY() + volume * Math.sin(frequency * theta); | ||||
| 
 | ||||
|     return new Vector2(x, y); | ||||
|   } | ||||
| } | ||||
| package sh.ball.audio.effect; | ||||
| 
 | ||||
| import sh.ball.audio.FrequencyListener; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class WobbleEffect extends PhaseEffect implements FrequencyListener { | ||||
| 
 | ||||
|   private static final double DEFAULT_VOLUME = 0.2; | ||||
| 
 | ||||
|   private double frequency; | ||||
|   private double lastFrequency; | ||||
|   private double volume; | ||||
| 
 | ||||
|   public WobbleEffect(int sampleRate, double volume) { | ||||
|     super(sampleRate, 2); | ||||
|     this.volume = Math.max(Math.min(volume, 1), 0); | ||||
|   } | ||||
| 
 | ||||
|   public WobbleEffect(int sampleRate) { | ||||
|     this(sampleRate, DEFAULT_VOLUME); | ||||
|   } | ||||
| 
 | ||||
|   public void update() { | ||||
|     frequency = lastFrequency; | ||||
|   } | ||||
| 
 | ||||
|   public void setVolume(double volume) { | ||||
|     this.volume = volume; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void updateFrequency(double leftFrequency, double rightFrequency) { | ||||
|     lastFrequency = leftFrequency; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 apply(int count, Vector2 vector) { | ||||
|     double theta = nextTheta(); | ||||
|     double x = vector.getX() + volume * Math.sin(frequency * theta); | ||||
|     double y = vector.getY() + volume * Math.sin(frequency * theta); | ||||
| 
 | ||||
|     return new Vector2(x, y); | ||||
|   } | ||||
| } | ||||
|  |  | |||
										
											
												Plik diff jest za duży
												Load Diff
											
										
									
								
							|  | @ -1,47 +1,47 @@ | |||
| package sh.ball.engine; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| public class Line3D { | ||||
| 
 | ||||
|   private final Vector3 start; | ||||
|   private final Vector3 end; | ||||
| 
 | ||||
|   public Line3D(Vector3 start, Vector3 end) { | ||||
|     this.start = start; | ||||
|     this.end = end; | ||||
|   } | ||||
| 
 | ||||
|   public Vector3 getStart() { | ||||
|     return start; | ||||
|   } | ||||
| 
 | ||||
|   public Vector3 getEnd() { | ||||
|     return end; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean equals(Object o) { | ||||
|     if (this == o) { | ||||
|       return true; | ||||
|     } | ||||
|     if (o == null || getClass() != o.getClass()) { | ||||
|       return false; | ||||
|     } | ||||
|     Line3D line3D = (Line3D) o; | ||||
|     return Objects.equals(start, line3D.start) && Objects.equals(end, line3D.end); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int hashCode() { | ||||
|     return Objects.hash(start, end); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Line3D{" + | ||||
|       "start=" + start + | ||||
|       ", end=" + end + | ||||
|       '}'; | ||||
|   } | ||||
| } | ||||
| package sh.ball.engine; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| public class Line3D { | ||||
| 
 | ||||
|   private final Vector3 start; | ||||
|   private final Vector3 end; | ||||
| 
 | ||||
|   public Line3D(Vector3 start, Vector3 end) { | ||||
|     this.start = start; | ||||
|     this.end = end; | ||||
|   } | ||||
| 
 | ||||
|   public Vector3 getStart() { | ||||
|     return start; | ||||
|   } | ||||
| 
 | ||||
|   public Vector3 getEnd() { | ||||
|     return end; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean equals(Object o) { | ||||
|     if (this == o) { | ||||
|       return true; | ||||
|     } | ||||
|     if (o == null || getClass() != o.getClass()) { | ||||
|       return false; | ||||
|     } | ||||
|     Line3D line3D = (Line3D) o; | ||||
|     return Objects.equals(start, line3D.start) && Objects.equals(end, line3D.end); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public int hashCode() { | ||||
|     return Objects.hash(start, end); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String toString() { | ||||
|     return "Line3D{" + | ||||
|       "start=" + start + | ||||
|       ", end=" + end + | ||||
|       '}'; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,381 +1,390 @@ | |||
| package sh.ball.gui; | ||||
| 
 | ||||
| import javafx.animation.KeyFrame; | ||||
| import javafx.animation.Timeline; | ||||
| import javafx.application.Platform; | ||||
| import javafx.scene.control.*; | ||||
| import javafx.util.Duration; | ||||
| import sh.ball.audio.*; | ||||
| import sh.ball.audio.effect.*; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.URL; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.ResourceBundle; | ||||
| import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.Executors; | ||||
| import java.util.function.Consumer; | ||||
| 
 | ||||
| import javafx.beans.InvalidationListener; | ||||
| import javafx.fxml.FXML; | ||||
| import javafx.fxml.Initializable; | ||||
| import javafx.stage.FileChooser; | ||||
| import javafx.stage.Stage; | ||||
| 
 | ||||
| import javax.sound.sampled.AudioFileFormat; | ||||
| import javax.sound.sampled.AudioInputStream; | ||||
| import javax.sound.sampled.AudioSystem; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.effect.Effect; | ||||
| import sh.ball.audio.effect.EffectType; | ||||
| import sh.ball.engine.Vector3; | ||||
| import sh.ball.parser.obj.Listener; | ||||
| import sh.ball.parser.obj.ObjSettingsFactory; | ||||
| import sh.ball.parser.obj.ObjParser; | ||||
| import sh.ball.parser.ParserFactory; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class Controller implements Initializable, FrequencyListener, Listener { | ||||
| 
 | ||||
|   private static final int SAMPLE_RATE = 192000; | ||||
|   private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj"); | ||||
| 
 | ||||
|   private final FileChooser fileChooser = new FileChooser(); | ||||
|   private final Renderer<List<Shape>, AudioInputStream> renderer; | ||||
|   private final ExecutorService executor = Executors.newSingleThreadExecutor(); | ||||
| 
 | ||||
|   private final RotateEffect rotateEffect = new RotateEffect(SAMPLE_RATE); | ||||
|   private final TranslateEffect translateEffect = new TranslateEffect(SAMPLE_RATE); | ||||
|   private final WobbleEffect wobbleEffect = new WobbleEffect(SAMPLE_RATE); | ||||
|   private final ScaleEffect scaleEffect = new ScaleEffect(); | ||||
| 
 | ||||
|   private FrameProducer<List<Shape>, AudioInputStream> producer; | ||||
|   private boolean recording = false; | ||||
| 
 | ||||
|   private Stage stage; | ||||
| 
 | ||||
|   @FXML | ||||
|   private Label frequencyLabel; | ||||
|   @FXML | ||||
|   private Button chooseFileButton; | ||||
|   @FXML | ||||
|   private Label fileLabel; | ||||
|   @FXML | ||||
|   private Button recordButton; | ||||
|   @FXML | ||||
|   private Label recordLabel; | ||||
|   @FXML | ||||
|   private TextField translationXTextField; | ||||
|   @FXML | ||||
|   private TextField translationYTextField; | ||||
|   @FXML | ||||
|   private Slider weightSlider; | ||||
|   @FXML | ||||
|   private Slider rotateSpeedSlider; | ||||
|   @FXML | ||||
|   private Slider translationSpeedSlider; | ||||
|   @FXML | ||||
|   private Slider scaleSlider; | ||||
|   @FXML | ||||
|   private TitledPane objTitledPane; | ||||
|   @FXML | ||||
|   private Slider focalLengthSlider; | ||||
|   @FXML | ||||
|   private TextField cameraXTextField; | ||||
|   @FXML | ||||
|   private TextField cameraYTextField; | ||||
|   @FXML | ||||
|   private TextField cameraZTextField; | ||||
|   @FXML | ||||
|   private Slider objectRotateSpeedSlider; | ||||
|   @FXML | ||||
|   private CheckBox rotateCheckBox; | ||||
|   @FXML | ||||
|   private CheckBox vectorCancellingCheckBox; | ||||
|   @FXML | ||||
|   private Slider vectorCancellingSlider; | ||||
|   @FXML | ||||
|   private CheckBox bitCrushCheckBox; | ||||
|   @FXML | ||||
|   private Slider bitCrushSlider; | ||||
|   @FXML | ||||
|   private CheckBox verticalDistortCheckBox; | ||||
|   @FXML | ||||
|   private Slider verticalDistortSlider; | ||||
|   @FXML | ||||
|   private CheckBox horizontalDistortCheckBox; | ||||
|   @FXML | ||||
|   private Slider horizontalDistortSlider; | ||||
|   @FXML | ||||
|   private CheckBox wobbleCheckBox; | ||||
|   @FXML | ||||
|   private Slider wobbleSlider; | ||||
| 
 | ||||
|   public Controller(Renderer<List<Shape>, AudioInputStream> renderer) throws IOException { | ||||
|     this.renderer = renderer; | ||||
|     FrameSet<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse(); | ||||
|     frames.addListener(this); | ||||
|     this.producer = new FrameProducer<>(renderer, frames); | ||||
|   } | ||||
| 
 | ||||
|   private Map<Slider, Consumer<Double>> initializeSliderMap() { | ||||
|     return Map.of( | ||||
|       weightSlider, | ||||
|       renderer::setQuality, | ||||
|       rotateSpeedSlider, | ||||
|       rotateEffect::setSpeed, | ||||
|       translationSpeedSlider, | ||||
|       translateEffect::setSpeed, | ||||
|       scaleSlider, | ||||
|       scaleEffect::setScale, | ||||
|       focalLengthSlider, | ||||
|       d -> updateFocalLength(), | ||||
|       objectRotateSpeedSlider, | ||||
|       d -> updateObjectRotateSpeed() | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   private Map<EffectType, Slider> effectTypes; | ||||
| 
 | ||||
|   private void initializeEffectTypes() { | ||||
|     effectTypes = Map.of( | ||||
|       EffectType.VECTOR_CANCELLING, | ||||
|       vectorCancellingSlider, | ||||
|       EffectType.BIT_CRUSH, | ||||
|       bitCrushSlider, | ||||
|       EffectType.VERTICAL_DISTORT, | ||||
|       verticalDistortSlider, | ||||
|       EffectType.HORIZONTAL_DISTORT, | ||||
|       horizontalDistortSlider, | ||||
|       EffectType.WOBBLE, | ||||
|       wobbleSlider | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void initialize(URL url, ResourceBundle resourceBundle) { | ||||
|     Map<Slider, Consumer<Double>> sliders = initializeSliderMap(); | ||||
|     initializeEffectTypes(); | ||||
| 
 | ||||
|     for (Slider slider : sliders.keySet()) { | ||||
|       slider.valueProperty().addListener((source, oldValue, newValue) -> | ||||
|         sliders.get(slider).accept(slider.getValue()) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     translationXTextField.textProperty().addListener(e -> updateTranslation()); | ||||
|     translationYTextField.textProperty().addListener(e -> updateTranslation()); | ||||
| 
 | ||||
|     cameraXTextField.focusedProperty().addListener(e -> updateCameraPos()); | ||||
|     cameraYTextField.focusedProperty().addListener(e -> updateCameraPos()); | ||||
|     cameraZTextField.focusedProperty().addListener(e -> updateCameraPos()); | ||||
| 
 | ||||
|     InvalidationListener vectorCancellingListener = e -> | ||||
|       updateEffect(EffectType.VECTOR_CANCELLING, vectorCancellingCheckBox.isSelected(), | ||||
|         EffectFactory.vectorCancelling((int) vectorCancellingSlider.getValue())); | ||||
|     InvalidationListener bitCrushListener = e -> | ||||
|       updateEffect(EffectType.BIT_CRUSH, bitCrushCheckBox.isSelected(), | ||||
|         EffectFactory.bitCrush(bitCrushSlider.getValue())); | ||||
|     InvalidationListener verticalDistortListener = e -> | ||||
|       updateEffect(EffectType.VERTICAL_DISTORT, verticalDistortCheckBox.isSelected(), | ||||
|         EffectFactory.verticalDistort(verticalDistortSlider.getValue())); | ||||
|     InvalidationListener horizontalDistortListener = e -> | ||||
|       updateEffect(EffectType.HORIZONTAL_DISTORT, horizontalDistortCheckBox.isSelected(), | ||||
|         EffectFactory.horizontalDistort(horizontalDistortSlider.getValue())); | ||||
|     InvalidationListener wobbleListener = e -> { | ||||
|       wobbleEffect.setVolume(wobbleSlider.getValue()); | ||||
|       updateEffect(EffectType.WOBBLE, wobbleCheckBox.isSelected(), wobbleEffect); | ||||
|     }; | ||||
| 
 | ||||
|     vectorCancellingSlider.valueProperty().addListener(vectorCancellingListener); | ||||
|     vectorCancellingCheckBox.selectedProperty().addListener(vectorCancellingListener); | ||||
| 
 | ||||
|     bitCrushSlider.valueProperty().addListener(bitCrushListener); | ||||
|     bitCrushCheckBox.selectedProperty().addListener(bitCrushListener); | ||||
| 
 | ||||
|     verticalDistortSlider.valueProperty().addListener(verticalDistortListener); | ||||
|     verticalDistortCheckBox.selectedProperty().addListener(verticalDistortListener); | ||||
| 
 | ||||
|     horizontalDistortSlider.valueProperty().addListener(horizontalDistortListener); | ||||
|     horizontalDistortCheckBox.selectedProperty().addListener(horizontalDistortListener); | ||||
| 
 | ||||
|     wobbleSlider.valueProperty().addListener(wobbleListener); | ||||
|     wobbleCheckBox.selectedProperty().addListener(wobbleListener); | ||||
|     wobbleCheckBox.selectedProperty().addListener(e -> wobbleEffect.update()); | ||||
| 
 | ||||
|     fileChooser.setInitialFileName("out.wav"); | ||||
|     fileChooser.getExtensionFilters().addAll( | ||||
|       new FileChooser.ExtensionFilter("All Files", "*.*"), | ||||
|       new FileChooser.ExtensionFilter("WAV Files", "*.wav"), | ||||
|       new FileChooser.ExtensionFilter("Wavefront OBJ Files", "*.obj"), | ||||
|       new FileChooser.ExtensionFilter("SVG Files", "*.svg"), | ||||
|       new FileChooser.ExtensionFilter("Text Files", "*.txt") | ||||
|     ); | ||||
| 
 | ||||
|     chooseFileButton.setOnAction(e -> { | ||||
|       File file = fileChooser.showOpenDialog(stage); | ||||
|       if (file != null) { | ||||
|         chooseFile(file); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     recordButton.setOnAction(event -> toggleRecord()); | ||||
| 
 | ||||
|     updateObjectRotateSpeed(); | ||||
| 
 | ||||
|     renderer.addEffect(EffectType.SCALE, scaleEffect); | ||||
|     renderer.addEffect(EffectType.ROTATE, rotateEffect); | ||||
|     renderer.addEffect(EffectType.TRANSLATE, translateEffect); | ||||
| 
 | ||||
|     executor.submit(producer); | ||||
|     new Thread(renderer).start(); | ||||
|     FrequencyAnalyser<List<Shape>, AudioInputStream> analyser = new FrequencyAnalyser<>(renderer, 2, SAMPLE_RATE); | ||||
|     analyser.addListener(this); | ||||
|     analyser.addListener(wobbleEffect); | ||||
|     new Thread(analyser).start(); | ||||
|   } | ||||
| 
 | ||||
|   private void toggleRecord() { | ||||
|     recording = !recording; | ||||
|     if (recording) { | ||||
|       recordLabel.setText("Recording..."); | ||||
|       recordButton.setText("Stop Recording"); | ||||
|       renderer.startRecord(); | ||||
|     } else { | ||||
|       recordButton.setText("Record"); | ||||
|       AudioInputStream input = renderer.stopRecord(); | ||||
|       try { | ||||
|         File file = fileChooser.showSaveDialog(stage); | ||||
|         SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); | ||||
|         Date date = new Date(System.currentTimeMillis()); | ||||
|         if (file == null) { | ||||
|           file = new File("out-" + formatter.format(date) + ".wav"); | ||||
|         } | ||||
|         AudioSystem.write(input, AudioFileFormat.Type.WAVE, file); | ||||
|         input.close(); | ||||
|         recordLabel.setText("Saved to " + file.getAbsolutePath()); | ||||
|       } catch (IOException e) { | ||||
|         recordLabel.setText("Error saving file"); | ||||
|         e.printStackTrace(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void updateFocalLength() { | ||||
|     double focalLength = focalLengthSlider.getValue(); | ||||
|     producer.setFrameSettings(ObjSettingsFactory.focalLength(focalLength)); | ||||
|   } | ||||
| 
 | ||||
|   private void updateObjectRotateSpeed() { | ||||
|     double rotateSpeed = objectRotateSpeedSlider.getValue(); | ||||
|     producer.setFrameSettings( | ||||
|       ObjSettingsFactory.rotateSpeed((Math.exp(3 * rotateSpeed) - 1) / 50) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   private void updateTranslation() { | ||||
|     translateEffect.setTranslation(new Vector2( | ||||
|       tryParse(translationXTextField.getText()), | ||||
|       tryParse(translationYTextField.getText()) | ||||
|     )); | ||||
|   } | ||||
| 
 | ||||
|   private void updateCameraPos() { | ||||
|     producer.setFrameSettings(ObjSettingsFactory.cameraPosition(new Vector3( | ||||
|       tryParse(cameraXTextField.getText()), | ||||
|       tryParse(cameraYTextField.getText()), | ||||
|       tryParse(cameraZTextField.getText()) | ||||
|     ))); | ||||
|   } | ||||
| 
 | ||||
|   private double tryParse(String value) { | ||||
|     try { | ||||
|       return Double.parseDouble(value); | ||||
|     } catch (NumberFormatException e) { | ||||
|       return 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void updateEffect(EffectType type, boolean checked, Effect effect) { | ||||
|     if (checked) { | ||||
|       renderer.addEffect(type, effect); | ||||
|       effectTypes.get(type).setDisable(false); | ||||
|     } else { | ||||
|       renderer.removeEffect(type); | ||||
|       effectTypes.get(type).setDisable(true); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void chooseFile(File file) { | ||||
|     try { | ||||
|       producer.stop(); | ||||
|       String path = file.getAbsolutePath(); | ||||
|       FrameSet<List<Shape>> frames = ParserFactory.getParser(path).parse(); | ||||
|       frames.addListener(this); | ||||
|       producer = new FrameProducer<>(renderer, frames); | ||||
| 
 | ||||
|       updateObjectRotateSpeed(); | ||||
|       updateFocalLength(); | ||||
|       executor.submit(producer); | ||||
| 
 | ||||
|       KeyFrame kf1 = new KeyFrame(Duration.seconds(0), e -> wobbleEffect.setVolume(0)); | ||||
|       KeyFrame kf2 = new KeyFrame(Duration.seconds(1), e -> { | ||||
|         wobbleEffect.update(); | ||||
|         wobbleEffect.setVolume(wobbleSlider.getValue()); | ||||
|       }); | ||||
|       Timeline timeline = new Timeline(kf1, kf2); | ||||
|       Platform.runLater(timeline::play); | ||||
| 
 | ||||
|       if (file.exists() && !file.isDirectory()) { | ||||
|         fileLabel.setText(path); | ||||
|         objTitledPane.setDisable(!ObjParser.isObjFile(path)); | ||||
|       } else { | ||||
|         objTitledPane.setDisable(true); | ||||
|       } | ||||
|     } catch (IOException | ParserConfigurationException | SAXException ioException) { | ||||
|       ioException.printStackTrace(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public void setStage(Stage stage) { | ||||
|     this.stage = stage; | ||||
|   } | ||||
| 
 | ||||
|   protected boolean mouseRotate() { | ||||
|     return rotateCheckBox.isSelected(); | ||||
|   } | ||||
| 
 | ||||
|   protected void disableMouseRotate() { | ||||
|     rotateCheckBox.setSelected(false); | ||||
|   } | ||||
| 
 | ||||
|   protected void setObjRotate(Vector3 vector) { | ||||
|     producer.setFrameSettings(ObjSettingsFactory.rotation(vector)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void updateFrequency(double leftFrequency, double rightFrequency) { | ||||
|     Platform.runLater(() -> | ||||
|       frequencyLabel.setText(String.format("L Frequency: %d Hz\nR Frequency: %d Hz", Math.round(leftFrequency), Math.round(rightFrequency))) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void update(Object pos) { | ||||
|     if (pos instanceof Vector3 vector) { | ||||
|       Platform.runLater(() -> { | ||||
|         cameraXTextField.setText(String.valueOf(vector.getX())); | ||||
|         cameraYTextField.setText(String.valueOf(vector.getY())); | ||||
|         cameraZTextField.setText(String.valueOf(vector.getZ())); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| package sh.ball.gui; | ||||
| 
 | ||||
| import javafx.animation.KeyFrame; | ||||
| import javafx.animation.Timeline; | ||||
| import javafx.application.Platform; | ||||
| import javafx.scene.control.*; | ||||
| import javafx.util.Duration; | ||||
| import sh.ball.audio.*; | ||||
| import sh.ball.audio.effect.*; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.URL; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.ResourceBundle; | ||||
| import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.Executors; | ||||
| import java.util.function.Consumer; | ||||
| 
 | ||||
| import javafx.beans.InvalidationListener; | ||||
| import javafx.fxml.FXML; | ||||
| import javafx.fxml.Initializable; | ||||
| import javafx.stage.FileChooser; | ||||
| import javafx.stage.Stage; | ||||
| 
 | ||||
| import javax.sound.sampled.AudioFileFormat; | ||||
| import javax.sound.sampled.AudioInputStream; | ||||
| import javax.sound.sampled.AudioSystem; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.effect.Effect; | ||||
| import sh.ball.audio.effect.EffectType; | ||||
| import sh.ball.engine.Vector3; | ||||
| import sh.ball.parser.obj.Listener; | ||||
| import sh.ball.parser.obj.ObjSettingsFactory; | ||||
| import sh.ball.parser.obj.ObjParser; | ||||
| import sh.ball.parser.ParserFactory; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class Controller implements Initializable, FrequencyListener, Listener { | ||||
| 
 | ||||
|   private static final int SAMPLE_RATE = 192000; | ||||
|   private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj"); | ||||
| 
 | ||||
|   private final FileChooser fileChooser = new FileChooser(); | ||||
|   private final Renderer<List<Shape>, AudioInputStream> renderer; | ||||
|   private final ExecutorService executor = Executors.newSingleThreadExecutor(); | ||||
| 
 | ||||
|   private final RotateEffect rotateEffect = new RotateEffect(SAMPLE_RATE); | ||||
|   private final TranslateEffect translateEffect = new TranslateEffect(SAMPLE_RATE); | ||||
|   private final WobbleEffect wobbleEffect = new WobbleEffect(SAMPLE_RATE); | ||||
|   private final ScaleEffect scaleEffect = new ScaleEffect(); | ||||
| 
 | ||||
|   private FrameProducer<List<Shape>, AudioInputStream> producer; | ||||
|   private boolean recording = false; | ||||
| 
 | ||||
|   private Stage stage; | ||||
| 
 | ||||
|   @FXML | ||||
|   private Label frequencyLabel; | ||||
|   @FXML | ||||
|   private Button chooseFileButton; | ||||
|   @FXML | ||||
|   private Label fileLabel; | ||||
|   @FXML | ||||
|   private Button recordButton; | ||||
|   @FXML | ||||
|   private Label recordLabel; | ||||
|   @FXML | ||||
|   private TextField translationXTextField; | ||||
|   @FXML | ||||
|   private TextField translationYTextField; | ||||
|   @FXML | ||||
|   private Slider weightSlider; | ||||
|   @FXML | ||||
|   private Slider rotateSpeedSlider; | ||||
|   @FXML | ||||
|   private Slider translationSpeedSlider; | ||||
|   @FXML | ||||
|   private Slider scaleSlider; | ||||
|   @FXML | ||||
|   private TitledPane objTitledPane; | ||||
|   @FXML | ||||
|   private Slider focalLengthSlider; | ||||
|   @FXML | ||||
|   private TextField cameraXTextField; | ||||
|   @FXML | ||||
|   private TextField cameraYTextField; | ||||
|   @FXML | ||||
|   private TextField cameraZTextField; | ||||
|   @FXML | ||||
|   private Slider objectRotateSpeedSlider; | ||||
|   @FXML | ||||
|   private CheckBox rotateCheckBox; | ||||
|   @FXML | ||||
|   private CheckBox vectorCancellingCheckBox; | ||||
|   @FXML | ||||
|   private Slider vectorCancellingSlider; | ||||
|   @FXML | ||||
|   private CheckBox bitCrushCheckBox; | ||||
|   @FXML | ||||
|   private Slider bitCrushSlider; | ||||
|   @FXML | ||||
|   private CheckBox verticalDistortCheckBox; | ||||
|   @FXML | ||||
|   private Slider verticalDistortSlider; | ||||
|   @FXML | ||||
|   private CheckBox horizontalDistortCheckBox; | ||||
|   @FXML | ||||
|   private Slider horizontalDistortSlider; | ||||
|   @FXML | ||||
|   private CheckBox wobbleCheckBox; | ||||
|   @FXML | ||||
|   private Slider wobbleSlider; | ||||
| 
 | ||||
|   public Controller(Renderer<List<Shape>, AudioInputStream> renderer) throws IOException { | ||||
|     this.renderer = renderer; | ||||
|     FrameSet<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse(); | ||||
|     frames.addListener(this); | ||||
|     this.producer = new FrameProducer<>(renderer, frames); | ||||
|   } | ||||
| 
 | ||||
|   private Map<Slider, Consumer<Double>> initializeSliderMap() { | ||||
|     return Map.of( | ||||
|       weightSlider, | ||||
|       renderer::setQuality, | ||||
|       rotateSpeedSlider, | ||||
|       rotateEffect::setSpeed, | ||||
|       translationSpeedSlider, | ||||
|       translateEffect::setSpeed, | ||||
|       scaleSlider, | ||||
|       scaleEffect::setScale, | ||||
|       focalLengthSlider, | ||||
|       d -> updateFocalLength(), | ||||
|       objectRotateSpeedSlider, | ||||
|       d -> updateObjectRotateSpeed() | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   private Map<EffectType, Slider> effectTypes; | ||||
| 
 | ||||
|   private void initializeEffectTypes() { | ||||
|     effectTypes = Map.of( | ||||
|       EffectType.VECTOR_CANCELLING, | ||||
|       vectorCancellingSlider, | ||||
|       EffectType.BIT_CRUSH, | ||||
|       bitCrushSlider, | ||||
|       EffectType.VERTICAL_DISTORT, | ||||
|       verticalDistortSlider, | ||||
|       EffectType.HORIZONTAL_DISTORT, | ||||
|       horizontalDistortSlider, | ||||
|       EffectType.WOBBLE, | ||||
|       wobbleSlider | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void initialize(URL url, ResourceBundle resourceBundle) { | ||||
|     Map<Slider, Consumer<Double>> sliders = initializeSliderMap(); | ||||
|     initializeEffectTypes(); | ||||
| 
 | ||||
|     for (Slider slider : sliders.keySet()) { | ||||
|       slider.valueProperty().addListener((source, oldValue, newValue) -> | ||||
|         sliders.get(slider).accept(slider.getValue()) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     translationXTextField.textProperty().addListener(e -> updateTranslation()); | ||||
|     translationYTextField.textProperty().addListener(e -> updateTranslation()); | ||||
| 
 | ||||
|     cameraXTextField.focusedProperty().addListener(e -> updateCameraPos()); | ||||
|     cameraYTextField.focusedProperty().addListener(e -> updateCameraPos()); | ||||
|     cameraZTextField.focusedProperty().addListener(e -> updateCameraPos()); | ||||
| 
 | ||||
|     InvalidationListener vectorCancellingListener = e -> | ||||
|       updateEffect(EffectType.VECTOR_CANCELLING, vectorCancellingCheckBox.isSelected(), | ||||
|         EffectFactory.vectorCancelling((int) vectorCancellingSlider.getValue())); | ||||
|     InvalidationListener bitCrushListener = e -> | ||||
|       updateEffect(EffectType.BIT_CRUSH, bitCrushCheckBox.isSelected(), | ||||
|         EffectFactory.bitCrush(bitCrushSlider.getValue())); | ||||
|     InvalidationListener verticalDistortListener = e -> | ||||
|       updateEffect(EffectType.VERTICAL_DISTORT, verticalDistortCheckBox.isSelected(), | ||||
|         EffectFactory.verticalDistort(verticalDistortSlider.getValue())); | ||||
|     InvalidationListener horizontalDistortListener = e -> | ||||
|       updateEffect(EffectType.HORIZONTAL_DISTORT, horizontalDistortCheckBox.isSelected(), | ||||
|         EffectFactory.horizontalDistort(horizontalDistortSlider.getValue())); | ||||
|     InvalidationListener wobbleListener = e -> { | ||||
|       wobbleEffect.setVolume(wobbleSlider.getValue()); | ||||
|       updateEffect(EffectType.WOBBLE, wobbleCheckBox.isSelected(), wobbleEffect); | ||||
|     }; | ||||
| 
 | ||||
|     vectorCancellingSlider.valueProperty().addListener(vectorCancellingListener); | ||||
|     vectorCancellingCheckBox.selectedProperty().addListener(vectorCancellingListener); | ||||
| 
 | ||||
|     bitCrushSlider.valueProperty().addListener(bitCrushListener); | ||||
|     bitCrushCheckBox.selectedProperty().addListener(bitCrushListener); | ||||
| 
 | ||||
|     verticalDistortSlider.valueProperty().addListener(verticalDistortListener); | ||||
|     verticalDistortCheckBox.selectedProperty().addListener(verticalDistortListener); | ||||
| 
 | ||||
|     horizontalDistortSlider.valueProperty().addListener(horizontalDistortListener); | ||||
|     horizontalDistortCheckBox.selectedProperty().addListener(horizontalDistortListener); | ||||
| 
 | ||||
|     wobbleSlider.valueProperty().addListener(wobbleListener); | ||||
|     wobbleCheckBox.selectedProperty().addListener(wobbleListener); | ||||
|     wobbleCheckBox.selectedProperty().addListener(e -> wobbleEffect.update()); | ||||
| 
 | ||||
|     fileChooser.setInitialFileName("out.wav"); | ||||
|     fileChooser.getExtensionFilters().addAll( | ||||
|       new FileChooser.ExtensionFilter("All Files", "*.*"), | ||||
|       new FileChooser.ExtensionFilter("WAV Files", "*.wav"), | ||||
|       new FileChooser.ExtensionFilter("Wavefront OBJ Files", "*.obj"), | ||||
|       new FileChooser.ExtensionFilter("SVG Files", "*.svg"), | ||||
|       new FileChooser.ExtensionFilter("Text Files", "*.txt") | ||||
|     ); | ||||
| 
 | ||||
|     chooseFileButton.setOnAction(e -> { | ||||
|       File file = fileChooser.showOpenDialog(stage); | ||||
|       if (file != null) { | ||||
|         chooseFile(file); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     recordButton.setOnAction(event -> toggleRecord()); | ||||
| 
 | ||||
|     updateObjectRotateSpeed(); | ||||
| 
 | ||||
|     renderer.addEffect(EffectType.SCALE, scaleEffect); | ||||
|     renderer.addEffect(EffectType.ROTATE, rotateEffect); | ||||
|     renderer.addEffect(EffectType.TRANSLATE, translateEffect); | ||||
| 
 | ||||
|     executor.submit(producer); | ||||
|     new Thread(renderer).start(); | ||||
|     FrequencyAnalyser<List<Shape>, AudioInputStream> analyser = new FrequencyAnalyser<>(renderer, 2, SAMPLE_RATE); | ||||
|     analyser.addListener(this); | ||||
|     analyser.addListener(wobbleEffect); | ||||
|     new Thread(analyser).start(); | ||||
|   } | ||||
| 
 | ||||
|   private void toggleRecord() { | ||||
|     recording = !recording; | ||||
|     if (recording) { | ||||
|       recordLabel.setText("Recording..."); | ||||
|       recordButton.setText("Stop Recording"); | ||||
|       renderer.startRecord(); | ||||
|     } else { | ||||
|       recordButton.setText("Record"); | ||||
|       AudioInputStream input = renderer.stopRecord(); | ||||
|       try { | ||||
|         File file = fileChooser.showSaveDialog(stage); | ||||
|         SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); | ||||
|         Date date = new Date(System.currentTimeMillis()); | ||||
|         if (file == null) { | ||||
|           file = new File("out-" + formatter.format(date) + ".wav"); | ||||
|         } | ||||
|         AudioSystem.write(input, AudioFileFormat.Type.WAVE, file); | ||||
|         input.close(); | ||||
|         recordLabel.setText("Saved to " + file.getAbsolutePath()); | ||||
|       } catch (IOException e) { | ||||
|         recordLabel.setText("Error saving file"); | ||||
|         e.printStackTrace(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void updateFocalLength() { | ||||
|     double focalLength = focalLengthSlider.getValue(); | ||||
|     producer.setFrameSettings(ObjSettingsFactory.focalLength(focalLength)); | ||||
|   } | ||||
| 
 | ||||
|   private void updateObjectRotateSpeed() { | ||||
|     double rotateSpeed = objectRotateSpeedSlider.getValue(); | ||||
|     producer.setFrameSettings( | ||||
|       ObjSettingsFactory.rotateSpeed((Math.exp(3 * rotateSpeed) - 1) / 50) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   private void updateTranslation() { | ||||
|     translateEffect.setTranslation(new Vector2( | ||||
|       tryParse(translationXTextField.getText()), | ||||
|       tryParse(translationYTextField.getText()) | ||||
|     )); | ||||
|   } | ||||
| 
 | ||||
|   private void updateCameraPos() { | ||||
|     producer.setFrameSettings(ObjSettingsFactory.cameraPosition(new Vector3( | ||||
|       tryParse(cameraXTextField.getText()), | ||||
|       tryParse(cameraYTextField.getText()), | ||||
|       tryParse(cameraZTextField.getText()) | ||||
|     ))); | ||||
|   } | ||||
| 
 | ||||
|   private double tryParse(String value) { | ||||
|     try { | ||||
|       return Double.parseDouble(value); | ||||
|     } catch (NumberFormatException e) { | ||||
|       return 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void updateEffect(EffectType type, boolean checked, Effect effect) { | ||||
|     if (checked) { | ||||
|       renderer.addEffect(type, effect); | ||||
|       effectTypes.get(type).setDisable(false); | ||||
|     } else { | ||||
|       renderer.removeEffect(type); | ||||
|       effectTypes.get(type).setDisable(true); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void chooseFile(File file) { | ||||
|     try { | ||||
|       producer.stop(); | ||||
|       String path = file.getAbsolutePath(); | ||||
|       FrameSet<List<Shape>> frames = ParserFactory.getParser(path).parse(); | ||||
|       frames.addListener(this); | ||||
|       producer = new FrameProducer<>(renderer, frames); | ||||
| 
 | ||||
|       updateObjectRotateSpeed(); | ||||
|       updateFocalLength(); | ||||
|       executor.submit(producer); | ||||
| 
 | ||||
|       KeyFrame kf1 = new KeyFrame(Duration.seconds(0), e -> wobbleEffect.setVolume(0)); | ||||
|       KeyFrame kf2 = new KeyFrame(Duration.seconds(1), e -> { | ||||
|         wobbleEffect.update(); | ||||
|         wobbleEffect.setVolume(wobbleSlider.getValue()); | ||||
|       }); | ||||
|       Timeline timeline = new Timeline(kf1, kf2); | ||||
|       Platform.runLater(timeline::play); | ||||
| 
 | ||||
|       if (file.exists() && !file.isDirectory()) { | ||||
|         fileLabel.setText(path); | ||||
|         objTitledPane.setDisable(!ObjParser.isObjFile(path)); | ||||
|       } else { | ||||
|         objTitledPane.setDisable(true); | ||||
|       } | ||||
|     } catch (IOException | ParserConfigurationException | SAXException ioException) { | ||||
|       ioException.printStackTrace(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public void setStage(Stage stage) { | ||||
|     this.stage = stage; | ||||
|   } | ||||
| 
 | ||||
|   protected boolean mouseRotate() { | ||||
|     return rotateCheckBox.isSelected(); | ||||
|   } | ||||
| 
 | ||||
|   protected void disableMouseRotate() { | ||||
|     rotateCheckBox.setSelected(false); | ||||
|   } | ||||
| 
 | ||||
|   protected void setObjRotate(Vector3 vector) { | ||||
|     producer.setFrameSettings(ObjSettingsFactory.rotation(vector)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void updateFrequency(double leftFrequency, double rightFrequency) { | ||||
|     Platform.runLater(() -> | ||||
|       frequencyLabel.setText(String.format("L/R Frequency:\n%d Hz / %d Hz", Math.round(leftFrequency), Math.round(rightFrequency))) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void update(Object pos) { | ||||
|     if (pos instanceof Vector3 vector) { | ||||
|       Platform.runLater(() -> { | ||||
|         cameraXTextField.setText(String.valueOf(round(vector.getX(), 3))); | ||||
|         cameraYTextField.setText(String.valueOf(round(vector.getY(), 3))); | ||||
|         cameraZTextField.setText(String.valueOf(round(vector.getZ(), 3))); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static double round(double value, double places) { | ||||
|     if (places < 0) throw new IllegalArgumentException(); | ||||
| 
 | ||||
|     long factor = (long) Math.pow(10, places); | ||||
|     value = value * factor; | ||||
|     long tmp = Math.round(value); | ||||
|     return (double) tmp / factor; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| package sh.ball.gui; | ||||
| 
 | ||||
| enum EffectType { | ||||
|   VECTOR_CANCELLING | ||||
| } | ||||
| package sh.ball.gui; | ||||
| 
 | ||||
| enum EffectType { | ||||
|   VECTOR_CANCELLING | ||||
| } | ||||
|  |  | |||
|  | @ -1,68 +1,66 @@ | |||
| package sh.ball.gui; | ||||
| 
 | ||||
| import javafx.application.Application; | ||||
| import javafx.application.Platform; | ||||
| import javafx.fxml.FXMLLoader; | ||||
| import javafx.scene.Parent; | ||||
| import javafx.scene.Scene; | ||||
| import javafx.scene.image.Image; | ||||
| import javafx.scene.input.KeyCode; | ||||
| import javafx.scene.input.KeyEvent; | ||||
| import javafx.scene.input.MouseEvent; | ||||
| import javafx.scene.text.Font; | ||||
| import javafx.stage.Stage; | ||||
| import javafx.stage.StageStyle; | ||||
| import sh.ball.audio.AudioPlayer; | ||||
| import sh.ball.engine.Vector3; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| public class Gui extends Application { | ||||
| 
 | ||||
|   private static final int SAMPLE_RATE = 192000; | ||||
| 
 | ||||
|   @Override | ||||
|   public void start(Stage stage) throws Exception { | ||||
|     Thread.currentThread().setPriority(Thread.MAX_PRIORITY); | ||||
|     System.setProperty("prism.lcdtext", "false"); | ||||
| 
 | ||||
|     FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml")); | ||||
|     Controller controller = new Controller(new AudioPlayer(SAMPLE_RATE)); | ||||
|     loader.setController(controller); | ||||
|     Parent root = loader.load(); | ||||
| 
 | ||||
|     stage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/icons/icon.png")))); | ||||
|     stage.setTitle("osci-render"); | ||||
|     Scene scene = new Scene(root); | ||||
|     scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm()); | ||||
|     scene.addEventFilter(MouseEvent.MOUSE_MOVED, event -> { | ||||
|       if (controller.mouseRotate()) { | ||||
|         controller.setObjRotate(new Vector3( | ||||
|           3 * Math.PI * (event.getSceneY() / scene.getHeight()), | ||||
|           3 * Math.PI * (event.getSceneX() /  scene.getWidth()), | ||||
|           0 | ||||
|         )); | ||||
|       } | ||||
|     }); | ||||
|     scene.addEventHandler(KeyEvent.KEY_PRESSED, t -> { | ||||
|       if (t.getCode() == KeyCode.ESCAPE) { | ||||
|         controller.disableMouseRotate(); | ||||
|       } | ||||
|     }); | ||||
|     stage.setScene(scene); | ||||
|     stage.setResizable(false); | ||||
| 
 | ||||
|     controller.setStage(stage); | ||||
| 
 | ||||
|     stage.show(); | ||||
| 
 | ||||
|     stage.setOnCloseRequest(t -> { | ||||
|       Platform.exit(); | ||||
|       System.exit(0); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   public static void main(String[] args) { | ||||
|     launch(args); | ||||
|   } | ||||
| } | ||||
| package sh.ball.gui; | ||||
| 
 | ||||
| import javafx.application.Application; | ||||
| import javafx.application.Platform; | ||||
| import javafx.fxml.FXMLLoader; | ||||
| import javafx.scene.Parent; | ||||
| import javafx.scene.Scene; | ||||
| import javafx.scene.image.Image; | ||||
| import javafx.scene.input.KeyCode; | ||||
| import javafx.scene.input.KeyEvent; | ||||
| import javafx.scene.input.MouseEvent; | ||||
| import javafx.stage.Stage; | ||||
| import sh.ball.audio.AudioPlayer; | ||||
| import sh.ball.engine.Vector3; | ||||
| 
 | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| public class Gui extends Application { | ||||
| 
 | ||||
|   private static final int SAMPLE_RATE = 192000; | ||||
| 
 | ||||
|   @Override | ||||
|   public void start(Stage stage) throws Exception { | ||||
|     Thread.currentThread().setPriority(Thread.MAX_PRIORITY); | ||||
|     System.setProperty("prism.lcdtext", "false"); | ||||
| 
 | ||||
|     FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml")); | ||||
|     Controller controller = new Controller(new AudioPlayer(SAMPLE_RATE)); | ||||
|     loader.setController(controller); | ||||
|     Parent root = loader.load(); | ||||
| 
 | ||||
|     stage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/icons/icon.png")))); | ||||
|     stage.setTitle("osci-render"); | ||||
|     Scene scene = new Scene(root); | ||||
|     scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm()); | ||||
|     scene.addEventFilter(MouseEvent.MOUSE_MOVED, event -> { | ||||
|       if (controller.mouseRotate()) { | ||||
|         controller.setObjRotate(new Vector3( | ||||
|           3 * Math.PI * (event.getSceneY() / scene.getHeight()), | ||||
|           3 * Math.PI * (event.getSceneX() /  scene.getWidth()), | ||||
|           0 | ||||
|         )); | ||||
|       } | ||||
|     }); | ||||
|     scene.addEventHandler(KeyEvent.KEY_PRESSED, t -> { | ||||
|       if (t.getCode() == KeyCode.ESCAPE) { | ||||
|         controller.disableMouseRotate(); | ||||
|       } | ||||
|     }); | ||||
|     stage.setScene(scene); | ||||
|     stage.setResizable(false); | ||||
| 
 | ||||
|     controller.setStage(stage); | ||||
| 
 | ||||
|     stage.show(); | ||||
| 
 | ||||
|     stage.setOnCloseRequest(t -> { | ||||
|       Platform.exit(); | ||||
|       System.exit(0); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   public static void main(String[] args) { | ||||
|     launch(args); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| package sh.ball.gui; | ||||
| 
 | ||||
| @FunctionalInterface | ||||
| public interface Settable<T> { | ||||
| 
 | ||||
|   void set(T value); | ||||
| } | ||||
| package sh.ball.gui; | ||||
| 
 | ||||
| @FunctionalInterface | ||||
| public interface Settable<T> { | ||||
| 
 | ||||
|   void set(T value); | ||||
| } | ||||
|  |  | |||
|  | @ -1,25 +1,25 @@ | |||
| package sh.ball.parser; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| 
 | ||||
| public abstract class FileParser<T> { | ||||
| 
 | ||||
|   public abstract String getFileExtension(); | ||||
| 
 | ||||
|   protected void checkFileExtension(String path) throws IllegalArgumentException { | ||||
|     if (!hasCorrectFileExtension(path)) { | ||||
|       throw new IllegalArgumentException( | ||||
|         "File to parse is not a ." + getFileExtension() + " file."); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public boolean hasCorrectFileExtension(String path) { | ||||
|     return path.matches(".*\\." + getFileExtension()); | ||||
|   } | ||||
| 
 | ||||
|   public abstract T parse() | ||||
|     throws ParserConfigurationException, IOException, SAXException, IllegalArgumentException; | ||||
| } | ||||
| package sh.ball.parser; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| 
 | ||||
| public abstract class FileParser<T> { | ||||
| 
 | ||||
|   public abstract String getFileExtension(); | ||||
| 
 | ||||
|   protected void checkFileExtension(String path) throws IllegalArgumentException { | ||||
|     if (!hasCorrectFileExtension(path)) { | ||||
|       throw new IllegalArgumentException( | ||||
|         "File to parse is not a ." + getFileExtension() + " file."); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public boolean hasCorrectFileExtension(String path) { | ||||
|     return path.matches(".*\\." + getFileExtension()); | ||||
|   } | ||||
| 
 | ||||
|   public abstract T parse() | ||||
|     throws ParserConfigurationException, IOException, SAXException, IllegalArgumentException; | ||||
| } | ||||
|  |  | |||
|  | @ -1,28 +1,28 @@ | |||
| package sh.ball.parser; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.parser.obj.ObjParser; | ||||
| import sh.ball.parser.svg.SvgParser; | ||||
| import sh.ball.parser.txt.TextParser; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class ParserFactory { | ||||
| 
 | ||||
|   public static FileParser<FrameSet<List<Shape>>> getParser(String filePath) throws IOException, ParserConfigurationException, SAXException { | ||||
|     if (ObjParser.isObjFile(filePath)) { | ||||
|       return new ObjParser(filePath); | ||||
|     } else if (SvgParser.isSvgFile(filePath)) { | ||||
|       return new SvgParser(filePath); | ||||
|     } else if (TextParser.isTxtFile(filePath)) { | ||||
|       return new TextParser(filePath); | ||||
|     } | ||||
|     throw new IOException("No known parser that can parse " + new File(filePath).getName()); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| package sh.ball.parser; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.parser.obj.ObjParser; | ||||
| import sh.ball.parser.svg.SvgParser; | ||||
| import sh.ball.parser.txt.TextParser; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class ParserFactory { | ||||
| 
 | ||||
|   public static FileParser<FrameSet<List<Shape>>> getParser(String filePath) throws IOException, ParserConfigurationException, SAXException { | ||||
|     if (ObjParser.isObjFile(filePath)) { | ||||
|       return new ObjParser(filePath); | ||||
|     } else if (SvgParser.isSvgFile(filePath)) { | ||||
|       return new SvgParser(filePath); | ||||
|     } else if (TextParser.isTxtFile(filePath)) { | ||||
|       return new TextParser(filePath); | ||||
|     } | ||||
|     throw new IOException("No known parser that can parse " + new File(filePath).getName()); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,80 +1,80 @@ | |||
| package sh.ball.parser; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.AbstractList; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.RandomAccess; | ||||
| import javax.xml.parsers.DocumentBuilder; | ||||
| import javax.xml.parsers.DocumentBuilderFactory; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Node; | ||||
| import org.w3c.dom.NodeList; | ||||
| import org.xml.sax.SAXException; | ||||
| 
 | ||||
| public final class XmlUtil { | ||||
| 
 | ||||
|   private XmlUtil() { | ||||
|   } | ||||
| 
 | ||||
|   public static List<Node> asList(NodeList n) { | ||||
|     return n.getLength() == 0 ? | ||||
|       Collections.emptyList() : new NodeListWrapper(n); | ||||
|   } | ||||
| 
 | ||||
|   static final class NodeListWrapper extends AbstractList<Node> | ||||
|     implements RandomAccess { | ||||
| 
 | ||||
|     private final NodeList list; | ||||
| 
 | ||||
|     NodeListWrapper(NodeList l) { | ||||
|       list = l; | ||||
|     } | ||||
| 
 | ||||
|     public Node get(int index) { | ||||
|       return list.item(index); | ||||
|     } | ||||
| 
 | ||||
|     public int size() { | ||||
|       return list.getLength(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static String getNodeValue(Node node, String namedItem) { | ||||
|     Node attribute = node.getAttributes().getNamedItem(namedItem); | ||||
|     return attribute == null ? null : attribute.getNodeValue(); | ||||
|   } | ||||
| 
 | ||||
|   public static Document getXMLDocument(InputStream input) | ||||
|     throws IOException, SAXException, ParserConfigurationException { | ||||
|     // Opens XML reader for svg file.
 | ||||
|     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | ||||
| 
 | ||||
|     // Remove validation which massively slows down file parsing
 | ||||
|     factory.setNamespaceAware(false); | ||||
|     factory.setValidating(false); | ||||
|     factory.setFeature("http://xml.org/sax/features/namespaces", false); | ||||
|     factory.setFeature("http://xml.org/sax/features/validation", false); | ||||
|     factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); | ||||
|     factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); | ||||
| 
 | ||||
|     DocumentBuilder builder = factory.newDocumentBuilder(); | ||||
| 
 | ||||
|     return builder.parse(input); | ||||
|   } | ||||
| 
 | ||||
|   public static List<Node> getAttributesOnTags(Document xml, String tagName, String attribute) { | ||||
|     List<Node> attributes = new ArrayList<>(); | ||||
| 
 | ||||
|     for (Node elem : asList(xml.getElementsByTagName(tagName))) { | ||||
|       attributes.add(elem.getAttributes().getNamedItem(attribute)); | ||||
|     } | ||||
| 
 | ||||
|     return attributes; | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.AbstractList; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.RandomAccess; | ||||
| import javax.xml.parsers.DocumentBuilder; | ||||
| import javax.xml.parsers.DocumentBuilderFactory; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Node; | ||||
| import org.w3c.dom.NodeList; | ||||
| import org.xml.sax.SAXException; | ||||
| 
 | ||||
| public final class XmlUtil { | ||||
| 
 | ||||
|   private XmlUtil() { | ||||
|   } | ||||
| 
 | ||||
|   public static List<Node> asList(NodeList n) { | ||||
|     return n.getLength() == 0 ? | ||||
|       Collections.emptyList() : new NodeListWrapper(n); | ||||
|   } | ||||
| 
 | ||||
|   static final class NodeListWrapper extends AbstractList<Node> | ||||
|     implements RandomAccess { | ||||
| 
 | ||||
|     private final NodeList list; | ||||
| 
 | ||||
|     NodeListWrapper(NodeList l) { | ||||
|       list = l; | ||||
|     } | ||||
| 
 | ||||
|     public Node get(int index) { | ||||
|       return list.item(index); | ||||
|     } | ||||
| 
 | ||||
|     public int size() { | ||||
|       return list.getLength(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static String getNodeValue(Node node, String namedItem) { | ||||
|     Node attribute = node.getAttributes().getNamedItem(namedItem); | ||||
|     return attribute == null ? null : attribute.getNodeValue(); | ||||
|   } | ||||
| 
 | ||||
|   public static Document getXMLDocument(InputStream input) | ||||
|     throws IOException, SAXException, ParserConfigurationException { | ||||
|     // Opens XML reader for svg file.
 | ||||
|     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | ||||
| 
 | ||||
|     // Remove validation which massively slows down file parsing
 | ||||
|     factory.setNamespaceAware(false); | ||||
|     factory.setValidating(false); | ||||
|     factory.setFeature("http://xml.org/sax/features/namespaces", false); | ||||
|     factory.setFeature("http://xml.org/sax/features/validation", false); | ||||
|     factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); | ||||
|     factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); | ||||
| 
 | ||||
|     DocumentBuilder builder = factory.newDocumentBuilder(); | ||||
| 
 | ||||
|     return builder.parse(input); | ||||
|   } | ||||
| 
 | ||||
|   public static List<Node> getAttributesOnTags(Document xml, String tagName, String attribute) { | ||||
|     List<Node> attributes = new ArrayList<>(); | ||||
| 
 | ||||
|     for (Node elem : asList(xml.getElementsByTagName(tagName))) { | ||||
|       attributes.add(elem.getAttributes().getNamedItem(attribute)); | ||||
|     } | ||||
| 
 | ||||
|     return attributes; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| public interface Listener { | ||||
| 
 | ||||
|   void update(Object obj); | ||||
| } | ||||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| public interface Listener { | ||||
| 
 | ||||
|   void update(Object obj); | ||||
| } | ||||
|  |  | |||
|  | @ -1,72 +1,72 @@ | |||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.engine.Camera; | ||||
| import sh.ball.engine.Vector3; | ||||
| import sh.ball.engine.WorldObject; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class ObjFrameSet implements FrameSet<List<Shape>> { | ||||
| 
 | ||||
|   private final WorldObject object; | ||||
|   private final Camera camera; | ||||
|   private final List<Listener> listeners = new ArrayList<>(); | ||||
| 
 | ||||
|   private Vector3 rotation = new Vector3(); | ||||
|   private Double rotateSpeed = 0.0; | ||||
| 
 | ||||
|   public ObjFrameSet(WorldObject object, Camera camera) { | ||||
|     this.object = object; | ||||
|     this.camera = camera; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void addListener(Listener listener) { | ||||
|     listeners.add(listener); | ||||
|     notifyListener(listener); | ||||
|   } | ||||
| 
 | ||||
|   private void notifyListener(Listener listener) { | ||||
|     listener.update(camera.getPos()); | ||||
|   } | ||||
| 
 | ||||
|   private void notifyListeners() { | ||||
|     listeners.forEach(this::notifyListener); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public List<Shape> next() { | ||||
|     if (rotateSpeed == 0) { | ||||
|       object.setRotation(rotation); | ||||
|     } else { | ||||
|       object.rotate(rotation.scale(rotateSpeed)); | ||||
|     } | ||||
|     return camera.draw(object); | ||||
|   } | ||||
| 
 | ||||
|   // TODO: Refactor!
 | ||||
|   @Override | ||||
|   public void setFrameSettings(Object settings) { | ||||
|     if (settings instanceof ObjFrameSettings obj) { | ||||
|       if (obj.focalLength != null && camera.getFocalLength() != obj.focalLength) { | ||||
|         camera.setFocalLength(obj.focalLength); | ||||
|       } | ||||
|       if (obj.cameraPos != null && camera.getPos() != obj.cameraPos) { | ||||
|         camera.setPos(obj.cameraPos); | ||||
|         notifyListeners(); | ||||
|       } | ||||
|       if (obj.rotation != null) { | ||||
|         this.rotation = obj.rotation; | ||||
|       } | ||||
|       if (obj.rotateSpeed != null) { | ||||
|         this.rotateSpeed = obj.rotateSpeed; | ||||
|       } | ||||
|       if (obj.resetRotation) { | ||||
|         object.resetRotation(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.engine.Camera; | ||||
| import sh.ball.engine.Vector3; | ||||
| import sh.ball.engine.WorldObject; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class ObjFrameSet implements FrameSet<List<Shape>> { | ||||
| 
 | ||||
|   private final WorldObject object; | ||||
|   private final Camera camera; | ||||
|   private final List<Listener> listeners = new ArrayList<>(); | ||||
| 
 | ||||
|   private Vector3 rotation = new Vector3(); | ||||
|   private Double rotateSpeed = 0.0; | ||||
| 
 | ||||
|   public ObjFrameSet(WorldObject object, Camera camera) { | ||||
|     this.object = object; | ||||
|     this.camera = camera; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void addListener(Listener listener) { | ||||
|     listeners.add(listener); | ||||
|     notifyListener(listener); | ||||
|   } | ||||
| 
 | ||||
|   private void notifyListener(Listener listener) { | ||||
|     listener.update(camera.getPos()); | ||||
|   } | ||||
| 
 | ||||
|   private void notifyListeners() { | ||||
|     listeners.forEach(this::notifyListener); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public List<Shape> next() { | ||||
|     if (rotateSpeed == 0) { | ||||
|       object.setRotation(rotation); | ||||
|     } else { | ||||
|       object.rotate(rotation.scale(rotateSpeed)); | ||||
|     } | ||||
|     return camera.draw(object); | ||||
|   } | ||||
| 
 | ||||
|   // TODO: Refactor!
 | ||||
|   @Override | ||||
|   public void setFrameSettings(Object settings) { | ||||
|     if (settings instanceof ObjFrameSettings obj) { | ||||
|       if (obj.focalLength != null && camera.getFocalLength() != obj.focalLength) { | ||||
|         camera.setFocalLength(obj.focalLength); | ||||
|       } | ||||
|       if (obj.cameraPos != null && camera.getPos() != obj.cameraPos) { | ||||
|         camera.setPos(obj.cameraPos); | ||||
|         notifyListeners(); | ||||
|       } | ||||
|       if (obj.rotation != null) { | ||||
|         this.rotation = obj.rotation; | ||||
|       } | ||||
|       if (obj.rotateSpeed != null) { | ||||
|         this.rotateSpeed = obj.rotateSpeed; | ||||
|       } | ||||
|       if (obj.resetRotation) { | ||||
|         object.resetRotation(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,29 +1,29 @@ | |||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.engine.Vector3; | ||||
| 
 | ||||
| public class ObjFrameSettings { | ||||
| 
 | ||||
|   protected Double focalLength; | ||||
|   protected Vector3 cameraPos; | ||||
|   protected Vector3 rotation; | ||||
|   protected Double rotateSpeed; | ||||
|   protected boolean resetRotation = false; | ||||
| 
 | ||||
|   protected ObjFrameSettings(double focalLength) { | ||||
|     this.focalLength = focalLength; | ||||
|   } | ||||
| 
 | ||||
|   protected ObjFrameSettings(Vector3 cameraPos) { | ||||
|     this.cameraPos = cameraPos; | ||||
|   } | ||||
| 
 | ||||
|   protected ObjFrameSettings(Vector3 rotation, Double rotateSpeed) { | ||||
|     this.rotation = rotation; | ||||
|     this.rotateSpeed = rotateSpeed; | ||||
|   } | ||||
| 
 | ||||
|   protected ObjFrameSettings(boolean resetRotation) { | ||||
|     this.resetRotation = resetRotation; | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.engine.Vector3; | ||||
| 
 | ||||
| public class ObjFrameSettings { | ||||
| 
 | ||||
|   protected Double focalLength; | ||||
|   protected Vector3 cameraPos; | ||||
|   protected Vector3 rotation; | ||||
|   protected Double rotateSpeed; | ||||
|   protected boolean resetRotation = false; | ||||
| 
 | ||||
|   protected ObjFrameSettings(double focalLength) { | ||||
|     this.focalLength = focalLength; | ||||
|   } | ||||
| 
 | ||||
|   protected ObjFrameSettings(Vector3 cameraPos) { | ||||
|     this.cameraPos = cameraPos; | ||||
|   } | ||||
| 
 | ||||
|   protected ObjFrameSettings(Vector3 rotation, Double rotateSpeed) { | ||||
|     this.rotation = rotation; | ||||
|     this.rotateSpeed = rotateSpeed; | ||||
|   } | ||||
| 
 | ||||
|   protected ObjFrameSettings(boolean resetRotation) { | ||||
|     this.resetRotation = resetRotation; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,70 +1,70 @@ | |||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.engine.Camera; | ||||
| import sh.ball.engine.Vector3; | ||||
| import sh.ball.engine.WorldObject; | ||||
| 
 | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.parser.FileParser; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| public class ObjParser extends FileParser<FrameSet<List<Shape>>> { | ||||
| 
 | ||||
|   private final boolean isDefaultPosition; | ||||
|   private final InputStream input; | ||||
|   private final Camera camera; | ||||
| 
 | ||||
|   private WorldObject object; | ||||
| 
 | ||||
|   public ObjParser(InputStream input, float cameraX, float cameraY, float cameraZ, | ||||
|                    float focalLength, boolean isDefaultPosition) { | ||||
|     this.input = input; | ||||
|     this.isDefaultPosition = isDefaultPosition; | ||||
|     Vector3 cameraPos = new Vector3(cameraX, cameraY, cameraZ); | ||||
|     this.camera = new Camera(focalLength, cameraPos); | ||||
|   } | ||||
| 
 | ||||
|   public ObjParser(InputStream input, float focalLength) { | ||||
|     this(input, 0, 0, 0, focalLength, true); | ||||
|   } | ||||
| 
 | ||||
|   public ObjParser(InputStream input) { | ||||
|     this(input, (float) Camera.DEFAULT_FOCAL_LENGTH); | ||||
|   } | ||||
| 
 | ||||
|   public ObjParser(String path) throws FileNotFoundException { | ||||
|     this(new FileInputStream(path), (float) Camera.DEFAULT_FOCAL_LENGTH); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getFileExtension() { | ||||
|     return "obj"; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public FrameSet<List<Shape>> parse() throws IllegalArgumentException, IOException { | ||||
|     object = new WorldObject(input); | ||||
|     camera.findZPos(object); | ||||
| 
 | ||||
|     return new ObjFrameSet(object, camera); | ||||
|   } | ||||
| 
 | ||||
|   // If camera position arguments haven't been specified, automatically work out the position of
 | ||||
|   // the camera based on the size of the object in the camera's view.
 | ||||
|   public void setFocalLength(double focalLength) { | ||||
|     camera.setFocalLength(focalLength); | ||||
|     if (isDefaultPosition) { | ||||
|       camera.findZPos(object); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static boolean isObjFile(String path) { | ||||
|     return path.matches(".*\\.obj"); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.engine.Camera; | ||||
| import sh.ball.engine.Vector3; | ||||
| import sh.ball.engine.WorldObject; | ||||
| 
 | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.parser.FileParser; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| public class ObjParser extends FileParser<FrameSet<List<Shape>>> { | ||||
| 
 | ||||
|   private final boolean isDefaultPosition; | ||||
|   private final InputStream input; | ||||
|   private final Camera camera; | ||||
| 
 | ||||
|   private WorldObject object; | ||||
| 
 | ||||
|   public ObjParser(InputStream input, float cameraX, float cameraY, float cameraZ, | ||||
|                    float focalLength, boolean isDefaultPosition) { | ||||
|     this.input = input; | ||||
|     this.isDefaultPosition = isDefaultPosition; | ||||
|     Vector3 cameraPos = new Vector3(cameraX, cameraY, cameraZ); | ||||
|     this.camera = new Camera(focalLength, cameraPos); | ||||
|   } | ||||
| 
 | ||||
|   public ObjParser(InputStream input, float focalLength) { | ||||
|     this(input, 0, 0, 0, focalLength, true); | ||||
|   } | ||||
| 
 | ||||
|   public ObjParser(InputStream input) { | ||||
|     this(input, (float) Camera.DEFAULT_FOCAL_LENGTH); | ||||
|   } | ||||
| 
 | ||||
|   public ObjParser(String path) throws FileNotFoundException { | ||||
|     this(new FileInputStream(path), (float) Camera.DEFAULT_FOCAL_LENGTH); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getFileExtension() { | ||||
|     return "obj"; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public FrameSet<List<Shape>> parse() throws IllegalArgumentException, IOException { | ||||
|     object = new WorldObject(input); | ||||
|     camera.findZPos(object); | ||||
| 
 | ||||
|     return new ObjFrameSet(object, camera); | ||||
|   } | ||||
| 
 | ||||
|   // If camera position arguments haven't been specified, automatically work out the position of
 | ||||
|   // the camera based on the size of the object in the camera's view.
 | ||||
|   public void setFocalLength(double focalLength) { | ||||
|     camera.setFocalLength(focalLength); | ||||
|     if (isDefaultPosition) { | ||||
|       camera.findZPos(object); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static boolean isObjFile(String path) { | ||||
|     return path.matches(".*\\.obj"); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,26 +1,26 @@ | |||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.engine.Vector3; | ||||
| 
 | ||||
| public class ObjSettingsFactory { | ||||
| 
 | ||||
|   public static ObjFrameSettings focalLength(double focalLength) { | ||||
|     return new ObjFrameSettings(focalLength); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings cameraPosition(Vector3 cameraPos) { | ||||
|     return new ObjFrameSettings(cameraPos); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings rotation(Vector3 rotation) { | ||||
|     return new ObjFrameSettings(rotation, null); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings rotateSpeed(double rotateSpeed) { | ||||
|     return new ObjFrameSettings(null, rotateSpeed); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings resetRotation() { | ||||
|     return new ObjFrameSettings(true); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.obj; | ||||
| 
 | ||||
| import sh.ball.engine.Vector3; | ||||
| 
 | ||||
| public class ObjSettingsFactory { | ||||
| 
 | ||||
|   public static ObjFrameSettings focalLength(double focalLength) { | ||||
|     return new ObjFrameSettings(focalLength); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings cameraPosition(Vector3 cameraPos) { | ||||
|     return new ObjFrameSettings(cameraPos); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings rotation(Vector3 rotation) { | ||||
|     return new ObjFrameSettings(rotation, null); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings rotateSpeed(double rotateSpeed) { | ||||
|     return new ObjFrameSettings(null, rotateSpeed); | ||||
|   } | ||||
| 
 | ||||
|   public static ObjFrameSettings resetRotation() { | ||||
|     return new ObjFrameSettings(true); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,28 +1,28 @@ | |||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Line; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| class ClosePath { | ||||
| 
 | ||||
|   // Parses close path commands (Z and z commands)
 | ||||
|   private static List<Shape> parseClosePath(SvgState state, List<Float> args) { | ||||
|     if (!state.currPoint.equals(state.initialPoint)) { | ||||
|       Line line = new Line(state.currPoint, state.initialPoint); | ||||
|       state.currPoint = state.initialPoint; | ||||
|       return List.of(line); | ||||
|     } else { | ||||
|       return List.of(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseClosePath(state, args); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseClosePath(state, args); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Line; | ||||
| import sh.ball.shapes.Shape; | ||||
| 
 | ||||
| class ClosePath { | ||||
| 
 | ||||
|   // Parses close path commands (Z and z commands)
 | ||||
|   private static List<Shape> parseClosePath(SvgState state, List<Float> args) { | ||||
|     if (!state.currPoint.equals(state.initialPoint)) { | ||||
|       Line line = new Line(state.currPoint, state.initialPoint); | ||||
|       state.currPoint = state.initialPoint; | ||||
|       return List.of(line); | ||||
|     } else { | ||||
|       return List.of(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseClosePath(state, args); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseClosePath(state, args); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,105 +1,105 @@ | |||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.CubicBezierCurve; | ||||
| import sh.ball.shapes.QuadraticBezierCurve; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class CurveTo { | ||||
| 
 | ||||
|   // Parses curveto commands (C, c, S, s, Q, q, T, and t commands)
 | ||||
|   // isCubic should be true for parsing C, c, S, and s commands
 | ||||
|   // isCubic should be false for parsing Q, q, T, and t commands
 | ||||
|   // isSmooth should be true for parsing S, s, T, and t commands
 | ||||
|   // isSmooth should be false for parsing C, c, Q, and q commands
 | ||||
|   private static List<Shape> parseCurveTo(SvgState state, List<Float> args, boolean isAbsolute, | ||||
|                                           boolean isCubic, boolean isSmooth) { | ||||
|     int expectedArgs = isCubic ? 4 : 2; | ||||
|     if (!isSmooth) { | ||||
|       expectedArgs += 2; | ||||
|     } | ||||
| 
 | ||||
|     if (args.size() % expectedArgs != 0 || args.size() < expectedArgs) { | ||||
|       throw new IllegalArgumentException("SVG curveto command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     List<Shape> curves = new ArrayList<>(); | ||||
| 
 | ||||
|     for (int i = 0; i < args.size(); i += expectedArgs) { | ||||
|       Vector2 controlPoint1; | ||||
|       Vector2 controlPoint2 = new Vector2(); | ||||
| 
 | ||||
|       if (isSmooth) { | ||||
|         if (isCubic) { | ||||
|           controlPoint1 = state.prevCubicControlPoint == null ? state.currPoint | ||||
|             : state.prevCubicControlPoint.reflectRelativeToVector(state.currPoint); | ||||
|         } else { | ||||
|           controlPoint1 = state.prevQuadraticControlPoint == null ? state.currPoint | ||||
|             : state.prevQuadraticControlPoint.reflectRelativeToVector(state.currPoint); | ||||
|         } | ||||
|       } else { | ||||
|         controlPoint1 = new Vector2(args.get(i), args.get(i + 1)); | ||||
|       } | ||||
| 
 | ||||
|       if (isCubic) { | ||||
|         controlPoint2 = new Vector2(args.get(i + 2), args.get(i + 3)); | ||||
|       } | ||||
| 
 | ||||
|       Vector2 newPoint = new Vector2(args.get(i + expectedArgs - 2), | ||||
|         args.get(i + expectedArgs - 1)); | ||||
| 
 | ||||
|       if (!isAbsolute) { | ||||
|         controlPoint1 = state.currPoint.translate(controlPoint1); | ||||
|         controlPoint2 = state.currPoint.translate(controlPoint2); | ||||
|         newPoint = state.currPoint.translate(newPoint); | ||||
|       } | ||||
| 
 | ||||
|       if (isCubic) { | ||||
|         curves.add(new CubicBezierCurve(state.currPoint, controlPoint1, controlPoint2, newPoint)); | ||||
|         state.currPoint = newPoint; | ||||
|         state.prevCubicControlPoint = controlPoint2; | ||||
|       } else { | ||||
|         curves.add(new QuadraticBezierCurve(state.currPoint, controlPoint1, newPoint)); | ||||
|         state.currPoint = newPoint; | ||||
|         state.prevQuadraticControlPoint = controlPoint1; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return curves; | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> smoothAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> smoothRelative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, false, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticRelative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, false, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticSmoothAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, false, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticSmoothRelative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, false, true); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.CubicBezierCurve; | ||||
| import sh.ball.shapes.QuadraticBezierCurve; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class CurveTo { | ||||
| 
 | ||||
|   // Parses curveto commands (C, c, S, s, Q, q, T, and t commands)
 | ||||
|   // isCubic should be true for parsing C, c, S, and s commands
 | ||||
|   // isCubic should be false for parsing Q, q, T, and t commands
 | ||||
|   // isSmooth should be true for parsing S, s, T, and t commands
 | ||||
|   // isSmooth should be false for parsing C, c, Q, and q commands
 | ||||
|   private static List<Shape> parseCurveTo(SvgState state, List<Float> args, boolean isAbsolute, | ||||
|                                           boolean isCubic, boolean isSmooth) { | ||||
|     int expectedArgs = isCubic ? 4 : 2; | ||||
|     if (!isSmooth) { | ||||
|       expectedArgs += 2; | ||||
|     } | ||||
| 
 | ||||
|     if (args.size() % expectedArgs != 0 || args.size() < expectedArgs) { | ||||
|       throw new IllegalArgumentException("SVG curveto command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     List<Shape> curves = new ArrayList<>(); | ||||
| 
 | ||||
|     for (int i = 0; i < args.size(); i += expectedArgs) { | ||||
|       Vector2 controlPoint1; | ||||
|       Vector2 controlPoint2 = new Vector2(); | ||||
| 
 | ||||
|       if (isSmooth) { | ||||
|         if (isCubic) { | ||||
|           controlPoint1 = state.prevCubicControlPoint == null ? state.currPoint | ||||
|             : state.prevCubicControlPoint.reflectRelativeToVector(state.currPoint); | ||||
|         } else { | ||||
|           controlPoint1 = state.prevQuadraticControlPoint == null ? state.currPoint | ||||
|             : state.prevQuadraticControlPoint.reflectRelativeToVector(state.currPoint); | ||||
|         } | ||||
|       } else { | ||||
|         controlPoint1 = new Vector2(args.get(i), args.get(i + 1)); | ||||
|       } | ||||
| 
 | ||||
|       if (isCubic) { | ||||
|         controlPoint2 = new Vector2(args.get(i + 2), args.get(i + 3)); | ||||
|       } | ||||
| 
 | ||||
|       Vector2 newPoint = new Vector2(args.get(i + expectedArgs - 2), | ||||
|         args.get(i + expectedArgs - 1)); | ||||
| 
 | ||||
|       if (!isAbsolute) { | ||||
|         controlPoint1 = state.currPoint.translate(controlPoint1); | ||||
|         controlPoint2 = state.currPoint.translate(controlPoint2); | ||||
|         newPoint = state.currPoint.translate(newPoint); | ||||
|       } | ||||
| 
 | ||||
|       if (isCubic) { | ||||
|         curves.add(new CubicBezierCurve(state.currPoint, controlPoint1, controlPoint2, newPoint)); | ||||
|         state.currPoint = newPoint; | ||||
|         state.prevCubicControlPoint = controlPoint2; | ||||
|       } else { | ||||
|         curves.add(new QuadraticBezierCurve(state.currPoint, controlPoint1, newPoint)); | ||||
|         state.currPoint = newPoint; | ||||
|         state.prevQuadraticControlPoint = controlPoint1; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return curves; | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> smoothAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> smoothRelative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, false, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticRelative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, false, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticSmoothAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, true, false, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> quarticSmoothRelative(SvgState state, List<Float> args) { | ||||
|     return parseCurveTo(state, args, false, false, true); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,146 +1,146 @@ | |||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.awt.geom.AffineTransform; | ||||
| import java.awt.geom.Arc2D; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Line; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class EllipticalArcTo { | ||||
| 
 | ||||
|   private static final int EXPECTED_ARGS = 7; | ||||
| 
 | ||||
|   private static List<Shape> parseEllipticalArc(SvgState state, List<Float> args, boolean isAbsolute) { | ||||
|     if (args.size() % EXPECTED_ARGS != 0 || args.size() < EXPECTED_ARGS) { | ||||
|       throw new IllegalArgumentException( | ||||
|         "SVG elliptical arc command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     List<Shape> arcs = new ArrayList<>(); | ||||
| 
 | ||||
|     for (int i = 0; i < args.size(); i += EXPECTED_ARGS) { | ||||
|       Vector2 newPoint = new Vector2(args.get(i + 5), args.get(i + 6)); | ||||
| 
 | ||||
|       newPoint = isAbsolute ? newPoint : state.currPoint.translate(newPoint); | ||||
| 
 | ||||
|       arcs.addAll(createArc( | ||||
|         state.currPoint, | ||||
|         args.get(i), | ||||
|         args.get(i + 1), | ||||
|         args.get(i + 2), | ||||
|         args.get(i + 3) == 1, | ||||
|         args.get(i + 4) == 1, | ||||
|         newPoint | ||||
|       )); | ||||
| 
 | ||||
|       state.currPoint = newPoint; | ||||
|     } | ||||
| 
 | ||||
|     return arcs; | ||||
|   } | ||||
| 
 | ||||
|   // The following algorithm is completely based on https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
 | ||||
|   private static List<Shape> createArc(Vector2 start, double rx, double ry, float theta, boolean largeArcFlag, boolean sweepFlag, Vector2 end) { | ||||
|     double x2 = end.getX(); | ||||
|     double y2 = end.getY(); | ||||
|     // Ensure radii are valid
 | ||||
|     if (rx == 0 || ry == 0) { | ||||
|       return List.of(new Line(start, end)); | ||||
|     } | ||||
|     double x1 = start.getX(); | ||||
|     double y1 = start.getY(); | ||||
|     // Compute the half distance between the current and the final point
 | ||||
|     double dx2 = (x1 - x2) / 2.0; | ||||
|     double dy2 = (y1 - y2) / 2.0; | ||||
|     // Convert theta from degrees to radians
 | ||||
|     theta = (float) Math.toRadians(theta % 360); | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 1 : Compute (x1', y1')
 | ||||
|     //
 | ||||
|     double x1prime = Math.cos(theta) * dx2 + Math.sin(theta) * dy2; | ||||
|     double y1prime = -Math.sin(theta) * dx2 + Math.cos(theta) * dy2; | ||||
|     // Ensure radii are large enough
 | ||||
|     rx = Math.abs(rx); | ||||
|     ry = Math.abs(ry); | ||||
|     double Prx = rx * rx; | ||||
|     double Pry = ry * ry; | ||||
|     double Px1prime = x1prime * x1prime; | ||||
|     double Py1prime = y1prime * y1prime; | ||||
|     double d = Px1prime / Prx + Py1prime / Pry; | ||||
|     if (d > 1) { | ||||
|       rx = Math.abs(Math.sqrt(d) * rx); | ||||
|       ry = Math.abs(Math.sqrt(d) * ry); | ||||
|       Prx = rx * rx; | ||||
|       Pry = ry * ry; | ||||
|     } | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 2 : Compute (cx', cy')
 | ||||
|     //
 | ||||
|     double sign = (largeArcFlag == sweepFlag) ? -1.0 : 1.0; | ||||
|     // Forcing the inner term to be positive. It should be >= 0 but can sometimes be negative due
 | ||||
|     // to double precision.
 | ||||
|     double coef = sign * | ||||
|       Math.sqrt(Math.abs((Prx * Pry) - (Prx * Py1prime) - (Pry * Px1prime)) / ((Prx * Py1prime) + (Pry * Px1prime))); | ||||
|     double cxprime = coef * ((rx * y1prime) / ry); | ||||
|     double cyprime = coef * -((ry * x1prime) / rx); | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 3 : Compute (cx, cy) from (cx', cy')
 | ||||
|     //
 | ||||
|     double sx2 = (x1 + x2) / 2.0; | ||||
|     double sy2 = (y1 + y2) / 2.0; | ||||
|     double cx = sx2 + Math.cos(theta) * cxprime - Math.sin(theta) * cyprime; | ||||
|     double cy = sy2 + Math.sin(theta) * cxprime + Math.cos(theta) * cyprime; | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 4 : Compute the angleStart (theta1) and the angleExtent (dtheta)
 | ||||
|     //
 | ||||
|     double ux = (x1prime - cxprime) / rx; | ||||
|     double uy = (y1prime - cyprime) / ry; | ||||
|     double vx = (-x1prime - cxprime) / rx; | ||||
|     double vy = (-y1prime - cyprime) / ry; | ||||
|     double p, n; | ||||
|     // Compute the angle start
 | ||||
|     n = Math.sqrt((ux * ux) + (uy * uy)); | ||||
|     p = ux; // (1 * ux) + (0 * uy)
 | ||||
|     sign = (uy < 0) ? -1.0 : 1.0; | ||||
|     double angleStart = Math.toDegrees(sign * Math.acos(p / n)); | ||||
|     // Compute the angle extent
 | ||||
|     n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); | ||||
|     p = ux * vx + uy * vy; | ||||
|     sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0; | ||||
|     double angleExtent = Math.toDegrees(sign * Math.acos(p / n)); | ||||
|     if (!sweepFlag && angleExtent > 0) { | ||||
|       angleExtent -= 360; | ||||
|     } else if (sweepFlag && angleExtent < 0) { | ||||
|       angleExtent += 360; | ||||
|     } | ||||
|     angleExtent %= 360; | ||||
|     angleStart %= 360; | ||||
| 
 | ||||
|     Arc2D.Float arc = new Arc2D.Float(); | ||||
|     arc.x = (float) (cx - rx); | ||||
|     arc.y = (float) (cy - ry); | ||||
|     arc.width = (float) (rx * 2.0); | ||||
|     arc.height = (float) (ry * 2.0); | ||||
|     arc.start = (float) -angleStart; | ||||
|     arc.extent = (float) -angleExtent; | ||||
| 
 | ||||
|     AffineTransform transform = AffineTransform.getRotateInstance(theta, arc.getX() + arc.getWidth()/2, arc.getY() + arc.getHeight()/2); | ||||
| 
 | ||||
|     return Shape.convert(transform.createTransformedShape(arc)); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseEllipticalArc(state, args, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseEllipticalArc(state, args, false); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.awt.geom.AffineTransform; | ||||
| import java.awt.geom.Arc2D; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Line; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class EllipticalArcTo { | ||||
| 
 | ||||
|   private static final int EXPECTED_ARGS = 7; | ||||
| 
 | ||||
|   private static List<Shape> parseEllipticalArc(SvgState state, List<Float> args, boolean isAbsolute) { | ||||
|     if (args.size() % EXPECTED_ARGS != 0 || args.size() < EXPECTED_ARGS) { | ||||
|       throw new IllegalArgumentException( | ||||
|         "SVG elliptical arc command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     List<Shape> arcs = new ArrayList<>(); | ||||
| 
 | ||||
|     for (int i = 0; i < args.size(); i += EXPECTED_ARGS) { | ||||
|       Vector2 newPoint = new Vector2(args.get(i + 5), args.get(i + 6)); | ||||
| 
 | ||||
|       newPoint = isAbsolute ? newPoint : state.currPoint.translate(newPoint); | ||||
| 
 | ||||
|       arcs.addAll(createArc( | ||||
|         state.currPoint, | ||||
|         args.get(i), | ||||
|         args.get(i + 1), | ||||
|         args.get(i + 2), | ||||
|         args.get(i + 3) == 1, | ||||
|         args.get(i + 4) == 1, | ||||
|         newPoint | ||||
|       )); | ||||
| 
 | ||||
|       state.currPoint = newPoint; | ||||
|     } | ||||
| 
 | ||||
|     return arcs; | ||||
|   } | ||||
| 
 | ||||
|   // The following algorithm is completely based on https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
 | ||||
|   private static List<Shape> createArc(Vector2 start, double rx, double ry, float theta, boolean largeArcFlag, boolean sweepFlag, Vector2 end) { | ||||
|     double x2 = end.getX(); | ||||
|     double y2 = end.getY(); | ||||
|     // Ensure radii are valid
 | ||||
|     if (rx == 0 || ry == 0) { | ||||
|       return List.of(new Line(start, end)); | ||||
|     } | ||||
|     double x1 = start.getX(); | ||||
|     double y1 = start.getY(); | ||||
|     // Compute the half distance between the current and the final point
 | ||||
|     double dx2 = (x1 - x2) / 2.0; | ||||
|     double dy2 = (y1 - y2) / 2.0; | ||||
|     // Convert theta from degrees to radians
 | ||||
|     theta = (float) Math.toRadians(theta % 360); | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 1 : Compute (x1', y1')
 | ||||
|     //
 | ||||
|     double x1prime = Math.cos(theta) * dx2 + Math.sin(theta) * dy2; | ||||
|     double y1prime = -Math.sin(theta) * dx2 + Math.cos(theta) * dy2; | ||||
|     // Ensure radii are large enough
 | ||||
|     rx = Math.abs(rx); | ||||
|     ry = Math.abs(ry); | ||||
|     double Prx = rx * rx; | ||||
|     double Pry = ry * ry; | ||||
|     double Px1prime = x1prime * x1prime; | ||||
|     double Py1prime = y1prime * y1prime; | ||||
|     double d = Px1prime / Prx + Py1prime / Pry; | ||||
|     if (d > 1) { | ||||
|       rx = Math.abs(Math.sqrt(d) * rx); | ||||
|       ry = Math.abs(Math.sqrt(d) * ry); | ||||
|       Prx = rx * rx; | ||||
|       Pry = ry * ry; | ||||
|     } | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 2 : Compute (cx', cy')
 | ||||
|     //
 | ||||
|     double sign = (largeArcFlag == sweepFlag) ? -1.0 : 1.0; | ||||
|     // Forcing the inner term to be positive. It should be >= 0 but can sometimes be negative due
 | ||||
|     // to double precision.
 | ||||
|     double coef = sign * | ||||
|       Math.sqrt(Math.abs((Prx * Pry) - (Prx * Py1prime) - (Pry * Px1prime)) / ((Prx * Py1prime) + (Pry * Px1prime))); | ||||
|     double cxprime = coef * ((rx * y1prime) / ry); | ||||
|     double cyprime = coef * -((ry * x1prime) / rx); | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 3 : Compute (cx, cy) from (cx', cy')
 | ||||
|     //
 | ||||
|     double sx2 = (x1 + x2) / 2.0; | ||||
|     double sy2 = (y1 + y2) / 2.0; | ||||
|     double cx = sx2 + Math.cos(theta) * cxprime - Math.sin(theta) * cyprime; | ||||
|     double cy = sy2 + Math.sin(theta) * cxprime + Math.cos(theta) * cyprime; | ||||
| 
 | ||||
|     //
 | ||||
|     // Step 4 : Compute the angleStart (theta1) and the angleExtent (dtheta)
 | ||||
|     //
 | ||||
|     double ux = (x1prime - cxprime) / rx; | ||||
|     double uy = (y1prime - cyprime) / ry; | ||||
|     double vx = (-x1prime - cxprime) / rx; | ||||
|     double vy = (-y1prime - cyprime) / ry; | ||||
|     double p, n; | ||||
|     // Compute the angle start
 | ||||
|     n = Math.sqrt((ux * ux) + (uy * uy)); | ||||
|     p = ux; // (1 * ux) + (0 * uy)
 | ||||
|     sign = (uy < 0) ? -1.0 : 1.0; | ||||
|     double angleStart = Math.toDegrees(sign * Math.acos(p / n)); | ||||
|     // Compute the angle extent
 | ||||
|     n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); | ||||
|     p = ux * vx + uy * vy; | ||||
|     sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0; | ||||
|     double angleExtent = Math.toDegrees(sign * Math.acos(p / n)); | ||||
|     if (!sweepFlag && angleExtent > 0) { | ||||
|       angleExtent -= 360; | ||||
|     } else if (sweepFlag && angleExtent < 0) { | ||||
|       angleExtent += 360; | ||||
|     } | ||||
|     angleExtent %= 360; | ||||
|     angleStart %= 360; | ||||
| 
 | ||||
|     Arc2D.Float arc = new Arc2D.Float(); | ||||
|     arc.x = (float) (cx - rx); | ||||
|     arc.y = (float) (cy - ry); | ||||
|     arc.width = (float) (rx * 2.0); | ||||
|     arc.height = (float) (ry * 2.0); | ||||
|     arc.start = (float) -angleStart; | ||||
|     arc.extent = (float) -angleExtent; | ||||
| 
 | ||||
|     AffineTransform transform = AffineTransform.getRotateInstance(theta, arc.getX() + arc.getWidth()/2, arc.getY() + arc.getHeight()/2); | ||||
| 
 | ||||
|     return Shape.convert(transform.createTransformedShape(arc)); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseEllipticalArc(state, args, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseEllipticalArc(state, args, false); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,75 +1,75 @@ | |||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Line; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class LineTo { | ||||
| 
 | ||||
|   // Parses lineto commands (L, l, H, h, V, and v commands)
 | ||||
|   // isHorizontal and isVertical should be true for parsing L and l commands
 | ||||
|   // Only isHorizontal should be true for parsing H and h commands
 | ||||
|   // Only isVertical should be true for parsing V and v commands
 | ||||
|   private static List<Shape> parseLineTo(SvgState state, List<Float> args, boolean isAbsolute, | ||||
|                                          boolean isHorizontal, boolean isVertical) { | ||||
|     int expectedArgs = isHorizontal && isVertical ? 2 : 1; | ||||
| 
 | ||||
|     if (args.size() % expectedArgs != 0 || args.size() < expectedArgs) { | ||||
|       throw new IllegalArgumentException("SVG lineto command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     List<Shape> lines = new ArrayList<>(); | ||||
| 
 | ||||
|     for (int i = 0; i < args.size(); i += expectedArgs) { | ||||
|       Vector2 newPoint; | ||||
| 
 | ||||
|       if (expectedArgs == 1) { | ||||
|         newPoint = new Vector2(args.get(i), args.get(i)); | ||||
|       } else { | ||||
|         newPoint = new Vector2(args.get(i), args.get(i + 1)); | ||||
|       } | ||||
| 
 | ||||
|       if (isHorizontal && !isVertical) { | ||||
|         newPoint = isAbsolute ? newPoint.setY(state.currPoint.getY()) : newPoint.setY(0); | ||||
|       } else if (isVertical && !isHorizontal) { | ||||
|         newPoint = isAbsolute ? newPoint.setX(state.currPoint.getX()) : newPoint.setX(0); | ||||
|       } | ||||
| 
 | ||||
|       if (!isAbsolute) { | ||||
|         newPoint = state.currPoint.translate(newPoint); | ||||
|       } | ||||
| 
 | ||||
|       lines.add(new Line(state.currPoint, newPoint)); | ||||
|       state.currPoint = newPoint; | ||||
|     } | ||||
| 
 | ||||
|     return lines; | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, true, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, false, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> horizontalAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, true, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> horizontalRelative(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, false, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> verticalAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, true, false, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> verticalRelative(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, false, false, true); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Line; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class LineTo { | ||||
| 
 | ||||
|   // Parses lineto commands (L, l, H, h, V, and v commands)
 | ||||
|   // isHorizontal and isVertical should be true for parsing L and l commands
 | ||||
|   // Only isHorizontal should be true for parsing H and h commands
 | ||||
|   // Only isVertical should be true for parsing V and v commands
 | ||||
|   private static List<Shape> parseLineTo(SvgState state, List<Float> args, boolean isAbsolute, | ||||
|                                          boolean isHorizontal, boolean isVertical) { | ||||
|     int expectedArgs = isHorizontal && isVertical ? 2 : 1; | ||||
| 
 | ||||
|     if (args.size() % expectedArgs != 0 || args.size() < expectedArgs) { | ||||
|       throw new IllegalArgumentException("SVG lineto command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     List<Shape> lines = new ArrayList<>(); | ||||
| 
 | ||||
|     for (int i = 0; i < args.size(); i += expectedArgs) { | ||||
|       Vector2 newPoint; | ||||
| 
 | ||||
|       if (expectedArgs == 1) { | ||||
|         newPoint = new Vector2(args.get(i), args.get(i)); | ||||
|       } else { | ||||
|         newPoint = new Vector2(args.get(i), args.get(i + 1)); | ||||
|       } | ||||
| 
 | ||||
|       if (isHorizontal && !isVertical) { | ||||
|         newPoint = isAbsolute ? newPoint.setY(state.currPoint.getY()) : newPoint.setY(0); | ||||
|       } else if (isVertical && !isHorizontal) { | ||||
|         newPoint = isAbsolute ? newPoint.setX(state.currPoint.getX()) : newPoint.setX(0); | ||||
|       } | ||||
| 
 | ||||
|       if (!isAbsolute) { | ||||
|         newPoint = state.currPoint.translate(newPoint); | ||||
|       } | ||||
| 
 | ||||
|       lines.add(new Line(state.currPoint, newPoint)); | ||||
|       state.currPoint = newPoint; | ||||
|     } | ||||
| 
 | ||||
|     return lines; | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, true, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, false, true, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> horizontalAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, true, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> horizontalRelative(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, false, true, false); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> verticalAbsolute(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, true, false, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> verticalRelative(SvgState state, List<Float> args) { | ||||
|     return parseLineTo(state, args, false, false, true); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,43 +1,43 @@ | |||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class MoveTo { | ||||
| 
 | ||||
|   // Parses moveto commands (M and m commands)
 | ||||
|   private static List<Shape> parseMoveTo(SvgState state, List<Float> args, boolean isAbsolute) { | ||||
|     if (args.size() % 2 != 0 || args.size() < 2) { | ||||
|       throw new IllegalArgumentException("SVG moveto command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     Vector2 vec = new Vector2(args.get(0), args.get(1)); | ||||
| 
 | ||||
|     if (isAbsolute) { | ||||
|       state.currPoint = vec; | ||||
|       state.initialPoint = state.currPoint; | ||||
|       if (args.size() > 2) { | ||||
|         return LineTo.absolute(state, args.subList(2, args.size() - 1)); | ||||
|       } | ||||
|     } else { | ||||
|       state.currPoint = state.currPoint.translate(vec); | ||||
|       state.initialPoint = state.currPoint; | ||||
|       if (args.size() > 2) { | ||||
|         return LineTo.relative(state, args.subList(2, args.size() - 1)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return new ArrayList<>(); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseMoveTo(state, args, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseMoveTo(state, args, false); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| class MoveTo { | ||||
| 
 | ||||
|   // Parses moveto commands (M and m commands)
 | ||||
|   private static List<Shape> parseMoveTo(SvgState state, List<Float> args, boolean isAbsolute) { | ||||
|     if (args.size() % 2 != 0 || args.size() < 2) { | ||||
|       throw new IllegalArgumentException("SVG moveto command has incorrect number of arguments."); | ||||
|     } | ||||
| 
 | ||||
|     Vector2 vec = new Vector2(args.get(0), args.get(1)); | ||||
| 
 | ||||
|     if (isAbsolute) { | ||||
|       state.currPoint = vec; | ||||
|       state.initialPoint = state.currPoint; | ||||
|       if (args.size() > 2) { | ||||
|         return LineTo.absolute(state, args.subList(2, args.size() - 1)); | ||||
|       } | ||||
|     } else { | ||||
|       state.currPoint = state.currPoint.translate(vec); | ||||
|       state.initialPoint = state.currPoint; | ||||
|       if (args.size() > 2) { | ||||
|         return LineTo.relative(state, args.subList(2, args.size() - 1)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return new ArrayList<>(); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> absolute(SvgState state, List<Float> args) { | ||||
|     return parseMoveTo(state, args, true); | ||||
|   } | ||||
| 
 | ||||
|   static List<Shape> relative(SvgState state, List<Float> args) { | ||||
|     return parseMoveTo(state, args, false); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,210 +1,210 @@ | |||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import static sh.ball.parser.XmlUtil.asList; | ||||
| import static sh.ball.parser.XmlUtil.getAttributesOnTags; | ||||
| import static sh.ball.parser.XmlUtil.getNodeValue; | ||||
| import static sh.ball.parser.XmlUtil.getXMLDocument; | ||||
| 
 | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Predicate; | ||||
| import java.util.stream.Collectors; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.unbescape.html.HtmlEscape; | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Node; | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.parser.FileParser; | ||||
| import sh.ball.shapes.ShapeFrameSet; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class SvgParser extends FileParser<FrameSet<List<Shape>>> { | ||||
| 
 | ||||
|   private final Map<Character, BiFunction<SvgState, List<Float>, List<Shape>>> commandMap; | ||||
|   private final SvgState state; | ||||
|   private final InputStream input; | ||||
| 
 | ||||
|   private Document svg; | ||||
| 
 | ||||
|   public SvgParser(InputStream input) { | ||||
|     this.input = input; | ||||
|     this.state = new SvgState(); | ||||
|     this.commandMap = new HashMap<>(); | ||||
|     initialiseCommandMap(); | ||||
|   } | ||||
| 
 | ||||
|   public SvgParser(String path) throws FileNotFoundException { | ||||
|     this(new FileInputStream(path)); | ||||
|     checkFileExtension(path); | ||||
|   } | ||||
| 
 | ||||
|   // Map command chars to function calls.
 | ||||
|   private void initialiseCommandMap() { | ||||
|     commandMap.put('M', MoveTo::absolute); | ||||
|     commandMap.put('m', MoveTo::relative); | ||||
|     commandMap.put('L', LineTo::absolute); | ||||
|     commandMap.put('l', LineTo::relative); | ||||
|     commandMap.put('H', LineTo::horizontalAbsolute); | ||||
|     commandMap.put('h', LineTo::horizontalRelative); | ||||
|     commandMap.put('V', LineTo::verticalAbsolute); | ||||
|     commandMap.put('v', LineTo::verticalRelative); | ||||
|     commandMap.put('C', CurveTo::absolute); | ||||
|     commandMap.put('c', CurveTo::relative); | ||||
|     commandMap.put('S', CurveTo::smoothAbsolute); | ||||
|     commandMap.put('s', CurveTo::smoothRelative); | ||||
|     commandMap.put('Q', CurveTo::quarticAbsolute); | ||||
|     commandMap.put('q', CurveTo::quarticRelative); | ||||
|     commandMap.put('T', CurveTo::quarticSmoothAbsolute); | ||||
|     commandMap.put('t', CurveTo::quarticSmoothRelative); | ||||
|     commandMap.put('A', EllipticalArcTo::absolute); | ||||
|     commandMap.put('a', EllipticalArcTo::relative); | ||||
|     commandMap.put('Z', ClosePath::absolute); | ||||
|     commandMap.put('z', ClosePath::relative); | ||||
|   } | ||||
| 
 | ||||
|   // Does error checking against SVG path and returns array of SVG commands and arguments
 | ||||
|   private String[] preProcessPath(String path) throws IllegalArgumentException { | ||||
|     // Replace all commas with spaces and then remove unnecessary whitespace
 | ||||
|     path = path.replace(',', ' '); | ||||
|     path = path.replace("-", " -"); | ||||
|     path = path.replaceAll("\\s+", " "); | ||||
|     path = path.replaceAll("(^\\s|\\s$)", ""); | ||||
| 
 | ||||
|     // If there are any characters in the path that are illegal
 | ||||
|     if (path.matches("[^mlhvcsqtazMLHVCSQTAZ\\-.\\d\\s]")) { | ||||
|       throw new IllegalArgumentException("Illegal characters in SVG path."); | ||||
|       // If there are more than 1 letters or delimiters next to one another
 | ||||
|     } else if (path.matches("[a-zA-Z.\\-]{2,}")) { | ||||
|       throw new IllegalArgumentException( | ||||
|         "Multiple letters or delimiters found next to one another in SVG path."); | ||||
|       // First character in path must be a command
 | ||||
|     } else if (path.matches("^[a-zA-Z]")) { | ||||
|       throw new IllegalArgumentException("Start of SVG path is not a letter."); | ||||
|     } | ||||
| 
 | ||||
|     // Split on SVG path characters to get a list of instructions, keeping the SVG commands
 | ||||
|     return path.split("(?=[mlhvcsqtazMLHVCSQTAZ])"); | ||||
|   } | ||||
| 
 | ||||
|   private static List<Float> splitCommand(String command) { | ||||
|     List<Float> nums = new ArrayList<>(); | ||||
|     String[] decimalSplit = command.split("\\."); | ||||
| 
 | ||||
|     try { | ||||
|       if (decimalSplit.length == 1) { | ||||
|         nums.add(Float.parseFloat(decimalSplit[0])); | ||||
|       } else { | ||||
|         nums.add(Float.parseFloat(decimalSplit[0] + "." + decimalSplit[1])); | ||||
| 
 | ||||
|         for (int i = 2; i < decimalSplit.length; i++) { | ||||
|           nums.add(Float.parseFloat("." + decimalSplit[i])); | ||||
|         } | ||||
|       } | ||||
|     } catch (Exception e) { | ||||
|       System.out.println(Arrays.toString(decimalSplit)); | ||||
|       System.out.println(command); | ||||
|     } | ||||
| 
 | ||||
|     return nums; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getFileExtension() { | ||||
|     return "svg"; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public FrameSet<List<Shape>> parse() | ||||
|     throws ParserConfigurationException, IOException, SAXException, IllegalArgumentException { | ||||
|     this.svg = getXMLDocument(input); | ||||
|     List<Node> svgElem = asList(svg.getElementsByTagName("svg")); | ||||
|     List<Shape> shapes = new ArrayList<>(); | ||||
| 
 | ||||
|     if (svgElem.size() != 1) { | ||||
|       throw new IllegalArgumentException("SVG has either zero or more than one svg element."); | ||||
|     } | ||||
| 
 | ||||
|     // Get all d attributes within path elements in the SVG file.
 | ||||
|     for (Node node : getAttributesOnTags(svg, "path", "d")) { | ||||
|       shapes.addAll(parsePath(node.getNodeValue())); | ||||
|     } | ||||
| 
 | ||||
|     return new ShapeFrameSet(Shape.normalize(shapes)); | ||||
|   } | ||||
| 
 | ||||
|   /* Given a character, will return the glyph associated with it. | ||||
|    * Assumes that the .svg loaded has character glyphs. */ | ||||
|   public List<Shape> parseGlyphsWithUnicode(char unicode) { | ||||
|     for (Node node : asList(svg.getElementsByTagName("glyph"))) { | ||||
|       String unicodeString = getNodeValue(node, "unicode"); | ||||
| 
 | ||||
|       if (unicodeString == null) { | ||||
|         throw new IllegalArgumentException("Glyph should have unicode attribute."); | ||||
|       } | ||||
| 
 | ||||
|       /* Removes all html escaped characters, allowing it to be directly compared to the unicode | ||||
|        * parameter. */ | ||||
|       String decodedString = HtmlEscape.unescapeHtml(unicodeString); | ||||
| 
 | ||||
|       if (String.valueOf(unicode).equals(decodedString)) { | ||||
|         return parsePath(getNodeValue(node, "d")); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return List.of(); | ||||
|   } | ||||
| 
 | ||||
|   // Performs path parsing on a single d=path
 | ||||
|   private List<Shape> parsePath(String path) { | ||||
|     if (path == null) { | ||||
|       return List.of(); | ||||
|     } | ||||
| 
 | ||||
|     state.currPoint = new Vector2(); | ||||
|     state.prevCubicControlPoint = null; | ||||
|     state.prevQuadraticControlPoint = null; | ||||
|     String[] commands = preProcessPath(path); | ||||
|     List<Shape> svgShapes = new ArrayList<>(); | ||||
| 
 | ||||
|     for (String command : commands) { | ||||
|       char commandChar = command.charAt(0); | ||||
|       List<Float> nums = null; | ||||
| 
 | ||||
|       if (commandChar != 'z' && commandChar != 'Z') { | ||||
|         // Split the command into number strings and convert them into floats.
 | ||||
|         nums = Arrays.stream(command.substring(1).split(" ")) | ||||
|           .filter(Predicate.not(String::isBlank)) | ||||
|           .flatMap((numString) -> splitCommand(numString).stream()) | ||||
|           .collect(Collectors.toList()); | ||||
|       } | ||||
| 
 | ||||
|       // Use the nums to get a list of sh.ball.shapes, using the first character in the command to specify
 | ||||
|       // the function to use.
 | ||||
|       svgShapes.addAll(commandMap.get(commandChar).apply(state, nums)); | ||||
| 
 | ||||
|       if (!String.valueOf(commandChar).matches("[csCS]")) { | ||||
|         state.prevCubicControlPoint = null; | ||||
|       } | ||||
|       if (!String.valueOf(commandChar).matches("[qtQT]")) { | ||||
|         state.prevQuadraticControlPoint = null; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return svgShapes; | ||||
|   } | ||||
| 
 | ||||
|   public static boolean isSvgFile(String path) { | ||||
|     return path.matches(".*\\.svg"); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import static sh.ball.parser.XmlUtil.asList; | ||||
| import static sh.ball.parser.XmlUtil.getAttributesOnTags; | ||||
| import static sh.ball.parser.XmlUtil.getNodeValue; | ||||
| import static sh.ball.parser.XmlUtil.getXMLDocument; | ||||
| 
 | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Predicate; | ||||
| import java.util.stream.Collectors; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.unbescape.html.HtmlEscape; | ||||
| import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Node; | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.parser.FileParser; | ||||
| import sh.ball.shapes.ShapeFrameSet; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class SvgParser extends FileParser<FrameSet<List<Shape>>> { | ||||
| 
 | ||||
|   private final Map<Character, BiFunction<SvgState, List<Float>, List<Shape>>> commandMap; | ||||
|   private final SvgState state; | ||||
|   private final InputStream input; | ||||
| 
 | ||||
|   private Document svg; | ||||
| 
 | ||||
|   public SvgParser(InputStream input) { | ||||
|     this.input = input; | ||||
|     this.state = new SvgState(); | ||||
|     this.commandMap = new HashMap<>(); | ||||
|     initialiseCommandMap(); | ||||
|   } | ||||
| 
 | ||||
|   public SvgParser(String path) throws FileNotFoundException { | ||||
|     this(new FileInputStream(path)); | ||||
|     checkFileExtension(path); | ||||
|   } | ||||
| 
 | ||||
|   // Map command chars to function calls.
 | ||||
|   private void initialiseCommandMap() { | ||||
|     commandMap.put('M', MoveTo::absolute); | ||||
|     commandMap.put('m', MoveTo::relative); | ||||
|     commandMap.put('L', LineTo::absolute); | ||||
|     commandMap.put('l', LineTo::relative); | ||||
|     commandMap.put('H', LineTo::horizontalAbsolute); | ||||
|     commandMap.put('h', LineTo::horizontalRelative); | ||||
|     commandMap.put('V', LineTo::verticalAbsolute); | ||||
|     commandMap.put('v', LineTo::verticalRelative); | ||||
|     commandMap.put('C', CurveTo::absolute); | ||||
|     commandMap.put('c', CurveTo::relative); | ||||
|     commandMap.put('S', CurveTo::smoothAbsolute); | ||||
|     commandMap.put('s', CurveTo::smoothRelative); | ||||
|     commandMap.put('Q', CurveTo::quarticAbsolute); | ||||
|     commandMap.put('q', CurveTo::quarticRelative); | ||||
|     commandMap.put('T', CurveTo::quarticSmoothAbsolute); | ||||
|     commandMap.put('t', CurveTo::quarticSmoothRelative); | ||||
|     commandMap.put('A', EllipticalArcTo::absolute); | ||||
|     commandMap.put('a', EllipticalArcTo::relative); | ||||
|     commandMap.put('Z', ClosePath::absolute); | ||||
|     commandMap.put('z', ClosePath::relative); | ||||
|   } | ||||
| 
 | ||||
|   // Does error checking against SVG path and returns array of SVG commands and arguments
 | ||||
|   private String[] preProcessPath(String path) throws IllegalArgumentException { | ||||
|     // Replace all commas with spaces and then remove unnecessary whitespace
 | ||||
|     path = path.replace(',', ' '); | ||||
|     path = path.replace("-", " -"); | ||||
|     path = path.replaceAll("\\s+", " "); | ||||
|     path = path.replaceAll("(^\\s|\\s$)", ""); | ||||
| 
 | ||||
|     // If there are any characters in the path that are illegal
 | ||||
|     if (path.matches("[^mlhvcsqtazMLHVCSQTAZ\\-.\\d\\s]")) { | ||||
|       throw new IllegalArgumentException("Illegal characters in SVG path."); | ||||
|       // If there are more than 1 letters or delimiters next to one another
 | ||||
|     } else if (path.matches("[a-zA-Z.\\-]{2,}")) { | ||||
|       throw new IllegalArgumentException( | ||||
|         "Multiple letters or delimiters found next to one another in SVG path."); | ||||
|       // First character in path must be a command
 | ||||
|     } else if (path.matches("^[a-zA-Z]")) { | ||||
|       throw new IllegalArgumentException("Start of SVG path is not a letter."); | ||||
|     } | ||||
| 
 | ||||
|     // Split on SVG path characters to get a list of instructions, keeping the SVG commands
 | ||||
|     return path.split("(?=[mlhvcsqtazMLHVCSQTAZ])"); | ||||
|   } | ||||
| 
 | ||||
|   private static List<Float> splitCommand(String command) { | ||||
|     List<Float> nums = new ArrayList<>(); | ||||
|     String[] decimalSplit = command.split("\\."); | ||||
| 
 | ||||
|     try { | ||||
|       if (decimalSplit.length == 1) { | ||||
|         nums.add(Float.parseFloat(decimalSplit[0])); | ||||
|       } else { | ||||
|         nums.add(Float.parseFloat(decimalSplit[0] + "." + decimalSplit[1])); | ||||
| 
 | ||||
|         for (int i = 2; i < decimalSplit.length; i++) { | ||||
|           nums.add(Float.parseFloat("." + decimalSplit[i])); | ||||
|         } | ||||
|       } | ||||
|     } catch (Exception e) { | ||||
|       System.out.println(Arrays.toString(decimalSplit)); | ||||
|       System.out.println(command); | ||||
|     } | ||||
| 
 | ||||
|     return nums; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getFileExtension() { | ||||
|     return "svg"; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public FrameSet<List<Shape>> parse() | ||||
|     throws ParserConfigurationException, IOException, SAXException, IllegalArgumentException { | ||||
|     this.svg = getXMLDocument(input); | ||||
|     List<Node> svgElem = asList(svg.getElementsByTagName("svg")); | ||||
|     List<Shape> shapes = new ArrayList<>(); | ||||
| 
 | ||||
|     if (svgElem.size() != 1) { | ||||
|       throw new IllegalArgumentException("SVG has either zero or more than one svg element."); | ||||
|     } | ||||
| 
 | ||||
|     // Get all d attributes within path elements in the SVG file.
 | ||||
|     for (Node node : getAttributesOnTags(svg, "path", "d")) { | ||||
|       shapes.addAll(parsePath(node.getNodeValue())); | ||||
|     } | ||||
| 
 | ||||
|     return new ShapeFrameSet(Shape.normalize(shapes)); | ||||
|   } | ||||
| 
 | ||||
|   /* Given a character, will return the glyph associated with it. | ||||
|    * Assumes that the .svg loaded has character glyphs. */ | ||||
|   public List<Shape> parseGlyphsWithUnicode(char unicode) { | ||||
|     for (Node node : asList(svg.getElementsByTagName("glyph"))) { | ||||
|       String unicodeString = getNodeValue(node, "unicode"); | ||||
| 
 | ||||
|       if (unicodeString == null) { | ||||
|         throw new IllegalArgumentException("Glyph should have unicode attribute."); | ||||
|       } | ||||
| 
 | ||||
|       /* Removes all html escaped characters, allowing it to be directly compared to the unicode | ||||
|        * parameter. */ | ||||
|       String decodedString = HtmlEscape.unescapeHtml(unicodeString); | ||||
| 
 | ||||
|       if (String.valueOf(unicode).equals(decodedString)) { | ||||
|         return parsePath(getNodeValue(node, "d")); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return List.of(); | ||||
|   } | ||||
| 
 | ||||
|   // Performs path parsing on a single d=path
 | ||||
|   private List<Shape> parsePath(String path) { | ||||
|     if (path == null) { | ||||
|       return List.of(); | ||||
|     } | ||||
| 
 | ||||
|     state.currPoint = new Vector2(); | ||||
|     state.prevCubicControlPoint = null; | ||||
|     state.prevQuadraticControlPoint = null; | ||||
|     String[] commands = preProcessPath(path); | ||||
|     List<Shape> svgShapes = new ArrayList<>(); | ||||
| 
 | ||||
|     for (String command : commands) { | ||||
|       char commandChar = command.charAt(0); | ||||
|       List<Float> nums = null; | ||||
| 
 | ||||
|       if (commandChar != 'z' && commandChar != 'Z') { | ||||
|         // Split the command into number strings and convert them into floats.
 | ||||
|         nums = Arrays.stream(command.substring(1).split(" ")) | ||||
|           .filter(Predicate.not(String::isBlank)) | ||||
|           .flatMap((numString) -> splitCommand(numString).stream()) | ||||
|           .collect(Collectors.toList()); | ||||
|       } | ||||
| 
 | ||||
|       // Use the nums to get a list of sh.ball.shapes, using the first character in the command to specify
 | ||||
|       // the function to use.
 | ||||
|       svgShapes.addAll(commandMap.get(commandChar).apply(state, nums)); | ||||
| 
 | ||||
|       if (!String.valueOf(commandChar).matches("[csCS]")) { | ||||
|         state.prevCubicControlPoint = null; | ||||
|       } | ||||
|       if (!String.valueOf(commandChar).matches("[qtQT]")) { | ||||
|         state.prevQuadraticControlPoint = null; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return svgShapes; | ||||
|   } | ||||
| 
 | ||||
|   public static boolean isSvgFile(String path) { | ||||
|     return path.matches(".*\\.svg"); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| /* Intentionally package-private class for carrying around the current state of SVG parsing. */ | ||||
| class SvgState { | ||||
| 
 | ||||
|   Vector2 currPoint; | ||||
|   Vector2 initialPoint; | ||||
|   Vector2 prevCubicControlPoint; | ||||
|   Vector2 prevQuadraticControlPoint; | ||||
| } | ||||
| package sh.ball.parser.svg; | ||||
| 
 | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| /* Intentionally package-private class for carrying around the current state of SVG parsing. */ | ||||
| class SvgState { | ||||
| 
 | ||||
|   Vector2 currPoint; | ||||
|   Vector2 initialPoint; | ||||
|   Vector2 prevCubicControlPoint; | ||||
|   Vector2 prevQuadraticControlPoint; | ||||
| } | ||||
|  |  | |||
|  | @ -1,85 +1,85 @@ | |||
| package sh.ball.parser.txt; | ||||
| 
 | ||||
| import java.io.*; | ||||
| import java.nio.charset.Charset; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.parser.FileParser; | ||||
| import sh.ball.shapes.ShapeFrameSet; | ||||
| import sh.ball.parser.svg.SvgParser; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class TextParser extends FileParser<FrameSet<List<Shape>>> { | ||||
| 
 | ||||
|   private static final char WIDE_CHAR = 'W'; | ||||
|   private static final double HEIGHT_SCALAR = 1.6; | ||||
|   private static final String DEFAULT_FONT = "/fonts/SourceCodePro-ExtraLight.svg"; | ||||
| 
 | ||||
|   private final Map<Character, List<Shape>> charToShape; | ||||
|   private final InputStream input; | ||||
|   private final InputStream font; | ||||
| 
 | ||||
|   public TextParser(InputStream input, InputStream font) { | ||||
|     this.input = input; | ||||
|     this.font = font; | ||||
|     this.charToShape = new HashMap<>(); | ||||
|   } | ||||
| 
 | ||||
|   public TextParser(String path) throws FileNotFoundException { | ||||
|     this(new FileInputStream(path), TextParser.class.getResourceAsStream(DEFAULT_FONT)); | ||||
|     checkFileExtension(path); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getFileExtension() { | ||||
|     return "txt"; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public FrameSet<List<Shape>> parse() throws IllegalArgumentException, IOException, ParserConfigurationException, SAXException { | ||||
|     List<String> text = new BufferedReader(new InputStreamReader(input, Charset.defaultCharset())).lines().collect(Collectors.toList()); | ||||
|     SvgParser fontParser = new SvgParser(font); | ||||
|     fontParser.parse(); | ||||
|     List<Shape> shapes = new ArrayList<>(); | ||||
| 
 | ||||
|     /* WIDE_CHAR used as an example character that will be wide in most languages. | ||||
|      * This helps determine the correct character width for the font chosen. */ | ||||
|     charToShape.put(WIDE_CHAR, fontParser.parseGlyphsWithUnicode(WIDE_CHAR)); | ||||
| 
 | ||||
|     for (String line : text) { | ||||
|       for (char c : line.toCharArray()) { | ||||
|         if (!charToShape.containsKey(c)) { | ||||
|           List<Shape> glyph = fontParser.parseGlyphsWithUnicode(c); | ||||
|           charToShape.put(c, glyph); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     double width = Shape.width(charToShape.get(WIDE_CHAR)); | ||||
|     double height = HEIGHT_SCALAR * Shape.height(charToShape.get(WIDE_CHAR)); | ||||
| 
 | ||||
|     for (int i = 0; i < text.size(); i++) { | ||||
|       char[] lineChars = text.get(i).toCharArray(); | ||||
|       for (int j = 0; j < lineChars.length; j++) { | ||||
|         shapes.addAll(Shape.translate( | ||||
|           charToShape.get(lineChars[j]), | ||||
|           new Vector2(j * width, -i * height) | ||||
|         )); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return new ShapeFrameSet(Shape.flip(Shape.normalize(shapes))); | ||||
|   } | ||||
| 
 | ||||
|   public static boolean isTxtFile(String path) { | ||||
|     return path.matches(".*\\.txt"); | ||||
|   } | ||||
| } | ||||
| package sh.ball.parser.txt; | ||||
| 
 | ||||
| import java.io.*; | ||||
| import java.nio.charset.Charset; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
| 
 | ||||
| import org.xml.sax.SAXException; | ||||
| import sh.ball.audio.FrameSet; | ||||
| import sh.ball.parser.FileParser; | ||||
| import sh.ball.shapes.ShapeFrameSet; | ||||
| import sh.ball.parser.svg.SvgParser; | ||||
| import sh.ball.shapes.Shape; | ||||
| import sh.ball.shapes.Vector2; | ||||
| 
 | ||||
| public class TextParser extends FileParser<FrameSet<List<Shape>>> { | ||||
| 
 | ||||
|   private static final char WIDE_CHAR = 'W'; | ||||
|   private static final double HEIGHT_SCALAR = 1.6; | ||||
|   private static final String DEFAULT_FONT = "/fonts/SourceCodePro-ExtraLight.svg"; | ||||
| 
 | ||||
|   private final Map<Character, List<Shape>> charToShape; | ||||
|   private final InputStream input; | ||||
|   private final InputStream font; | ||||
| 
 | ||||
|   public TextParser(InputStream input, InputStream font) { | ||||
|     this.input = input; | ||||
|     this.font = font; | ||||
|     this.charToShape = new HashMap<>(); | ||||
|   } | ||||
| 
 | ||||
|   public TextParser(String path) throws FileNotFoundException { | ||||
|     this(new FileInputStream(path), TextParser.class.getResourceAsStream(DEFAULT_FONT)); | ||||
|     checkFileExtension(path); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getFileExtension() { | ||||
|     return "txt"; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public FrameSet<List<Shape>> parse() throws IllegalArgumentException, IOException, ParserConfigurationException, SAXException { | ||||
|     List<String> text = new BufferedReader(new InputStreamReader(input, Charset.defaultCharset())).lines().collect(Collectors.toList()); | ||||
|     SvgParser fontParser = new SvgParser(font); | ||||
|     fontParser.parse(); | ||||
|     List<Shape> shapes = new ArrayList<>(); | ||||
| 
 | ||||
|     /* WIDE_CHAR used as an example character that will be wide in most languages. | ||||
|      * This helps determine the correct character width for the font chosen. */ | ||||
|     charToShape.put(WIDE_CHAR, fontParser.parseGlyphsWithUnicode(WIDE_CHAR)); | ||||
| 
 | ||||
|     for (String line : text) { | ||||
|       for (char c : line.toCharArray()) { | ||||
|         if (!charToShape.containsKey(c)) { | ||||
|           List<Shape> glyph = fontParser.parseGlyphsWithUnicode(c); | ||||
|           charToShape.put(c, glyph); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     double width = Shape.width(charToShape.get(WIDE_CHAR)); | ||||
|     double height = HEIGHT_SCALAR * Shape.height(charToShape.get(WIDE_CHAR)); | ||||
| 
 | ||||
|     for (int i = 0; i < text.size(); i++) { | ||||
|       char[] lineChars = text.get(i).toCharArray(); | ||||
|       for (int j = 0; j < lineChars.length; j++) { | ||||
|         shapes.addAll(Shape.translate( | ||||
|           charToShape.get(lineChars[j]), | ||||
|           new Vector2(j * width, -i * height) | ||||
|         )); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return new ShapeFrameSet(Shape.flip(Shape.normalize(shapes))); | ||||
|   } | ||||
| 
 | ||||
|   public static boolean isTxtFile(String path) { | ||||
|     return path.matches(".*\\.txt"); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,58 +1,58 @@ | |||
| package sh.ball.shapes; | ||||
| 
 | ||||
| public class CubicBezierCurve extends Shape { | ||||
| 
 | ||||
|   private final Vector2 p0; | ||||
|   private final Vector2 p1; | ||||
|   private final Vector2 p2; | ||||
|   private final Vector2 p3; | ||||
| 
 | ||||
|   public CubicBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, double weight) { | ||||
|     this.p0 = p0; | ||||
|     this.p1 = p1; | ||||
|     this.p2 = p2; | ||||
|     this.p3 = p3; | ||||
|     this.weight = weight; | ||||
|     this.length = new Line(p0, p3).length; | ||||
|   } | ||||
| 
 | ||||
|   public CubicBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) { | ||||
|     this(p0, p1, p2, p3, DEFAULT_WEIGHT); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 nextVector(double t) { | ||||
|     return p0.scale(Math.pow(1 - t, 3)) | ||||
|       .add(p1.scale(3 * Math.pow(1 - t, 2) * t)) | ||||
|       .add(p2.scale(3 * (1 - t) * Math.pow(t, 2))) | ||||
|       .add(p3.scale(Math.pow(t, 3))); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve rotate(double theta) { | ||||
|     return new CubicBezierCurve(p0.rotate(theta), p1.rotate(theta), p2.rotate(theta), | ||||
|       p3.rotate(theta), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve scale(double factor) { | ||||
|     return scale(new Vector2(factor)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve scale(Vector2 vector) { | ||||
|     return new CubicBezierCurve(p0.scale(vector), p1.scale(vector), p2.scale(vector), | ||||
|       p3.scale(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve translate(Vector2 vector) { | ||||
|     return new CubicBezierCurve(p0.translate(vector), p1.translate(vector), p2.translate(vector), | ||||
|       p3.translate(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve setWeight(double weight) { | ||||
|     return new CubicBezierCurve(p0, p1, p2, p3, weight); | ||||
|   } | ||||
| } | ||||
| package sh.ball.shapes; | ||||
| 
 | ||||
| public class CubicBezierCurve extends Shape { | ||||
| 
 | ||||
|   private final Vector2 p0; | ||||
|   private final Vector2 p1; | ||||
|   private final Vector2 p2; | ||||
|   private final Vector2 p3; | ||||
| 
 | ||||
|   public CubicBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, double weight) { | ||||
|     this.p0 = p0; | ||||
|     this.p1 = p1; | ||||
|     this.p2 = p2; | ||||
|     this.p3 = p3; | ||||
|     this.weight = weight; | ||||
|     this.length = new Line(p0, p3).length; | ||||
|   } | ||||
| 
 | ||||
|   public CubicBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) { | ||||
|     this(p0, p1, p2, p3, DEFAULT_WEIGHT); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 nextVector(double t) { | ||||
|     return p0.scale(Math.pow(1 - t, 3)) | ||||
|       .add(p1.scale(3 * Math.pow(1 - t, 2) * t)) | ||||
|       .add(p2.scale(3 * (1 - t) * Math.pow(t, 2))) | ||||
|       .add(p3.scale(Math.pow(t, 3))); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve rotate(double theta) { | ||||
|     return new CubicBezierCurve(p0.rotate(theta), p1.rotate(theta), p2.rotate(theta), | ||||
|       p3.rotate(theta), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve scale(double factor) { | ||||
|     return scale(new Vector2(factor)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve scale(Vector2 vector) { | ||||
|     return new CubicBezierCurve(p0.scale(vector), p1.scale(vector), p2.scale(vector), | ||||
|       p3.scale(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve translate(Vector2 vector) { | ||||
|     return new CubicBezierCurve(p0.translate(vector), p1.translate(vector), p2.translate(vector), | ||||
|       p3.translate(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public CubicBezierCurve setWeight(double weight) { | ||||
|     return new CubicBezierCurve(p0, p1, p2, p3, weight); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,52 +1,52 @@ | |||
| package sh.ball.shapes; | ||||
| 
 | ||||
| public class QuadraticBezierCurve extends Shape { | ||||
| 
 | ||||
|   private final Vector2 p0; | ||||
|   private final Vector2 p1; | ||||
|   private final Vector2 p2; | ||||
| 
 | ||||
|   public QuadraticBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, double weight) { | ||||
|     this.p0 = p0; | ||||
|     this.p1 = p1; | ||||
|     this.p2 = p2; | ||||
|     this.weight = weight; | ||||
|     this.length = new Line(p0, p2).length; | ||||
|   } | ||||
| 
 | ||||
|   public QuadraticBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2) { | ||||
|     this(p0, p1, p2, DEFAULT_WEIGHT); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 nextVector(double t) { | ||||
|     return p1.add(p0.sub(p1).scale(Math.pow(1 - t, 2))) | ||||
|       .add(p2.sub(p1).scale(Math.pow(t, 2))); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve rotate(double theta) { | ||||
|     return new QuadraticBezierCurve(p0.rotate(theta), p1.rotate(theta), p2.rotate(theta), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve scale(double factor) { | ||||
|     return new QuadraticBezierCurve(p0.scale(factor), p1.scale(factor), p2.scale(factor), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve scale(Vector2 vector) { | ||||
|     return new QuadraticBezierCurve(p0.scale(vector), p1.scale(vector), p2.scale(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve translate(Vector2 vector) { | ||||
|     return new QuadraticBezierCurve(p0.translate(vector), p1.translate(vector), | ||||
|       p2.translate(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve setWeight(double weight) { | ||||
|     return new QuadraticBezierCurve(p0, p1, p2, weight); | ||||
|   } | ||||
| } | ||||
| package sh.ball.shapes; | ||||
| 
 | ||||
| public class QuadraticBezierCurve extends Shape { | ||||
| 
 | ||||
|   private final Vector2 p0; | ||||
|   private final Vector2 p1; | ||||
|   private final Vector2 p2; | ||||
| 
 | ||||
|   public QuadraticBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, double weight) { | ||||
|     this.p0 = p0; | ||||
|     this.p1 = p1; | ||||
|     this.p2 = p2; | ||||
|     this.weight = weight; | ||||
|     this.length = new Line(p0, p2).length; | ||||
|   } | ||||
| 
 | ||||
|   public QuadraticBezierCurve(Vector2 p0, Vector2 p1, Vector2 p2) { | ||||
|     this(p0, p1, p2, DEFAULT_WEIGHT); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Vector2 nextVector(double t) { | ||||
|     return p1.add(p0.sub(p1).scale(Math.pow(1 - t, 2))) | ||||
|       .add(p2.sub(p1).scale(Math.pow(t, 2))); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve rotate(double theta) { | ||||
|     return new QuadraticBezierCurve(p0.rotate(theta), p1.rotate(theta), p2.rotate(theta), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve scale(double factor) { | ||||
|     return new QuadraticBezierCurve(p0.scale(factor), p1.scale(factor), p2.scale(factor), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve scale(Vector2 vector) { | ||||
|     return new QuadraticBezierCurve(p0.scale(vector), p1.scale(vector), p2.scale(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve translate(Vector2 vector) { | ||||
|     return new QuadraticBezierCurve(p0.translate(vector), p1.translate(vector), | ||||
|       p2.translate(vector), weight); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public QuadraticBezierCurve setWeight(double weight) { | ||||
|     return new QuadraticBezierCurve(p0, p1, p2, weight); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,84 +1,87 @@ | |||
| .root { | ||||
|     dark_color: #282828; | ||||
|     darker_color: #1d1d1d; | ||||
|     very_dark: #111111; | ||||
|     grey_color: #555555; | ||||
|     accent_color: #00CC00; | ||||
|     -fx-background-color: dark_color; | ||||
|     -fx-text-background-color: white; | ||||
|     -fx-text-inner-color: white; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| .titled-pane, .text { | ||||
|     -fx-font-size: 13; | ||||
|     -fx-font-smoothing-type: gray; | ||||
|     -fx-text-fill: white; | ||||
| } | ||||
| 
 | ||||
| .titled-pane > .title | ||||
| { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-background-radius: 0; | ||||
| } | ||||
| 
 | ||||
| .titled-pane > *.content | ||||
| { | ||||
|     -fx-background-color: darker_color; | ||||
|     -fx-border-width: 0; | ||||
| } | ||||
| 
 | ||||
| .titled-pane:focused > .title > .arrow-button .arrow | ||||
| { | ||||
|     -fx-background-color: darker_color; | ||||
| } | ||||
| 
 | ||||
| .button { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-width: 1; | ||||
|     -fx-text-fill: white; | ||||
| } | ||||
| 
 | ||||
| .slider .thumb { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-radius: 1.0em; /* makes sure this remains circular */ | ||||
|     -fx-effect: inherit; | ||||
| } | ||||
| .slider .track { | ||||
|     -fx-background-color: grey_color; | ||||
|     -fx-padding: 0.2em; | ||||
| } | ||||
| 
 | ||||
| .check-box > .box { | ||||
|     -fx-background-radius: 0; | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-width: 1; | ||||
| } | ||||
| .check-box > .box > .mark { | ||||
|     -fx-shape: "M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"; | ||||
| } | ||||
| 
 | ||||
| .check-box:selected > .box > .mark, | ||||
| .check-box:indeterminate  > .box > .mark { | ||||
|     -fx-background-color: white; | ||||
| } | ||||
| 
 | ||||
| .text-field { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-width: 1; | ||||
|     -fx-border-radius: 0; | ||||
|     -fx-padding: 0.2em; | ||||
| } | ||||
| 
 | ||||
| .text-field .text { | ||||
|     -fx-text-fill: white; | ||||
| } | ||||
| 
 | ||||
| #frequency .text { | ||||
|     -fx-font-size: 20; | ||||
| .root { | ||||
|     dark_color: #424242; | ||||
|     darker_color: #212121; | ||||
|     very_dark: #111111; | ||||
|     grey_color: #555555; | ||||
|     accent_color: #00CC00; | ||||
|     -fx-background-color: dark_color; | ||||
|     -fx-text-background-color: white; | ||||
|     -fx-text-inner-color: white; | ||||
| } | ||||
| 
 | ||||
| .titled-pane, .text { | ||||
|     -fx-font-size: 13; | ||||
|     -fx-font-smoothing-type: gray; | ||||
|     -fx-text-fill: white; | ||||
| } | ||||
| 
 | ||||
| .titled-pane > .title | ||||
| { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-background-radius: 0; | ||||
| } | ||||
| 
 | ||||
| .titled-pane > *.content | ||||
| { | ||||
|     -fx-background-color: darker_color; | ||||
|     -fx-border-width: 0; | ||||
| } | ||||
| 
 | ||||
| .titled-pane:focused > .title > .arrow-button .arrow | ||||
| { | ||||
|     -fx-background-color: darker_color; | ||||
| } | ||||
| 
 | ||||
| .button { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-width: 1; | ||||
|     -fx-text-fill: white; | ||||
| } | ||||
| 
 | ||||
| .slider .thumb { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-radius: 1.0em; /* makes sure this remains circular */ | ||||
|     -fx-effect: inherit; | ||||
| } | ||||
| .slider .track { | ||||
|     -fx-background-color: grey_color; | ||||
|     -fx-padding: 0.2em; | ||||
| } | ||||
| 
 | ||||
| .check-box > .box { | ||||
|     -fx-background-radius: 0; | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-width: 1; | ||||
| } | ||||
| .check-box > .box > .mark { | ||||
|     -fx-shape: "M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"; | ||||
| } | ||||
| 
 | ||||
| .check-box:selected > .box > .mark, | ||||
| .check-box:indeterminate  > .box > .mark { | ||||
|     -fx-background-color: white; | ||||
| } | ||||
| 
 | ||||
| .text-field { | ||||
|     -fx-background-color: very_dark; | ||||
|     -fx-border-color: white; | ||||
|     -fx-border-width: 1; | ||||
|     -fx-border-radius: 0; | ||||
|     -fx-padding: 0.2em; | ||||
| } | ||||
| 
 | ||||
| .text-field .text { | ||||
|     -fx-text-fill: white; | ||||
| } | ||||
| 
 | ||||
| #frequency .text { | ||||
|     -fx-font-size: 20; | ||||
| } | ||||
| 
 | ||||
| #control-pane, .titled-pane { | ||||
|     -fx-background-color: darker_color; | ||||
|     -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0); | ||||
| } | ||||
|  | @ -7,88 +7,86 @@ | |||
| <?import javafx.scene.control.TextField?> | ||||
| <?import javafx.scene.control.TitledPane?> | ||||
| <?import javafx.scene.layout.AnchorPane?> | ||||
| <?import javafx.scene.layout.ColumnConstraints?> | ||||
| <?import javafx.scene.layout.GridPane?> | ||||
| <?import javafx.scene.layout.RowConstraints?> | ||||
| <?import javafx.scene.text.Font?> | ||||
| 
 | ||||
| <GridPane alignment="center" hgap="10" prefHeight="458.0" prefWidth="836.0" vgap="10" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1"> | ||||
|   <columnConstraints> | ||||
|     <ColumnConstraints /> | ||||
|   </columnConstraints> | ||||
|   <rowConstraints> | ||||
|     <RowConstraints /> | ||||
|   </rowConstraints> | ||||
|   <AnchorPane prefHeight="458.0" prefWidth="885.0"> | ||||
|       <AnchorPane layoutX="422.0" layoutY="26.0"> | ||||
|          <children> | ||||
|           <Button fx:id="chooseFileButton" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Choose File" /> | ||||
|           <Label fx:id="fileLabel" layoutX="132.0" layoutY="5.0" maxWidth="270.0" /> | ||||
|             <Button fx:id="recordButton" layoutY="40.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Record" /> | ||||
|             <Label fx:id="recordLabel" layoutX="132.0" layoutY="45.0" maxWidth="270.0" /> | ||||
|             <Label id="frequency" fx:id="frequencyLabel" layoutY="80.0" /> | ||||
|          </children> | ||||
|       </AnchorPane> | ||||
|       <TitledPane animated="false" collapsible="false" layoutX="422.0" layoutY="174.0" prefHeight="272.0" prefWidth="402.0" text="Effects"> | ||||
|         <content> | ||||
|           <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="246.0" prefWidth="442.0"> | ||||
|                <children> | ||||
|                   <CheckBox fx:id="vectorCancellingCheckBox" layoutX="14.0" layoutY="22.0" mnemonicParsing="false" text="Vector cancelling" /> | ||||
|                   <Slider fx:id="vectorCancellingSlider" blockIncrement="0.05" disable="true" layoutX="167.0" layoutY="16.0" majorTickUnit="1.0" max="10.0" min="2.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="2.0" /> | ||||
|                   <CheckBox fx:id="bitCrushCheckBox" layoutX="14.0" layoutY="60.0" mnemonicParsing="false" text="Bit crush" /> | ||||
|                   <Slider fx:id="bitCrushSlider" blockIncrement="0.01" disable="true" layoutX="167.0" layoutY="55.0" majorTickUnit="0.5" max="3.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="2.0" /> | ||||
|                   <CheckBox fx:id="verticalDistortCheckBox" layoutX="14.0" layoutY="101.0" mnemonicParsing="false" text="Vertical Distort" /> | ||||
|                   <Slider fx:id="verticalDistortSlider" blockIncrement="0.005" disable="true" layoutX="167.0" layoutY="96.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="0.2" /> | ||||
|                   <CheckBox fx:id="horizontalDistortCheckBox" layoutX="14.0" layoutY="144.0" mnemonicParsing="false" text="Horizontal Distort" /> | ||||
|                   <Slider fx:id="horizontalDistortSlider" blockIncrement="0.005" disable="true" layoutX="167.0" layoutY="139.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="0.2" /> | ||||
|                   <CheckBox fx:id="wobbleCheckBox" layoutX="14.0" layoutY="186.0" mnemonicParsing="false" text="Wobble" /> | ||||
|                   <Slider fx:id="wobbleSlider" blockIncrement="0.005" disable="true" layoutX="167.0" layoutY="181.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="0.2" /> | ||||
|                </children> | ||||
|             </AnchorPane> | ||||
|         </content> | ||||
|       </TitledPane> | ||||
|    <TitledPane fx:id="objTitledPane" animated="false" collapsible="false" layoutX="11.0" layoutY="240.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="206.0" prefWidth="402.0" text="3D .obj file settings"> | ||||
|      <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="359.0"> | ||||
|        <Slider fx:id="focalLengthSlider" blockIncrement="0.01" layoutX="116.0" layoutY="15.0" majorTickUnit="0.2" max="2.0" min="1.0E-5" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" value="1.0" /> | ||||
|        <Label layoutX="30.0" layoutY="14.0" text="Focal length" /> | ||||
|             <TextField fx:id="cameraXTextField" layoutX="134.0" layoutY="57.0" prefHeight="26.0" prefWidth="65.0" text="0" /> | ||||
|             <Label layoutX="31.0" layoutY="61.0" text="Camera pos" /> | ||||
|             <Label layoutX="118.0" layoutY="61.0" text="x :" /> | ||||
|             <Label layoutX="207.0" layoutY="60.0" text="y :" /> | ||||
|             <TextField fx:id="cameraYTextField" layoutX="224.0" layoutY="57.0" prefHeight="26.0" prefWidth="65.0" text="0" /> | ||||
|             <Label layoutX="299.0" layoutY="60.0" text="z :" /> | ||||
|             <TextField fx:id="cameraZTextField" layoutX="315.0" layoutY="57.0" prefHeight="26.0" prefWidth="65.0" text="-2.5" /> | ||||
|             <Slider fx:id="objectRotateSpeedSlider" blockIncrement="0.005" layoutX="116.0" layoutY="97.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" /> | ||||
|             <Label layoutX="24.0" layoutY="96.0" text="Rotate speed" /> | ||||
|             <CheckBox fx:id="rotateCheckBox" layoutX="90.0" layoutY="147.0" mnemonicParsing="false" text="Rotate with Mouse (Esc to disable)" /> | ||||
|      </AnchorPane> | ||||
|    </TitledPane> | ||||
|    <AnchorPane layoutX="11.0" layoutY="14.0" minHeight="0.0" minWidth="0.0"> | ||||
|      <Slider fx:id="rotateSpeedSlider" blockIncrement="0.05" layoutX="116.0" layoutY="90.0" majorTickUnit="1.0" max="10.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" /> | ||||
|      <Label layoutX="33.0" layoutY="88.0" text="Rotate speed"> | ||||
|             <font> | ||||
|                <Font size="13.0" /> | ||||
|             </font></Label> | ||||
|      <Slider fx:id="translationSpeedSlider" blockIncrement="0.05" layoutX="116.0" layoutY="128.0" majorTickUnit="1.0" max="10.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" /> | ||||
|      <Label layoutX="7.0" layoutY="126.0" text="Translation speed"> | ||||
|             <font> | ||||
|                <Font size="13.0" /> | ||||
|             </font></Label> | ||||
|      <Slider fx:id="scaleSlider" blockIncrement="0.05" layoutX="116.0" layoutY="167.0" majorTickUnit="1.0" max="10.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" value="1.0" /> | ||||
|      <Label layoutX="79.0" layoutY="164.0" text="Scale"> | ||||
|             <font> | ||||
|                <Font size="13.0" /> | ||||
|             </font></Label> | ||||
|      <Label layoutX="43.0" layoutY="49.0" text="Line weight"> | ||||
|             <font> | ||||
|                <Font size="13.0" /> | ||||
|             </font></Label> | ||||
|      <TextField fx:id="translationXTextField" layoutX="138.0" layoutY="15.0" prefHeight="26.0" prefWidth="70.0" text="0" /> | ||||
|      <Label layoutX="47.0" layoutY="18.0" text="Translation" /> | ||||
|      <Label layoutX="120.0" layoutY="18.0" text="x :" /> | ||||
|      <Label layoutX="219.0" layoutY="18.0" text="y :" /> | ||||
|      <TextField fx:id="translationYTextField" layoutX="238.0" layoutY="15.0" prefHeight="26.0" prefWidth="70.0" text="0" /> | ||||
|          <Slider fx:id="weightSlider" blockIncrement="1.0" layoutX="116.0" layoutY="52.0" majorTickUnit="100.0" max="1000.0" prefHeight="38.0" prefWidth="269.0" showTickLabels="true" showTickMarks="true" value="100.0" /> | ||||
| 
 | ||||
| <AnchorPane prefHeight="498.0" prefWidth="837.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1"> | ||||
|    <AnchorPane id="control-pane" layoutX="422.0" layoutY="19.0" prefHeight="185.0" prefWidth="402.0"> | ||||
|       <children> | ||||
|        <Button fx:id="chooseFileButton" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Choose File" /> | ||||
|        <Label fx:id="fileLabel" layoutX="146.0" layoutY="19.0" maxWidth="270.0" prefHeight="18.0" prefWidth="246.0" text="cube.obj" /> | ||||
|          <Button fx:id="recordButton" layoutX="14.0" layoutY="54.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="114.0" text="Record" /> | ||||
|          <Label fx:id="recordLabel" layoutX="146.0" layoutY="59.0" maxWidth="270.0" prefHeight="18.0" prefWidth="245.0" /> | ||||
|          <Label id="frequency" fx:id="frequencyLabel" layoutX="14.0" layoutY="113.0" prefHeight="58.0" prefWidth="376.0" text="L/R Frequency:
 
" /> | ||||
|       </children> | ||||
|    </AnchorPane> | ||||
|    <TitledPane animated="false" collapsible="false" layoutX="422.0" layoutY="213.0" prefHeight="272.0" prefWidth="402.0" text="Effects"> | ||||
|      <content> | ||||
|        <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="246.0" prefWidth="442.0"> | ||||
|             <children> | ||||
|                <CheckBox fx:id="vectorCancellingCheckBox" layoutX="14.0" layoutY="15.0" mnemonicParsing="false" text="Vector cancelling" /> | ||||
|                <Slider fx:id="vectorCancellingSlider" blockIncrement="0.05" disable="true" layoutX="167.0" layoutY="16.0" majorTickUnit="1.0" max="10.0" min="2.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="2.0" /> | ||||
|                <CheckBox fx:id="bitCrushCheckBox" layoutX="14.0" layoutY="53.0" mnemonicParsing="false" text="Bit crush" /> | ||||
|                <Slider fx:id="bitCrushSlider" blockIncrement="0.01" disable="true" layoutX="167.0" layoutY="55.0" majorTickUnit="0.5" max="3.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="2.0" /> | ||||
|                <CheckBox fx:id="verticalDistortCheckBox" layoutX="14.0" layoutY="94.0" mnemonicParsing="false" text="Vertical Distort" /> | ||||
|                <Slider fx:id="verticalDistortSlider" blockIncrement="0.005" disable="true" layoutX="167.0" layoutY="96.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="0.2" /> | ||||
|                <CheckBox fx:id="horizontalDistortCheckBox" layoutX="14.0" layoutY="137.0" mnemonicParsing="false" text="Horizontal Distort" /> | ||||
|                <Slider fx:id="horizontalDistortSlider" blockIncrement="0.005" disable="true" layoutX="167.0" layoutY="139.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="0.2" /> | ||||
|                <CheckBox fx:id="wobbleCheckBox" layoutX="14.0" layoutY="179.0" mnemonicParsing="false" text="Wobble" /> | ||||
|                <Slider fx:id="wobbleSlider" blockIncrement="0.005" disable="true" layoutX="167.0" layoutY="181.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="222.0" showTickLabels="true" showTickMarks="true" value="0.2" /> | ||||
|             </children> | ||||
|          </AnchorPane> | ||||
|      </content> | ||||
|    </TitledPane> | ||||
| <TitledPane fx:id="objTitledPane" animated="false" collapsible="false" layoutX="11.0" layoutY="275.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="210.0" prefWidth="402.0" text="3D .obj file settings"> | ||||
|   <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="359.0"> | ||||
|     <Slider fx:id="focalLengthSlider" blockIncrement="0.01" layoutX="116.0" layoutY="15.0" majorTickUnit="0.2" max="2.0" min="1.0E-5" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" value="1.0" /> | ||||
|     <Label layoutX="30.0" layoutY="14.0" text="Focal length" /> | ||||
|          <TextField fx:id="cameraXTextField" layoutX="134.0" layoutY="57.0" prefHeight="26.0" prefWidth="65.0" text="0" /> | ||||
|          <Label layoutX="31.0" layoutY="61.0" text="Camera pos" /> | ||||
|          <Label layoutX="118.0" layoutY="61.0" text="x :" /> | ||||
|          <Label layoutX="207.0" layoutY="60.0" text="y :" /> | ||||
|          <TextField fx:id="cameraYTextField" layoutX="224.0" layoutY="57.0" prefHeight="26.0" prefWidth="65.0" text="0" /> | ||||
|          <Label layoutX="299.0" layoutY="60.0" text="z :" /> | ||||
|          <TextField fx:id="cameraZTextField" layoutX="315.0" layoutY="57.0" prefHeight="26.0" prefWidth="65.0" text="-2.5" /> | ||||
|          <Slider fx:id="objectRotateSpeedSlider" blockIncrement="0.005" layoutX="116.0" layoutY="97.0" majorTickUnit="0.1" max="1.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" /> | ||||
|          <Label layoutX="24.0" layoutY="96.0" text="Rotate speed" /> | ||||
|          <CheckBox fx:id="rotateCheckBox" layoutX="90.0" layoutY="147.0" mnemonicParsing="false" text="Rotate with Mouse (Esc to disable)" /> | ||||
|   </AnchorPane> | ||||
| </GridPane> | ||||
| </TitledPane> | ||||
|    <TitledPane collapsible="false" layoutX="11.0" layoutY="18.0" prefHeight="248.0" prefWidth="402.0" text="Image settings"> | ||||
|       <content> | ||||
|       <AnchorPane minHeight="0.0" minWidth="0.0"> | ||||
|         <Slider fx:id="rotateSpeedSlider" blockIncrement="0.05" layoutX="116.0" layoutY="90.0" majorTickUnit="1.0" max="10.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" /> | ||||
|         <Label layoutX="33.0" layoutY="88.0" text="Rotate speed"> | ||||
|                <font> | ||||
|                   <Font size="13.0" /> | ||||
|                </font> | ||||
|             </Label> | ||||
|         <Slider fx:id="translationSpeedSlider" blockIncrement="0.05" layoutX="116.0" layoutY="128.0" majorTickUnit="1.0" max="10.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" /> | ||||
|         <Label layoutX="7.0" layoutY="126.0" text="Translation speed"> | ||||
|                <font> | ||||
|                   <Font size="13.0" /> | ||||
|                </font> | ||||
|             </Label> | ||||
|         <Slider fx:id="scaleSlider" blockIncrement="0.05" layoutX="116.0" layoutY="167.0" majorTickUnit="1.0" max="10.0" prefHeight="38.0" prefWidth="270.0" showTickLabels="true" showTickMarks="true" value="1.0" /> | ||||
|         <Label layoutX="79.0" layoutY="164.0" text="Scale"> | ||||
|                <font> | ||||
|                   <Font size="13.0" /> | ||||
|                </font> | ||||
|             </Label> | ||||
|         <Label layoutX="43.0" layoutY="49.0" text="Line weight"> | ||||
|                <font> | ||||
|                   <Font size="13.0" /> | ||||
|                </font> | ||||
|             </Label> | ||||
|         <TextField fx:id="translationXTextField" layoutX="138.0" layoutY="15.0" prefHeight="26.0" prefWidth="70.0" text="0" /> | ||||
|         <Label layoutX="47.0" layoutY="18.0" text="Translation" /> | ||||
|         <Label layoutX="120.0" layoutY="18.0" text="x :" /> | ||||
|         <Label layoutX="219.0" layoutY="18.0" text="y :" /> | ||||
|         <TextField fx:id="translationYTextField" layoutX="238.0" layoutY="15.0" prefHeight="26.0" prefWidth="70.0" text="0" /> | ||||
|             <Slider fx:id="weightSlider" blockIncrement="1.0" layoutX="116.0" layoutY="52.0" majorTickUnit="100.0" max="1000.0" prefHeight="38.0" prefWidth="269.0" showTickLabels="true" showTickMarks="true" value="100.0" /> | ||||
|       </AnchorPane> | ||||
|       </content> | ||||
|    </TitledPane> | ||||
| </AnchorPane> | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| αβγΓηθ | ||||
| ικλΛμν | ||||
| πΠρσΣτ | ||||
| αβγΓηθ | ||||
| ικλΛμν | ||||
| πΠρσΣτ | ||||
| υϕχψΨω | ||||
|  | @ -1,2 +1,2 @@ | |||
| hello | ||||
| hello | ||||
| world | ||||
		Ładowanie…
	
		Reference in New Issue
	
	 James Ball
						James Ball