diff --git a/.github/workflows/ci-actions.yml b/.github/workflows/ci-actions.yml new file mode 100644 index 0000000..dbdc176 --- /dev/null +++ b/.github/workflows/ci-actions.yml @@ -0,0 +1,85 @@ +name: Java CI + +on: [push] + +jobs: + create_release: + name: Create release + runs-on: ubuntu-latest + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + build: + needs: create_release + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 14 + uses: actions/setup-java@v1 + with: + java-version: '14' + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + + - run: ./gradlew runtimeZip + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: build/image.zip + asset_name: my-artifact-with-jre ${{ matrix.os }}.zip + asset_content_type: application/zip + + build-jar: + needs: create_release + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + + - run: ./gradlew distZip + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: build/distributions/shayan-circles.zip + asset_name: my-artifact ${{ matrix.os }}.zip + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore index a1c2a23..b66e0e5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,8 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +/.gradle/ +/.classpath +/.project +/build/ +/.settings/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1ec1933 --- /dev/null +++ b/build.gradle @@ -0,0 +1,78 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java Library project to get you started. + * For more details take a look at the Java Libraries chapter in the Gradle + * User Manual available at https://docs.gradle.org/6.0/userguide/java_library_plugin.html + */ + +plugins { + // Apply the java-library plugin to add support for Java Library +// id 'com.gluonhq.client-gradle-plugin' version '0.0.11' + id 'java-library' + id 'eclipse' + id 'application' + id 'java' + id 'net.nemerosa.versioning' version '2.6.1' + + id 'org.openjfx.javafxplugin' version '0.0.8' + id 'org.beryx.runtime' version '1.11.4' + id 'project-report' + id "io.spring.dependency-management" version "1.0.6.RELEASE" +} + +def springVersion = '5.2.3.RELEASE' + +javafx { + version = "14" + modules = [ + 'javafx.controls', + 'javafx.fxml', + 'javafx.swing' + ] +} + +repositories { + // Use jcenter for resolving dependencies. + // You can declare any Maven/Ivy/file repository here. + jcenter() + google() + mavenCentral() +// maven { +// url 'https://nexus.gluonhq.com/nexus/content/repositories/releases/' +//} +} + +dependencies { + + + // This dependency is exported to consumers, that is to say found on their implementation classpath. + api 'org.apache.commons:commons-math3:3.6.1' + +compileOnly 'org.projectlombok:lombok:1.18.12' + annotationProcessor 'org.projectlombok:lombok:1.18.12' +} + +mainClassName = "ir.hosseinmp76.shayancircles.Main" + +run { + + standardInput = System.in + +} + +jar { + manifest { + attributes( + 'Main-Class': 'ir.hosseinmp76.shayancircles.ui.fx.FXApp2', + 'Built-By' : System.properties['user.name'], + 'Build-Timestamp': new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()), + 'Build-Revision' : versioning.info.commit, +// "Implementation-Version": archiveVersion, + 'Created-By' : "Gradle ${gradle.gradleVersion}", + 'Build-Jdk' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})", + 'Build-OS' : "${System.properties['os.name']} ${System.properties['os.arch']} ${System.properties['os.version']}" + + ) + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..186b715 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..62bd9b9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..d6f0123 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/6.3-rc-3/userguide/multi_project_builds.html + */ + +rootProject.name = 'shayan-circles' diff --git a/src/main/java/ir/hosseinmp76/shayancircles/Main.java b/src/main/java/ir/hosseinmp76/shayancircles/Main.java new file mode 100644 index 0000000..34e4599 --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/Main.java @@ -0,0 +1,12 @@ +package ir.hosseinmp76.shayancircles; +import ir.hosseinmp76.shayancircles.ui.fx.FXApplication; + +public class Main { + + public static void main(String[] args) { + // TODO Auto-generated method stub + FXApplication.startApp(); +// FxCanvasExample6.main(args); + } + +} diff --git a/src/main/java/ir/hosseinmp76/shayancircles/tools/AbstractTool.java b/src/main/java/ir/hosseinmp76/shayancircles/tools/AbstractTool.java new file mode 100644 index 0000000..58e60aa --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/tools/AbstractTool.java @@ -0,0 +1,43 @@ +package ir.hosseinmp76.shayancircles.tools; + +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; + +import javafx.scene.paint.Color; +import lombok.Data; +import lombok.Getter; + +@Getter +public abstract class AbstractTool { + protected ToolState toolState; + + private Color color; + + public AbstractTool(Color color2) { + this.color = color2; + } + + public AbstractTool(double traveledDistance, Color color2) { + this.toolState = new ToolState(); + this.toolState.setTraveledRadius(traveledDistance); + this.color = color2; + } + + public abstract Vector2D getPoint(); + + public void nextPoint(final double travleRadius) { + this.toolState.setTraveledRadius( + this.toolState.getTraveledRadius() + travleRadius); + } + + public abstract boolean isFinished(); + + public void changeColor(Color value) { + this.color = value; + } +} + +@Data +class ToolState { + private double traveledRadius = 0; + +} \ No newline at end of file diff --git a/src/main/java/ir/hosseinmp76/shayancircles/tools/ClasicCircleOutOfRingTool.java b/src/main/java/ir/hosseinmp76/shayancircles/tools/ClasicCircleOutOfRingTool.java new file mode 100644 index 0000000..f395741 --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/tools/ClasicCircleOutOfRingTool.java @@ -0,0 +1,53 @@ +package ir.hosseinmp76.shayancircles.tools; + +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import org.apache.commons.math3.util.ArithmeticUtils; + +import javafx.scene.paint.Color; + +public class ClasicCircleOutOfRingTool extends ClasicRingCircleTool { + + public static Vector2D staticGetPoint( + final ClasicCircleOutOfRingTool tool) { + final var r = tool.getInnerCirleRadios(); + final var R = tool.getOuterCirleBigRadios(); + final double outerAngle = tool.toolState.getTraveledRadius() + % (2 * Math.PI * tool.getOuterCirleSmallRadios()) + / tool.getOuterCirleSmallRadios(); + final double innerAngle = tool.toolState.getTraveledRadius() + % (2 * Math.PI * r) / r; + + final var innerCirleCenter = new Vector2D( + -1.0 * Math.sin(outerAngle) * (R + r), + -1.0 * Math.cos(outerAngle) * (R + r)); + final var relativePenLocation = new Vector2D( + -1 * Math.sin(innerAngle) + * tool.getPenHoleDistanceToInnerCirleCenter(), + -1.0 * Math.cos(innerAngle) + * tool.getPenHoleDistanceToInnerCirleCenter()); + return relativePenLocation.add(innerCirleCenter); + } + + public ClasicCircleOutOfRingTool(final double outerCirleRadios, + final double outerCirleBigRadios, final double innerCirleRadios, + final double penHoleDistanceToInnerCirleCenter, final Color color) { + super(outerCirleRadios, outerCirleBigRadios, innerCirleRadios, + penHoleDistanceToInnerCirleCenter, color); + // TODO Auto-generated constructor stub + } + + @Override + public boolean isFinished() { + final var R = this.getOuterCirleBigRadios(); + double lcm = this.getInnerCirleRadios() * R; + if (this.getInnerCirleRadios() % 1 == 0 && R % 1 == 0) { + lcm = ArithmeticUtils.lcm((int) this.getInnerCirleRadios(), + (int) R); + } + if (2 * Math.PI * lcm + - this.toolState.getTraveledRadius() < this.epsilon) { + return true; + } + return false; + } +} diff --git a/src/main/java/ir/hosseinmp76/shayancircles/tools/ClasicRingCircleTool.java b/src/main/java/ir/hosseinmp76/shayancircles/tools/ClasicRingCircleTool.java new file mode 100644 index 0000000..d0a6e02 --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/tools/ClasicRingCircleTool.java @@ -0,0 +1,73 @@ +package ir.hosseinmp76.shayancircles.tools; + +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import org.apache.commons.math3.util.ArithmeticUtils; + +import javafx.scene.paint.Color; +import lombok.Getter; + +@Getter +public class ClasicRingCircleTool extends AbstractTool { + + public static Vector2D staticGetPoint(final ClasicRingCircleTool tool) { + final double outerAngle = tool.getToolState().getTraveledRadius() + % (2 * Math.PI * tool.getOuterCirleSmallRadios()) + / tool.getOuterCirleSmallRadios(); + final double innerAngle = tool.getToolState().getTraveledRadius() + % (2 * Math.PI * tool.getInnerCirleRadios()) + / tool.getInnerCirleRadios(); + + final var innerCirleCenter = new Vector2D( + -1.0 * Math.sin(outerAngle) * tool.getInnerCirleRadios(), + -1.0 * Math.cos(outerAngle) * tool.getInnerCirleRadios()); + final var relativePenLocation = new Vector2D( + Math.sin(innerAngle) + * tool.getPenHoleDistanceToInnerCirleCenter(), + -1.0 * Math.cos(innerAngle) + * tool.getPenHoleDistanceToInnerCirleCenter()); + return relativePenLocation.add(innerCirleCenter); + } + + private final double outerCirleSmallRadios; + private final double outerCirleBigRadios; + private final double innerCirleRadios; + private final double penHoleDistanceToInnerCirleCenter; + + protected final double epsilon = 0.00001; + + public ClasicRingCircleTool(final double outerCirleRadios, + final double outerCirleBigRadios, final double innerCirleRadios, + final double penHoleDistanceToInnerCirleCenter, final Color color) { + super(color); + if (penHoleDistanceToInnerCirleCenter > innerCirleRadios) + throw new RuntimeException( + "penHoleDistanceToInnerCirleCenter > innerCirleRadios"); + this.outerCirleBigRadios = outerCirleBigRadios; + this.outerCirleSmallRadios = outerCirleRadios; + this.innerCirleRadios = innerCirleRadios; + this.penHoleDistanceToInnerCirleCenter = penHoleDistanceToInnerCirleCenter; + this.toolState = new ToolState(); + } + + @Override + public Vector2D getPoint() { + return ClasicRingCircleTool.staticGetPoint(this); + } + + @Override + public boolean isFinished() { + double lcm = this.getInnerCirleRadios() + * this.getOuterCirleSmallRadios(); + if (this.getInnerCirleRadios() % 1 == 0 + && this.getOuterCirleSmallRadios() % 1 == 0) { + lcm = ArithmeticUtils.lcm((int) this.getInnerCirleRadios(), + (int) this.getOuterCirleSmallRadios()); + } + if (2 * Math.PI * lcm + - this.getToolState().getTraveledRadius() < this.epsilon) { + return true; + } + return false; + } + +} diff --git a/src/main/java/ir/hosseinmp76/shayancircles/tools/LongOvalAndCircleTool.java b/src/main/java/ir/hosseinmp76/shayancircles/tools/LongOvalAndCircleTool.java new file mode 100644 index 0000000..582a9a6 --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/tools/LongOvalAndCircleTool.java @@ -0,0 +1,96 @@ +package ir.hosseinmp76.shayancircles.tools; + +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import org.apache.commons.math3.util.ArithmeticUtils; + +import javafx.scene.paint.Color; +import lombok.Getter; + +@Getter +public class LongOvalAndCircleTool extends AbstractTool { + + public static Vector2D staticGetPoint(final LongOvalAndCircleTool tool) { + var ovarCircumference = 2 + * (Math.PI * tool.ovalHeadCircleRadis + tool.ovalWidth); + double locationOnOval = tool.toolState.getTraveledRadius() + % (ovarCircumference); + Vector2D innerCirleCenter; + if (locationOnOval < tool.ovalWidth) { + innerCirleCenter = new Vector2D(locationOnOval - tool.ovalWidth / 2, + -1.0 * (tool.ovalHeadCircleRadis + tool.circleRadios)); + } else if (locationOnOval < tool.ovalWidth + + Math.PI * tool.ovalHeadCircleRadis) { + locationOnOval -= tool.ovalWidth; + var alpha = locationOnOval / tool.ovalHeadCircleRadis; + var x = Math.sin(alpha) + * (tool.circleRadios + tool.ovalHeadCircleRadis); + var y = -1 * Math.cos(alpha) + * (tool.circleRadios + tool.ovalHeadCircleRadis); + innerCirleCenter = new Vector2D(x + tool.ovalWidth / 2, y); + } else if (locationOnOval < 2 * tool.ovalWidth + + Math.PI * tool.ovalHeadCircleRadis) { + locationOnOval -= (tool.ovalHeadCircleRadis * Math.PI + + tool.ovalWidth); + innerCirleCenter = new Vector2D(tool.ovalWidth / 2 - locationOnOval, + tool.ovalHeadCircleRadis + tool.circleRadios); + } else { + locationOnOval -= (2 * tool.ovalWidth + + Math.PI * tool.ovalHeadCircleRadis); + var alpha = locationOnOval / tool.ovalHeadCircleRadis; + var x = -1 * Math.sin(alpha) + * (tool.circleRadios + tool.ovalHeadCircleRadis); + var y = Math.cos(alpha) + * (tool.circleRadios + tool.ovalHeadCircleRadis); + innerCirleCenter = new Vector2D(x - tool.ovalWidth / 2, y); + } + + var innerAngle = (tool.toolState.getTraveledRadius() + % (2 * Math.PI * tool.circleRadios)) / tool.circleRadios; + + final var relativePenLocation = new Vector2D( + Math.sin(innerAngle) + * tool.getPenHoleDistanceToInnerCirleCenter(), + -1.0 * Math.cos(innerAngle) + * tool.getPenHoleDistanceToInnerCirleCenter()); + var res = relativePenLocation.add(innerCirleCenter); + return res; + } + + private final double ovalHeadCircleRadis; + private final double ovalWidth; + private final double circleRadios; + private final double penHoleDistanceToInnerCirleCenter; + + protected final double epsilon = 0.00001; + + public LongOvalAndCircleTool(double ovalHeadCircleRadis, double ovalWidth, + double circleRadios, double penHoleDistanceToInnerCirleCenter, + Color color2) { + super(0, color2); + this.ovalHeadCircleRadis = ovalHeadCircleRadis; + this.ovalWidth = ovalWidth; + this.circleRadios = circleRadios; + this.penHoleDistanceToInnerCirleCenter = penHoleDistanceToInnerCirleCenter; + } + + @Override + public Vector2D getPoint() { + return LongOvalAndCircleTool.staticGetPoint(this); + } + + @Override + public boolean isFinished() { + var a = circleRadios; + var b = ovalHeadCircleRadis + ovalWidth; + double lcm = a * b; + if (a % 1 == 0 && b % 1 == 0) { + lcm = ArithmeticUtils.lcm((int) a, (int) b); + } + if (2 * Math.PI * lcm + - this.toolState.getTraveledRadius() < this.epsilon) { + return true; + } + return false; + } + +} diff --git a/src/main/java/ir/hosseinmp76/shayancircles/tools/ToolType.java b/src/main/java/ir/hosseinmp76/shayancircles/tools/ToolType.java new file mode 100644 index 0000000..e47c77f --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/tools/ToolType.java @@ -0,0 +1,5 @@ +package ir.hosseinmp76.shayancircles.tools; + +public enum ToolType { + ClasicCircleOutOfRingTool, ClasicRingCircleTool, LongOvalAndCircleTool +} \ No newline at end of file diff --git a/src/main/java/ir/hosseinmp76/shayancircles/ui/fx/FXApplication.java b/src/main/java/ir/hosseinmp76/shayancircles/ui/fx/FXApplication.java new file mode 100644 index 0000000..4a0dfd1 --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/ui/fx/FXApplication.java @@ -0,0 +1,248 @@ +package ir.hosseinmp76.shayancircles.ui.fx; + +import java.awt.image.RenderedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + + +import ir.hosseinmp76.shayancircles.tools.AbstractTool; +import ir.hosseinmp76.shayancircles.tools.ClasicCircleOutOfRingTool; +import ir.hosseinmp76.shayancircles.tools.ClasicRingCircleTool; +import ir.hosseinmp76.shayancircles.tools.LongOvalAndCircleTool; +import ir.hosseinmp76.shayancircles.tools.ToolType; +import javafx.animation.AnimationTimer; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.embed.swing.SwingFXUtils; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.geometry.Orientation; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.Slider; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextField; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Region; +import javafx.scene.layout.TilePane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +public class FXApplication extends Application { + + static Region root; + static final int width = 900; + static final int height = 700; + + public static void startApp() { + Application.launch(); + } + + AnimationTimer frameRateMeter; + private final long[] frameTimes = new long[100]; + + private int frameTimeIndex = 0; + + private boolean arrayFilled = false; + + MyCanvas myCanvas; + + Label label = new Label(); + + Slider slider; + + AbstractTool tool; + + @Override + public void start(final Stage stage) throws Exception { + + final SplitPane mainSplitePane = new SplitPane(); + mainSplitePane.setDividerPositions(0.2); + final VBox sidebarVBox = new VBox(); + sidebarVBox.getChildren().add(this.label); + final TilePane fpr = new TilePane(); + final Label l = new Label("r"); + final TextField rFiled = new TextField("150"); + rFiled.setMaxWidth(50); + fpr.getChildren().add(l); + fpr.getChildren().add(rFiled); + sidebarVBox.getChildren().add(fpr); + + final FlowPane fpR = new FlowPane(); + final Label l2 = new Label("R"); + final TextField rFiled2 = new TextField("251"); + final TextField rFiled3 = new TextField("261"); + fpR.getChildren().add(l2); + fpR.getChildren().add(rFiled2); + fpR.getChildren().add(rFiled3); + sidebarVBox.getChildren().add(fpR); + + this.frameRateMeter = new AnimationTimer() { + + @Override + public void handle(final long now) { + + final long oldFrameTime = FXApplication.this.frameTimes[FXApplication.this.frameTimeIndex]; + FXApplication.this.frameTimes[FXApplication.this.frameTimeIndex] = now; + FXApplication.this.frameTimeIndex = (FXApplication.this.frameTimeIndex + + 1) % FXApplication.this.frameTimes.length; + if (FXApplication.this.frameTimeIndex == 0) { + FXApplication.this.arrayFilled = true; + } + if (FXApplication.this.arrayFilled) { + final long elapsedNanos = now - oldFrameTime; + final long elapsedNanosPerFrame = elapsedNanos + / FXApplication.this.frameTimes.length; + final double frameRate = 1_000_000_000.0 + / elapsedNanosPerFrame; + FXApplication.this.label.setText(String + .format("Current frame rate: %.3f", frameRate)); + } + } + }; + + this.frameRateMeter.start(); + + final FlowPane fpdis = new FlowPane(); + final Label l3 = new Label("dis"); + final TextField dFiled3 = new TextField("53"); + fpdis.getChildren().add(l3); + fpdis.getChildren().add(dFiled3); + sidebarVBox.getChildren().add(fpdis); + final Button startButt = new Button("start"); + final Button stopButt = new Button("stop"); + final ColorPicker colorPicker = new ColorPicker(); + + colorPicker.setOnAction(event -> { + FXApplication.this.tool.changeColor(colorPicker.getValue()); + }); + final var myCanvasParentPane = new Pane(); + + ComboBox comboBox = new ComboBox(); + comboBox.getItems().add(ToolType.ClasicCircleOutOfRingTool); + comboBox.getItems().add(ToolType.ClasicRingCircleTool); + comboBox.getItems().add(ToolType.LongOvalAndCircleTool); + + startButt.setOnMouseClicked(event -> { + stopButt.setText("stop"); + if (FXApplication.this.myCanvas != null) { + FXApplication.this.myCanvas.start(); + return; + } + final double r = Double.parseDouble(rFiled.getText()); + final double R = Double.parseDouble(rFiled2.getText()); + final double RR = Double.parseDouble(rFiled3.getText()); + final double dis = Double.parseDouble(dFiled3.getText()); + + final var color = colorPicker.getValue(); + switch (comboBox.getValue()) { + case ClasicRingCircleTool: + FXApplication.this.tool = new ClasicRingCircleTool(R, RR, r, + dis, color); + break; + case ClasicCircleOutOfRingTool: + FXApplication.this.tool = new ClasicCircleOutOfRingTool(R, RR, + r, dis, color); + break; + case LongOvalAndCircleTool: + FXApplication.this.tool = new LongOvalAndCircleTool(R, RR, r, + dis, color); + break; + default: + break; + } + + final var spped = (int) FXApplication.this.slider.getValue(); + + FXApplication.this.myCanvas = new MyCanvas( + FXApplication.width * 8 / 10, FXApplication.height, + FXApplication.this.tool, spped); + FXApplication.this.myCanvas.start(); + myCanvasParentPane.getChildren().add(FXApplication.this.myCanvas); + + }); + + stopButt.setOnMouseClicked(y -> { + if (this.myCanvas != null) { + if (this.myCanvas.isStoppedDrawing()) { + + myCanvasParentPane.getChildren().clear(); + this.myCanvas = null; + stopButt.setText("stop"); + + } else { + this.myCanvas.stop(); + stopButt.setText("remove"); + } + } + }); + + final Button buttonSave = new Button("Save"); + buttonSave.setOnAction(t -> { + final FileChooser fileChooser = new FileChooser(); + + // Set extension filter + final FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter( + "png files (*.png)", "*.png"); + fileChooser.getExtensionFilters().add(extFilter); + + // Show save file dialog + final File file = fileChooser.showSaveDialog(stage); + + if (file != null) { + try { + final WritableImage writableImage = new WritableImage( + FXApplication.width, FXApplication.height); + FXApplication.this.myCanvas.snapshot(null, writableImage); + final RenderedImage renderedImage = SwingFXUtils + .fromFXImage(writableImage, null); + ImageIO.write(renderedImage, "png", file); + } catch (final IOException ex) { + } + } + }); + + sidebarVBox.getChildren().add(colorPicker); + + sidebarVBox.getChildren().add(comboBox); + + sidebarVBox.getChildren().add(startButt); + + sidebarVBox.getChildren().add(stopButt); + sidebarVBox.getChildren().add(buttonSave); + this.slider = new Slider(1, 1000_0, 1000); +// slider.setMaxHeight(300); + this.slider.setMinHeight(400); + this.slider.setOrientation(Orientation.VERTICAL); + this.slider.setOnMouseDragged(e -> { + this.myCanvas.changeSpeed((int) this.slider.getValue()); + }); + + sidebarVBox.getChildren().add(this.slider); + + mainSplitePane.getItems().add(sidebarVBox); + mainSplitePane.getItems().add(myCanvasParentPane); + final Scene scene = new Scene(mainSplitePane, FXApplication.width, + FXApplication.height, Color.ANTIQUEWHITE); + + scene.getStylesheets().add( + FXApplication.class.getResource("styles.css").toExternalForm()); + + stage.setTitle("JavaFX and Gradle"); + stage.setScene(scene); + stage.setOnCloseRequest(x -> System.exit(0)); + stage.show(); + FXApplication.root = mainSplitePane; + + } + +} diff --git a/src/main/java/ir/hosseinmp76/shayancircles/ui/fx/MyCanvas.java b/src/main/java/ir/hosseinmp76/shayancircles/ui/fx/MyCanvas.java new file mode 100644 index 0000000..052e9cf --- /dev/null +++ b/src/main/java/ir/hosseinmp76/shayancircles/ui/fx/MyCanvas.java @@ -0,0 +1,97 @@ +package ir.hosseinmp76.shayancircles.ui.fx; + +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; + +import ir.hosseinmp76.shayancircles.tools.AbstractTool; +import javafx.application.Platform; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.paint.Color; + +public class MyCanvas extends Canvas { + AbstractTool tool; + int width; + int height; + private final Vector2D centerOfCanvas; + int perSecond; + final double travle = 1; + + Timer timer = new Timer(); + int repeatPerTick = 1; + + MyCanvas(final int widght, final int height, final AbstractTool tool, + final int perSecond) { + super(widght, height); + this.width = widght; + this.height = height; + this.tool = tool; + this.perSecond = perSecond; + this.centerOfCanvas = new Vector2D(widght / 2, height / 2); + var gc = this.getGraphicsContext2D(); + gc.fillOval(0, 0, widght, height); + + } + + boolean stoppedDrawing = true; + + public boolean isStoppedDrawing() { + return stoppedDrawing; + + } + + public void start() { + if (!isStoppedDrawing() || this.tool.isFinished()) + return; + this.timer = new Timer(); + TimerTask task; + + task = new TimerTask() { + @Override + public void run() { + MyCanvas.this.handle(); + } + + }; + repeatPerTick = Math.max(this.perSecond / 1000, 1); + this.timer.scheduleAtFixedRate(task, 0, + Math.max(1000 / this.perSecond, 1)); + this.stoppedDrawing = false; + } + + public void stop() { + + this.timer.cancel(); + this.stoppedDrawing = true; + } + + protected void handle() { + final GraphicsContext graphics_context = this.getGraphicsContext2D(); + Platform.runLater(() -> { + for (int i = 0; i < repeatPerTick; i++) { + + graphics_context.setFill(this.tool.getColor()); + final var point = this.tool.getPoint(); + final var x = this.centerOfCanvas.getX() + point.getX(); + final var y = this.centerOfCanvas.getY() - point.getY(); + graphics_context.fillRect(x, y, 2, 2); + this.tool.nextPoint(this.travle); + } + }); + + if (this.tool.isFinished()) { + this.timer.cancel(); + System.out.println("fff"); + } + } + + public void changeSpeed(int value) { + // TODO Auto-generated method stub + stop(); + this.perSecond = value; + start(); + + } +} diff --git a/src/main/resources/ir/hosseinmp76/shayancircles/ui/fx/styles.css b/src/main/resources/ir/hosseinmp76/shayancircles/ui/fx/styles.css new file mode 100644 index 0000000..600f13a --- /dev/null +++ b/src/main/resources/ir/hosseinmp76/shayancircles/ui/fx/styles.css @@ -0,0 +1,7 @@ +.button { + -fx-text-fill: blue; +} + +.root { + -fx-base: rgba(60, 63, 65, 255); +} \ No newline at end of file