Compare commits
1 Commits
main
...
97f8eaeb49
| Author | SHA1 | Date | |
|---|---|---|---|
| 97f8eaeb49 |
25
.gitignore
vendored
@@ -32,28 +32,3 @@ hs_err_pid*
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
._*
|
._*
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
.gradle
|
|
||||||
local.properties
|
|
||||||
**/build/
|
|
||||||
!src/**/build/
|
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
|
||||||
gradle-app.setting
|
|
||||||
|
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
|
||||||
!gradle-wrapper.jar
|
|
||||||
|
|
||||||
# Avoid ignore Gradle wrappper properties
|
|
||||||
!gradle-wrapper.properties
|
|
||||||
|
|
||||||
# Cache of project
|
|
||||||
.gradletasknamecache
|
|
||||||
|
|
||||||
# Python mkdocs
|
|
||||||
.venv
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|||||||
54
CHANGELOG.md
@@ -7,63 +7,13 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
- Dokumentation erweitert.
|
|
||||||
- Caching-Mechanismen in Klasse `util.Cache` ausgelagert.
|
|
||||||
- `util.io.ImageLoader` und `util.io.FontLoader` verwenden `Cache`.
|
|
||||||
- `mouseWheelMoved` Eventhandler für Mausrad.
|
|
||||||
- `DrawingLayer.imageRotate(...)` Methoden, um Bilder um ihr Zentrum gedreht zu zeichnen.
|
|
||||||
|
|
||||||
## Changed
|
|
||||||
- Die Methoden in `Validator` erwarten nun als zweiten Parameter den Namen des Parameters, der geprüft wird.
|
|
||||||
- `DrawingLayer.image(...)` mit Größenänderung umbenannt zu `imageScale(...)`.
|
|
||||||
- Klassen in `schule.ngb.zm.util.io` werfen nun nur eine Warnung ohne Stack-Trace, wenn die Ressource nicht gefunden werden konnte.
|
|
||||||
|
|
||||||
## Fixed
|
|
||||||
- `Constants.choice(int...)` und `Constants.choice(double...)` wiederhergestellt.
|
|
||||||
- Timing-Problem beim Aufruf von `AudioListener.playbackStopped()` in `Sound` behoben.
|
|
||||||
|
|
||||||
## Removed
|
|
||||||
- `layers.Shape2DLayer` ist nur noch im Test-Paket verfügbar.
|
|
||||||
|
|
||||||
## Version 0.0.34
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- `Faker`-Klasse zur Erzeugung von Fake-Daten hinzugefügt.
|
|
||||||
- Dokumentation unter [zeichenmaschine.xyz](https://zeichenmaschine.xyz) mit
|
|
||||||
[MkDocs](https://www.mkdocs.org) und [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/).
|
|
||||||
- Neue `image` methoden im `DrawingLayer`.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt.
|
|
||||||
- `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage.
|
|
||||||
- `io` Klassen nutzen nun mehr der `java.nio` Funktionen.
|
|
||||||
- Package-Struktur angepasst.
|
|
||||||
|
|
||||||
## Version 0.0.23
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- System für EventListener.
|
|
||||||
- `AudioListener` und `AnimationListener` als erste Anwendungsfälle.
|
|
||||||
- Pakete für Animationen und Maschinelles-Lernen.
|
|
||||||
- Farbverläufe als Füllung.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `update(double)` und `draw()` werden nun in einem eigenen Thread aufgerufen.
|
|
||||||
- Die Standardwerte in `Constants` wurden mit dem Prefix `DEFAULT_` benannt (vorher `STD_`).
|
|
||||||
- Die Standardwerte sind nun nicht mehr `final` und können vom Nutzer manuell gesetzt werden.
|
|
||||||
|
|
||||||
## Version 0.0.22
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- Interface `Audio` extrahiert, mit Basisfunktionen von `Sound` und `Music`.
|
- Interface `Audio` extrahiert, mit Basisfunktionen von `Sound` und `Music`.
|
||||||
- Klasse `Mixer` steuert mehrere Audio-Objekte gleichzeitig.
|
- Klasse `Mixer` steuert mehrere Audio-Objekte gleichzeitig.
|
||||||
- Klasse `tasks.RateLimitedTask`, `tasks.FramerateLimitedTask`, `tasks.FrameSynchronizedTask` und `tasks.DelayedTask`.
|
|
||||||
|
|
||||||
### Changed
|
## Changed
|
||||||
- Neue Package-Struktur:
|
- Neue Package-Struktur:
|
||||||
- `schule.ngb.zm.media` für Audio-Klassen (und ggf. zukünftig Video).
|
- `schule.ngb.zm.media` für Audio-Klassen (und ggf. zukünftig Video).
|
||||||
- `schule.ngb.zm.util.tasks` für alles Rund um Parallelität.
|
- `schule.ngb.zm.tasks` für alles Rund um Parallelität.
|
||||||
- `Zeichenthread` und `TaskRunner` setzen die Namen der Threads für besseres Debugging.
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Beispielprojekte in [eigenes Repository](https://github.com/jneug/zeichenmaschine-examples) verschoben.
|
- Beispielprojekte in [eigenes Repository](https://github.com/jneug/zeichenmaschine-examples) verschoben.
|
||||||
|
|||||||
153
build.gradle
@@ -1,153 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'idea'
|
|
||||||
id 'java-library'
|
|
||||||
id 'org.hidetake.ssh' version '2.10.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
group 'schule.ngb'
|
|
||||||
version '0.0.35-SNAPSHOT'
|
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
compileJava {
|
|
||||||
options.release = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
remotes {
|
|
||||||
uberspace {
|
|
||||||
host = 'westphal.uberspace.de'
|
|
||||||
user = 'ngb'
|
|
||||||
identity = file("${System.properties['user.home']}/.ssh/uberspace_rsa")
|
|
||||||
knownHosts = allowAnyHosts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
runtimeOnly 'com.googlecode.soundlibs:jlayer:1.0.1.4'
|
|
||||||
runtimeOnly 'com.googlecode.soundlibs:tritonus-share:0.3.7.4'
|
|
||||||
runtimeOnly 'com.googlecode.soundlibs:mp3spi:1.9.5.4'
|
|
||||||
|
|
||||||
compileOnlyApi 'colt:colt:1.2.0'
|
|
||||||
//api 'colt:colt:1.2.0'
|
|
||||||
//api 'net.sourceforge.parallelcolt:parallelcolt:0.10.1'
|
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1'
|
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest {
|
|
||||||
attributes 'Class-Path': '.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register('jarMP3SPI', Jar) {
|
|
||||||
group "build"
|
|
||||||
description "Build jar with MP3SPI included"
|
|
||||||
|
|
||||||
archiveClassifier = 'mp3spi'
|
|
||||||
duplicatesStrategy = 'exclude'
|
|
||||||
// archivesBaseName = 'zeichenmaschine-mp3spi'
|
|
||||||
from {
|
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
|
||||||
}
|
|
||||||
with jar
|
|
||||||
}
|
|
||||||
|
|
||||||
task buildAll {
|
|
||||||
group "build"
|
|
||||||
description "Build all jar packages"
|
|
||||||
|
|
||||||
dependsOn 'jar'
|
|
||||||
dependsOn 'jarMP3SPI'
|
|
||||||
dependsOn 'sourcesJar'
|
|
||||||
dependsOn 'javadocJar'
|
|
||||||
}
|
|
||||||
|
|
||||||
javadoc {
|
|
||||||
options {
|
|
||||||
encoding = "UTF-8"
|
|
||||||
overview = "src/resources/java/overview.html"
|
|
||||||
// title = "Die Zeichenmaschine"
|
|
||||||
|
|
||||||
// options.links 'https://docs.oracle.com/javase/8/docs/api/'
|
|
||||||
// options.links 'https://docs.oracle.com/javaee/7/api'
|
|
||||||
options.links 'https://docs.oracle.com/en/java/javase/11/docs/api'
|
|
||||||
}
|
|
||||||
options.addStringOption("charset", "UTF-8")
|
|
||||||
}
|
|
||||||
|
|
||||||
task mkdocs(type: Exec) {
|
|
||||||
group "documentation"
|
|
||||||
description "Build MKDocs site"
|
|
||||||
|
|
||||||
workingDir "${projectDir}"
|
|
||||||
commandLine ".venv/bin/python", "-m", "mkdocs", "build"
|
|
||||||
}
|
|
||||||
|
|
||||||
task buildDocs {
|
|
||||||
group "documentation"
|
|
||||||
description "Run all documentation tasks"
|
|
||||||
|
|
||||||
dependsOn 'javadoc'
|
|
||||||
dependsOn 'javadocJar'
|
|
||||||
dependsOn 'mkdocs'
|
|
||||||
}
|
|
||||||
|
|
||||||
task zipSite(type: Zip) {
|
|
||||||
group "documentation"
|
|
||||||
description "Create zip archives for documentations"
|
|
||||||
|
|
||||||
dependsOn 'mkdocs'
|
|
||||||
|
|
||||||
from fileTree("${buildDir}/docs/site")
|
|
||||||
exclude '*.py'
|
|
||||||
exclude '__pycache__'
|
|
||||||
archiveName 'site.zip'
|
|
||||||
destinationDir(file("${buildDir}/docs"))
|
|
||||||
}
|
|
||||||
|
|
||||||
task zipJavadoc(type: Zip) {
|
|
||||||
group "documentation"
|
|
||||||
description "Create zip archives for javadoc"
|
|
||||||
|
|
||||||
dependsOn 'javadoc'
|
|
||||||
|
|
||||||
from fileTree("${buildDir}/docs/javadoc")
|
|
||||||
archiveName 'javadoc.zip'
|
|
||||||
destinationDir(file("${buildDir}/docs"))
|
|
||||||
}
|
|
||||||
|
|
||||||
task uploadDocs {
|
|
||||||
group "documentation"
|
|
||||||
description "Run all documentation tasks and upload artifacts to zeichenmaschine.xyz"
|
|
||||||
|
|
||||||
dependsOn 'zipSite'
|
|
||||||
dependsOn 'zipJavadoc'
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
ssh.run {
|
|
||||||
session(remotes.uberspace) {
|
|
||||||
execute 'rm -rf /var/www/virtual/ngb/zeichenmaschine.xyz/*', ignoreError: true
|
|
||||||
|
|
||||||
put from: "${buildDir}/docs/site.zip", into: '/var/www/virtual/ngb/zeichenmaschine.xyz', ignoreError: true
|
|
||||||
execute 'unzip -o -q /var/www/virtual/ngb/zeichenmaschine.xyz/site.zip -d /var/www/virtual/ngb/zeichenmaschine.xyz'
|
|
||||||
|
|
||||||
put from: "${buildDir}/docs/javadoc.zip", into: '/var/www/virtual/ngb/zeichenmaschine.xyz', ignoreError: true
|
|
||||||
execute 'unzip -o -q /var/www/virtual/ngb/zeichenmaschine.xyz/javadoc.zip -d /var/www/virtual/ngb/zeichenmaschine.xyz/docs'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
||||||
@@ -1,634 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "excalidraw",
|
|
||||||
"version": 2,
|
|
||||||
"source": "https://excalidraw.com",
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 152,
|
|
||||||
"versionNonce": 1288225375,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "fxk8rHocjpTteICJMa6n8",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 619.9963325816416,
|
|
||||||
"y": 186.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#fab005",
|
|
||||||
"width": 150,
|
|
||||||
"height": 46,
|
|
||||||
"seed": 1339263918,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"id": "JmEBjNProPAgJQZUvMGGa"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "eryKwAzIMcBMQP0Ybl1Mm",
|
|
||||||
"type": "arrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"updated": 1670307372550,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 264,
|
|
||||||
"versionNonce": 1267158257,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "wZLPkORf755_2Vp0J6I0V",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 566.9963325816416,
|
|
||||||
"y": 289.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#fab005",
|
|
||||||
"width": 248,
|
|
||||||
"height": 50,
|
|
||||||
"seed": 359801906,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"id": "9TujIdwDvtinylO3z50y6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "eryKwAzIMcBMQP0Ybl1Mm",
|
|
||||||
"type": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Zc9GOJ8DsIQYo4WaGvvHy",
|
|
||||||
"type": "arrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"updated": 1670307379131,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 376,
|
|
||||||
"versionNonce": 1807861489,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "290mWFx31fA5smc5FqPUr",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 607.9963325816416,
|
|
||||||
"y": 392.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#228be6",
|
|
||||||
"width": 143,
|
|
||||||
"height": 50,
|
|
||||||
"seed": 1948766318,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"id": "_ISR-LCZm2Hu2G57R_uxN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Zc9GOJ8DsIQYo4WaGvvHy",
|
|
||||||
"type": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hUUqTCXva-vZjnBeM-PR3",
|
|
||||||
"type": "arrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"updated": 1670307888001,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 518,
|
|
||||||
"versionNonce": 2136415391,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "kbEG_cCZadugfCPxYedhf",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 614.9963325816416,
|
|
||||||
"y": 589.9758066195944,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#228be6",
|
|
||||||
"width": 131,
|
|
||||||
"height": 50,
|
|
||||||
"seed": 97599794,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"id": "6KjVrNy_dxGXJtntdqrjY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hUUqTCXva-vZjnBeM-PR3",
|
|
||||||
"type": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bplSSGA4kyy-Av7nKNK1B",
|
|
||||||
"type": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "qukSk_W6enSdwERPEPkhZ",
|
|
||||||
"type": "arrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"updated": 1670307888001,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 110,
|
|
||||||
"versionNonce": 1350901746,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "JmEBjNProPAgJQZUvMGGa",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 643.9963325816416,
|
|
||||||
"y": 199.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 102,
|
|
||||||
"height": 20,
|
|
||||||
"seed": 1889147762,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670164406970,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "new Shapes()",
|
|
||||||
"baseline": 14,
|
|
||||||
"textAlign": "center",
|
|
||||||
"verticalAlign": "middle",
|
|
||||||
"containerId": "fxk8rHocjpTteICJMa6n8",
|
|
||||||
"originalText": "new Shapes()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 245,
|
|
||||||
"versionNonce": 1536101230,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "9TujIdwDvtinylO3z50y6",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 584.9963325816416,
|
|
||||||
"y": 304.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 212,
|
|
||||||
"height": 20,
|
|
||||||
"seed": 1297543150,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670164433291,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "super(800, 800, \"Shapes\")",
|
|
||||||
"baseline": 14,
|
|
||||||
"textAlign": "center",
|
|
||||||
"verticalAlign": "middle",
|
|
||||||
"containerId": "wZLPkORf755_2Vp0J6I0V",
|
|
||||||
"originalText": "super(800, 800, \"Shapes\")"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 362,
|
|
||||||
"versionNonce": 2069822514,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "_ISR-LCZm2Hu2G57R_uxN",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 650.4963325816416,
|
|
||||||
"y": 407.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 58,
|
|
||||||
"height": 20,
|
|
||||||
"seed": 525219186,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670164509204,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "setup()",
|
|
||||||
"baseline": 14,
|
|
||||||
"textAlign": "center",
|
|
||||||
"verticalAlign": "middle",
|
|
||||||
"containerId": "290mWFx31fA5smc5FqPUr",
|
|
||||||
"originalText": "setup()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 502,
|
|
||||||
"versionNonce": 747611665,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "6KjVrNy_dxGXJtntdqrjY",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 655.4963325816416,
|
|
||||||
"y": 604.9758066195944,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 50,
|
|
||||||
"height": 20,
|
|
||||||
"seed": 1808016110,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670307245844,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "draw()",
|
|
||||||
"baseline": 14,
|
|
||||||
"textAlign": "center",
|
|
||||||
"verticalAlign": "middle",
|
|
||||||
"containerId": "kbEG_cCZadugfCPxYedhf",
|
|
||||||
"originalText": "draw()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "arrow",
|
|
||||||
"version": 31,
|
|
||||||
"versionNonce": 101778865,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "eryKwAzIMcBMQP0Ybl1Mm",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 694.9963325816416,
|
|
||||||
"y": 242.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#12b886",
|
|
||||||
"width": 0,
|
|
||||||
"height": 38,
|
|
||||||
"seed": 1130444978,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670307364549,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"startBinding": {
|
|
||||||
"elementId": "fxk8rHocjpTteICJMa6n8",
|
|
||||||
"focus": 0,
|
|
||||||
"gap": 10
|
|
||||||
},
|
|
||||||
"endBinding": {
|
|
||||||
"elementId": "wZLPkORf755_2Vp0J6I0V",
|
|
||||||
"focus": 0.03225806451612903,
|
|
||||||
"gap": 9
|
|
||||||
},
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": "arrow",
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
38
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "arrow",
|
|
||||||
"version": 160,
|
|
||||||
"versionNonce": 1672489439,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "Zc9GOJ8DsIQYo4WaGvvHy",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 686.7253597961584,
|
|
||||||
"y": 347.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#12b886",
|
|
||||||
"width": 0.3256260532492661,
|
|
||||||
"height": 36,
|
|
||||||
"seed": 1138446962,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670307364549,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"startBinding": {
|
|
||||||
"elementId": "wZLPkORf755_2Vp0J6I0V",
|
|
||||||
"gap": 8,
|
|
||||||
"focus": 0.03595554587056003
|
|
||||||
},
|
|
||||||
"endBinding": {
|
|
||||||
"elementId": "290mWFx31fA5smc5FqPUr",
|
|
||||||
"gap": 9,
|
|
||||||
"focus": 0.09195903246894747
|
|
||||||
},
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": "arrow",
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-0.3256260532492661,
|
|
||||||
36
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "arrow",
|
|
||||||
"version": 616,
|
|
||||||
"versionNonce": 447594705,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "hUUqTCXva-vZjnBeM-PR3",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 684.6524492354905,
|
|
||||||
"y": 452.97580661959455,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#228be6",
|
|
||||||
"width": 1.5815106831711319,
|
|
||||||
"height": 29.000000000000114,
|
|
||||||
"seed": 1169930546,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670307888001,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"startBinding": {
|
|
||||||
"elementId": "290mWFx31fA5smc5FqPUr",
|
|
||||||
"focus": -0.044553538542618336,
|
|
||||||
"gap": 10
|
|
||||||
},
|
|
||||||
"endBinding": {
|
|
||||||
"elementId": "5cG3FjQlYuIGPZMsIMgJU",
|
|
||||||
"focus": 0.08947713014192164,
|
|
||||||
"gap": 6.999999999999915
|
|
||||||
},
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": "arrow",
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
1.5815106831711319,
|
|
||||||
29.000000000000114
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 560,
|
|
||||||
"versionNonce": 11022527,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "5cG3FjQlYuIGPZMsIMgJU",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 616.4963325816416,
|
|
||||||
"y": 488.9758066195945,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#228be6",
|
|
||||||
"width": 131,
|
|
||||||
"height": 50,
|
|
||||||
"seed": 609895569,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": [
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"id": "-Fl205ZvyGaxHAHt7C3r3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hUUqTCXva-vZjnBeM-PR3",
|
|
||||||
"type": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bplSSGA4kyy-Av7nKNK1B",
|
|
||||||
"type": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "qukSk_W6enSdwERPEPkhZ",
|
|
||||||
"type": "arrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"updated": 1670307888001,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 551,
|
|
||||||
"versionNonce": 685087263,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "-Fl205ZvyGaxHAHt7C3r3",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 647.4963325816416,
|
|
||||||
"y": 503.97580661959455,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 69,
|
|
||||||
"height": 20,
|
|
||||||
"seed": 415680767,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670307260196,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "update()",
|
|
||||||
"baseline": 14,
|
|
||||||
"textAlign": "center",
|
|
||||||
"verticalAlign": "middle",
|
|
||||||
"containerId": "5cG3FjQlYuIGPZMsIMgJU",
|
|
||||||
"originalText": "update()"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bplSSGA4kyy-Av7nKNK1B",
|
|
||||||
"type": "arrow",
|
|
||||||
"x": 683.9963325816416,
|
|
||||||
"y": 546.9758066195944,
|
|
||||||
"width": 1,
|
|
||||||
"height": 34,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#228be6",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 1937189809,
|
|
||||||
"version": 33,
|
|
||||||
"versionNonce": 1639939761,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670307888001,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1,
|
|
||||||
34
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": {
|
|
||||||
"elementId": "5cG3FjQlYuIGPZMsIMgJU",
|
|
||||||
"focus": -0.044849023090586096,
|
|
||||||
"gap": 7.999999999999858
|
|
||||||
},
|
|
||||||
"endBinding": {
|
|
||||||
"elementId": "kbEG_cCZadugfCPxYedhf",
|
|
||||||
"focus": 0.022646536412078155,
|
|
||||||
"gap": 9
|
|
||||||
},
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": "arrow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "qukSk_W6enSdwERPEPkhZ",
|
|
||||||
"type": "arrow",
|
|
||||||
"x": 758.9963325816416,
|
|
||||||
"y": 615.9758066195944,
|
|
||||||
"width": 87,
|
|
||||||
"height": 107.99999999999994,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 677436529,
|
|
||||||
"version": 170,
|
|
||||||
"versionNonce": 377207327,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1670307315516,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
86,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
83,
|
|
||||||
-106.99999999999994
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1,
|
|
||||||
-107.99999999999994
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": {
|
|
||||||
"elementId": "kbEG_cCZadugfCPxYedhf",
|
|
||||||
"focus": 0.04,
|
|
||||||
"gap": 13
|
|
||||||
},
|
|
||||||
"endBinding": {
|
|
||||||
"elementId": "5cG3FjQlYuIGPZMsIMgJU",
|
|
||||||
"focus": -0.26783652736088875,
|
|
||||||
"gap": 10.5
|
|
||||||
},
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": "arrow"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"appState": {
|
|
||||||
"gridSize": null,
|
|
||||||
"viewBackgroundColor": "#ffffff"
|
|
||||||
},
|
|
||||||
"files": {}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 411 KiB |
@@ -1,375 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "excalidraw",
|
|
||||||
"version": 2,
|
|
||||||
"source": "https://excalidraw.com",
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"id": "Mhp-wQ2wZxCI4BYWvTvV2",
|
|
||||||
"type": "ellipse",
|
|
||||||
"x": 420,
|
|
||||||
"y": 233,
|
|
||||||
"width": 385.99999999999994,
|
|
||||||
"height": 385.99999999999994,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#ffdf22",
|
|
||||||
"fillStyle": "cross-hatch",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 474299186,
|
|
||||||
"version": 149,
|
|
||||||
"versionNonce": 2072559726,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158627399,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SqZRA75ACKHo0lB799wpS",
|
|
||||||
"type": "line",
|
|
||||||
"x": 605,
|
|
||||||
"y": 426,
|
|
||||||
"width": 173.02018127597637,
|
|
||||||
"height": 99.89324823492274,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#087f5b",
|
|
||||||
"backgroundColor": "#ffdf22",
|
|
||||||
"fillStyle": "cross-hatch",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "dashed",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 484805038,
|
|
||||||
"version": 286,
|
|
||||||
"versionNonce": 1603007154,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670159044301,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
173.02018127597637,
|
|
||||||
-99.89324823492274
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": null,
|
|
||||||
"endBinding": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7XVC5Wqiy62pN9Vc92bgl",
|
|
||||||
"type": "text",
|
|
||||||
"x": 645.7358370304399,
|
|
||||||
"y": 356.14958623820286,
|
|
||||||
"width": 86,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 5.766085793504818,
|
|
||||||
"strokeColor": "#087f5b",
|
|
||||||
"backgroundColor": "#ffdf22",
|
|
||||||
"fillStyle": "cross-hatch",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 1883808110,
|
|
||||||
"version": 228,
|
|
||||||
"versionNonce": 627525998,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670159044301,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "moleRadius",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "moleRadius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "zp9JMFpfOA6ZEWCCiqwW0",
|
|
||||||
"type": "line",
|
|
||||||
"x": 606,
|
|
||||||
"y": 427,
|
|
||||||
"width": 128.012747010704,
|
|
||||||
"height": 208.02230726873202,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#fa5252",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "dashed",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 224372978,
|
|
||||||
"version": 81,
|
|
||||||
"versionNonce": 563971630,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158772553,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-128.012747010704,
|
|
||||||
-208.02230726873202
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": null,
|
|
||||||
"endBinding": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "LIDFlKBUhkbZdzTUFT1qp",
|
|
||||||
"type": "line",
|
|
||||||
"x": 604.8240237554426,
|
|
||||||
"y": 425.10629955596494,
|
|
||||||
"width": 127.71248741178238,
|
|
||||||
"height": 4.628286654564533,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#fa5252",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "dashed",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 34525358,
|
|
||||||
"version": 124,
|
|
||||||
"versionNonce": 1131380590,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158787752,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-127.71248741178238,
|
|
||||||
-4.628286654564533
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": null,
|
|
||||||
"endBinding": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "-Of3RfHAvJDq0qHIwjm2U",
|
|
||||||
"type": "ellipse",
|
|
||||||
"x": 471.96813247323996,
|
|
||||||
"y": 413.60866976111646,
|
|
||||||
"width": 12,
|
|
||||||
"height": 12,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#4c6ef5",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 1050702514,
|
|
||||||
"version": 384,
|
|
||||||
"versionNonce": 1957432558,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158970885,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "NB4WGe-lT7XqLrsKX8jN8",
|
|
||||||
"type": "ellipse",
|
|
||||||
"x": 472,
|
|
||||||
"y": 212,
|
|
||||||
"width": 12,
|
|
||||||
"height": 12,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#fa5252",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 2059751730,
|
|
||||||
"version": 277,
|
|
||||||
"versionNonce": 756441010,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158769939,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T1vEWEHV-1FM4A7Q7dTzy",
|
|
||||||
"type": "ellipse",
|
|
||||||
"x": 601.6117120882632,
|
|
||||||
"y": 422.28007605476614,
|
|
||||||
"width": 6.436664554034337,
|
|
||||||
"height": 6.436664554034337,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#000000",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 0,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 1983845742,
|
|
||||||
"version": 376,
|
|
||||||
"versionNonce": 1202556466,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158984089,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "eEMIGJ2Ag5HqWZLPmjXMM",
|
|
||||||
"type": "text",
|
|
||||||
"x": 551.4695946462369,
|
|
||||||
"y": 432.8553279772536,
|
|
||||||
"width": 108,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#000000",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "dashed",
|
|
||||||
"roughness": 0,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 817794290,
|
|
||||||
"version": 97,
|
|
||||||
"versionNonce": 1438641266,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158980419,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "(moleX, moleY)",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "(moleX, moleY)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6cMmrU3lcJbub8sEKnVWe",
|
|
||||||
"type": "text",
|
|
||||||
"x": 409.80838527586974,
|
|
||||||
"y": 186.48331271464983,
|
|
||||||
"width": 135,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#c92a2a",
|
|
||||||
"backgroundColor": "#000000",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "dashed",
|
|
||||||
"roughness": 0,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 399694958,
|
|
||||||
"version": 166,
|
|
||||||
"versionNonce": 1571185842,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158939920,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "(mouseX, mouseY)",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "(mouseX, mouseY)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "0idmv_zLtEYyctHB4lSWV",
|
|
||||||
"type": "text",
|
|
||||||
"x": 406.7568976895149,
|
|
||||||
"y": 390.3911282833501,
|
|
||||||
"width": 135,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#364fc7",
|
|
||||||
"backgroundColor": "#000000",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "dashed",
|
|
||||||
"roughness": 0,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"seed": 1008210350,
|
|
||||||
"version": 224,
|
|
||||||
"versionNonce": 968376110,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670158975919,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "(mouseX, mouseY)",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "(mouseX, mouseY)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"appState": {
|
|
||||||
"gridSize": null,
|
|
||||||
"viewBackgroundColor": "#ffffff"
|
|
||||||
},
|
|
||||||
"files": {}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 821 KiB |
@@ -1,375 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "excalidraw",
|
|
||||||
"version": 2,
|
|
||||||
"source": "https://excalidraw.com",
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"id": "HPGP8tbAPy0aGI3_MCE3D",
|
|
||||||
"type": "diamond",
|
|
||||||
"x": 491.42779164474496,
|
|
||||||
"y": 479.56091158550765,
|
|
||||||
"width": 432.03244941244884,
|
|
||||||
"height": 55.325188818463005,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#868e96",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 1103578030,
|
|
||||||
"version": 138,
|
|
||||||
"versionNonce": 1150893294,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "vhIFJN3-91oJXV-RfFsBf",
|
|
||||||
"type": "diamond",
|
|
||||||
"x": 491.42779164474496,
|
|
||||||
"y": 435.8942449188411,
|
|
||||||
"width": 432.03244941244884,
|
|
||||||
"height": 55.325188818463005,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#fab005",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 618886834,
|
|
||||||
"version": 198,
|
|
||||||
"versionNonce": 1515704562,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "B2pM-4m_3Dk2w_6dY-hVJ",
|
|
||||||
"type": "diamond",
|
|
||||||
"x": 491.42779164474496,
|
|
||||||
"y": 392.2275782521744,
|
|
||||||
"width": 432.03244941244884,
|
|
||||||
"height": 55.325188818463005,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#228be6",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 844345582,
|
|
||||||
"version": 231,
|
|
||||||
"versionNonce": 1954508590,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Ge28XC9PqD26hknNaFXKP",
|
|
||||||
"type": "diamond",
|
|
||||||
"x": 491.42779164474496,
|
|
||||||
"y": 348.5609115855077,
|
|
||||||
"width": 432.03244941244884,
|
|
||||||
"height": 55.325188818463005,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 1326657902,
|
|
||||||
"version": 249,
|
|
||||||
"versionNonce": 685040306,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "PLU80s2TkyMEAOVgolwJx",
|
|
||||||
"type": "line",
|
|
||||||
"x": 506.9963325816415,
|
|
||||||
"y": 507.9758066195945,
|
|
||||||
"width": 0,
|
|
||||||
"height": 132.00000000000006,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "dotted",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 1246804466,
|
|
||||||
"version": 39,
|
|
||||||
"versionNonce": 627399022,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
-132.00000000000006
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": null,
|
|
||||||
"endBinding": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "xDdJ5FnbMLIbN95FE3Iyc",
|
|
||||||
"type": "line",
|
|
||||||
"x": 910.9963325816416,
|
|
||||||
"y": 506.9758066195945,
|
|
||||||
"width": 0,
|
|
||||||
"height": 132,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "dotted",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 1034568050,
|
|
||||||
"version": 77,
|
|
||||||
"versionNonce": 1363786866,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
-132
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": null,
|
|
||||||
"endBinding": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "clYu5Q9GzKbAfohBEsGwn",
|
|
||||||
"type": "line",
|
|
||||||
"x": 709.9963325816416,
|
|
||||||
"y": 532.9758066195944,
|
|
||||||
"width": 0,
|
|
||||||
"height": 129.99999999999994,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "dotted",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 1798035246,
|
|
||||||
"version": 42,
|
|
||||||
"versionNonce": 244579246,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"points": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
-129.99999999999994
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"lastCommittedPoint": null,
|
|
||||||
"startBinding": null,
|
|
||||||
"endBinding": null,
|
|
||||||
"startArrowhead": null,
|
|
||||||
"endArrowhead": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "v2nD7_bvWJYHqs82XDWO2",
|
|
||||||
"type": "text",
|
|
||||||
"x": 935.9963325816416,
|
|
||||||
"y": 497.2235059947391,
|
|
||||||
"width": 159,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#343a40",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "dotted",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 1323260978,
|
|
||||||
"version": 111,
|
|
||||||
"versionNonce": 372528622,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "Ebene 0: ColorLayer",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "Ebene 0: ColorLayer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c6T2j8hozL3RZcNYGMgRl",
|
|
||||||
"type": "text",
|
|
||||||
"x": 935.9963325816416,
|
|
||||||
"y": 453.55683932807256,
|
|
||||||
"width": 172,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#e67700",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "dotted",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 1949746930,
|
|
||||||
"version": 111,
|
|
||||||
"versionNonce": 1448527858,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "Ebene 1: DrawingLayer",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "Ebene 1: DrawingLayer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bAmGMg2i4Hvv7t_obXSKn",
|
|
||||||
"type": "text",
|
|
||||||
"x": 935.9963325816416,
|
|
||||||
"y": 409.8901726614059,
|
|
||||||
"width": 174,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#1864ab",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "dotted",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 706529710,
|
|
||||||
"version": 144,
|
|
||||||
"versionNonce": 1968138286,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163691142,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "Ebene 2: ShapesLayer",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "Ebene 2: ShapesLayer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9-QieQt-nVqGCe5D4r15j",
|
|
||||||
"type": "text",
|
|
||||||
"x": 935.9963325816416,
|
|
||||||
"y": 366.2235059947392,
|
|
||||||
"width": 65,
|
|
||||||
"height": 20,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#2b8a3e",
|
|
||||||
"backgroundColor": "#40c057",
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "dotted",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [],
|
|
||||||
"strokeSharpness": "round",
|
|
||||||
"seed": 2100742126,
|
|
||||||
"version": 179,
|
|
||||||
"versionNonce": 289074606,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1670163697684,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "Ebene 3",
|
|
||||||
"fontSize": 16,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"baseline": 14,
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "Ebene 3"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"appState": {
|
|
||||||
"gridSize": null,
|
|
||||||
"viewBackgroundColor": "#ffffff"
|
|
||||||
},
|
|
||||||
"files": {}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 289 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 408 KiB |
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 457 KiB |
@@ -1,12 +0,0 @@
|
|||||||
h1.title {
|
|
||||||
text-align: center;
|
|
||||||
color: #363636;
|
|
||||||
margin-bottom: .25rem;
|
|
||||||
}
|
|
||||||
h2.subtitle {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1rem;
|
|
||||||
color: #4a4a4a;
|
|
||||||
margin-top: -.25rem;
|
|
||||||
margin-bottom: -1.25rem;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block tabs %}
|
|
||||||
{{ super() }}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
{% block footer %}{% endblock %}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<figure markdown>
|
|
||||||
{ width=128 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
<h1 class="title">Zeichenmaschine.xyz</h1>
|
|
||||||
<h2 class="subtitle">Eine kleine Java-Bibliothek für grafische Programmierung im
|
|
||||||
Informatikunterricht.</h2>
|
|
||||||
|
|
||||||
## Projektidee
|
|
||||||
|
|
||||||
Die **Zeichenmaschine** ist eine für den Informatikunterricht entwickelte
|
|
||||||
Bibliothek, die unter anderem an [Processing](https://processing.org/) angelehnt
|
|
||||||
ist. Die Bibliothek soll einige der üblichen Anfängerschwierigkeiten mit Java
|
|
||||||
vereinfachen und grafische Ausgaben für Schülerinnen und Schüler im Unterricht
|
|
||||||
leichter nutzbar machen.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
|
|
||||||
Das Projekt befindet sich noch in der Entwicklungsphase und auch wenn die
|
|
||||||
aktuelle Version schon funktionsfähig ist und einen Großteil der angestrebten
|
|
||||||
Funktionen enthält, ist noch keine stabile Version 1.0 erreicht. Vor allem
|
|
||||||
am Umfang und konsistenten Design der APIs gilt es noch zu arbeiten und es
|
|
||||||
können sich Änderungen ergeben.
|
|
||||||
|
|
||||||
Feedback und Vorschläge zu diesem Prozess (oder auch eine Beteiligung an der
|
|
||||||
Entwicklung) können sehr gerne über [Github](https://github.com/jneug) oder
|
|
||||||
[Mastodon](https://bildung.social/@ngb) an mich kommuniziert werden.
|
|
||||||
|
|
||||||
(Gleiches gilt für diese Webseite zum Projekt.)
|
|
||||||
|
|
||||||
## Dokumentation
|
|
||||||
|
|
||||||
* [Schnellstart](schnellstart.md)
|
|
||||||
* [Installation](installation.md)
|
|
||||||
* [Javadoc]({{ javadoc() }})
|
|
||||||
|
|
||||||
## Über die Zeichenmaschine
|
|
||||||
|
|
||||||
Die _Zeichenmaschine_ ist aus dem Wunsch entstanden, nach einer Einführung in
|
|
||||||
die Grundlagen der Programmiersprache Java mit Processing, einen Übergang zur
|
|
||||||
objektorientierten Modellierung und Programmierung mit BlueJ zu leichter zu
|
|
||||||
ermöglichen. Mit Processing kann zwar auch objektorientiert programmiert werden,
|
|
||||||
aber mit Blick auf die Sekundarstufe II in NRW ist ein Wechsel zu einer
|
|
||||||
generellen Programmierumgebung wie BlueJ wünschenswert.
|
|
||||||
|
|
||||||
Die Motivation von Processing, schnell grafische Ergebnisse der eigenen
|
|
||||||
Programme zu sehen, sollte aber für den Übergang erhalten bleiben. So ist eine
|
|
||||||
kleine Bibliothek mit minimalen Abhängigkeiten entstanden, die an Processing
|
|
||||||
angelehnt einfache Schnittstellen bereitstellte, um Programmen eine grafische
|
|
||||||
Ausgabe zu ermöglichen, ohne viel "Boilerplate" Code schreiben zu müssen.
|
|
||||||
|
|
||||||
Aus diesen Anfängen ist nach und nach eine umfassende Grafikbibliothek
|
|
||||||
entstanden, die als _Zeichenmaschine_ veröffentlicht wurde.
|
|
||||||
|
|
||||||
### Was die Zeichenmaschine nicht ist
|
|
||||||
|
|
||||||
Die Bibliothek hat nicht den Anspruch, ein Konkurrent zu Processing oder
|
|
||||||
anderen, seit Jahren etablierten und ausgereiften, Grafiksystemen zu sein. Vor
|
|
||||||
allem ist die _Zeichenmaschine_ keine vollwertige Game Engine, die auf die
|
|
||||||
Ausführung komplexer Spiele spezialisiert ist. Für diese Zwecke gibt es genügend
|
|
||||||
Alternativen, von deren Nutzung gar nicht abgeraten werden soll.
|
|
||||||
|
|
||||||
## Aufbau der Zeichenmaschine
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
|
|
||||||
In der Zeichenmaschine werden bewusst nur englischsprachige Bezeichner für
|
|
||||||
Klassen, Methoden und Variablen verwendet. Ausnahme sind einzelne Klassen,
|
|
||||||
die im Zusammnehang mit dem Namen der Bibliothek stehen, wie die
|
|
||||||
Hauptklasse `Zeichenmaschine`.
|
|
||||||
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Installation
|
|
||||||
|
|
||||||
Um ein einfaches Projekt mit der **Zeichenmaschine** aufzusetzen ist nicht mehr
|
|
||||||
nötig, als
|
|
||||||
die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/releases/latest)
|
|
||||||
herunterzuladen und dem *Classpath* des Projekts hinzuzufügen. Beschreibungen
|
|
||||||
für verschiedene Entwicklungsumgebungen sind hier aufgelistet.
|
|
||||||
|
|
||||||
## Integration in Entwicklungsumgebungen
|
|
||||||
|
|
||||||
### BlueJ
|
|
||||||
|
|
||||||
[BlueJ](https://bluej.org) sucht an drei Orten nach Bibliotheken, die für ein
|
|
||||||
Projekt in den Classpath aufgenommen werden:
|
|
||||||
|
|
||||||
- Für ein einzelnes Projekt im Projektordner im Unterordner `+libs`.
|
|
||||||
- Im Reiter "Bibliotheken" der BlueJ-Einstellungen.
|
|
||||||
|
|
||||||
Hier können Programmbibliotheken hinzugefügt werden, die dann allen Projekten
|
|
||||||
zur Verfügung stehen.
|
|
||||||
|
|
||||||
- Für alle Projekte und alle Nutzer dieser BlueJ-Version im
|
|
||||||
Unterordner `userlib` des Programmordners.
|
|
||||||
|
|
||||||
Auf Windows-Systemen ist dieser im Order `lib` des Installationsordners von BlueJ zu finden.
|
|
||||||
|
|
||||||
Auf macos-Systemen muss via Rechtsklick auf die Programmdatei `BlueJ.app` über den Menüpunkt "Paketinhalt zeigen" in den Ordner `Contents/Resources/Java/` navigiert werden.
|
|
||||||
|
|
||||||
### VSCode / VSCodium
|
|
||||||
|
|
||||||
> Coming soon
|
|
||||||
|
|
||||||
### IntelliJ
|
|
||||||
|
|
||||||
> Coming soon
|
|
||||||
|
|
||||||
### Eclipse
|
|
||||||
|
|
||||||
> Coming soon
|
|
||||||
|
|
||||||
### NetBeans
|
|
||||||
|
|
||||||
> Coming soon
|
|
||||||
|
|
||||||
## Unterstützung für MP3
|
|
||||||
128
docs/macros.py
@@ -1,128 +0,0 @@
|
|||||||
import re
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
def define_env(env):
|
|
||||||
@env.macro
|
|
||||||
def javadoc(clazz: str = None, target: str = None) -> str:
|
|
||||||
if not "javadoc_url" in env.variables:
|
|
||||||
return clazz
|
|
||||||
|
|
||||||
if not clazz:
|
|
||||||
return f"{env.variables['javadoc_url'].rstrip('/')}/index.html"
|
|
||||||
else:
|
|
||||||
if "javadoc_default_package" in env.variables and not clazz.startswith(env.variables['javadoc_default_package']):
|
|
||||||
clazz = f"{env.variables['javadoc_default_package'].rstrip('.')}.{clazz}"
|
|
||||||
javadoc_url = env.variables["javadoc_url"].rstrip("/")
|
|
||||||
|
|
||||||
path = list()
|
|
||||||
name = list()
|
|
||||||
|
|
||||||
for p in clazz.split('.'):
|
|
||||||
if p[0].islower():
|
|
||||||
path.append(p)
|
|
||||||
else:
|
|
||||||
name.append(p)
|
|
||||||
|
|
||||||
path = '/'.join(path) + '/' + '.'.join(name) + ".html"
|
|
||||||
if target:
|
|
||||||
path = f"{path}#{target}"
|
|
||||||
return f"{javadoc_url}/{path}"
|
|
||||||
|
|
||||||
@env.macro
|
|
||||||
def jd(cl: str = None, t: str = None) -> str:
|
|
||||||
return javadoc(cl, t)
|
|
||||||
|
|
||||||
@env.macro
|
|
||||||
def javadoc_link(
|
|
||||||
clazz: str = None,
|
|
||||||
target: str = None,
|
|
||||||
strip_package: bool = True,
|
|
||||||
strip_clazz: bool = False,
|
|
||||||
strip_params: bool = True,
|
|
||||||
title: str = None
|
|
||||||
) -> str:
|
|
||||||
name = clazz or "Javadoc"
|
|
||||||
if strip_package:
|
|
||||||
if clazz and clazz.rfind(".") > -1:
|
|
||||||
name = clazz[clazz.rfind(".") + 1 :]
|
|
||||||
if target:
|
|
||||||
# _target = re.sub(r"([^(][^,]*?\.)*?([^)]+)", lambda m: m.group(2), target)
|
|
||||||
_target = target
|
|
||||||
|
|
||||||
if m := re.match(r'^(.+?)\((.*)\)$', _target):
|
|
||||||
if strip_params and m.group(2):
|
|
||||||
params = m.group(2).split(',')
|
|
||||||
for i, param in enumerate(params):
|
|
||||||
dot = param.rfind('.')
|
|
||||||
if dot >= 0:
|
|
||||||
params[i] = param[dot+1:].strip()
|
|
||||||
params = ", ".join(params)
|
|
||||||
_target = f'{m.group(1)}({params})'
|
|
||||||
|
|
||||||
if strip_clazz:
|
|
||||||
name = _target
|
|
||||||
else:
|
|
||||||
name = f"{name}.{_target}"
|
|
||||||
if title:
|
|
||||||
name = title
|
|
||||||
|
|
||||||
return f"[`{name}`]({javadoc(clazz, target)})"
|
|
||||||
|
|
||||||
@env.macro
|
|
||||||
def jdl(
|
|
||||||
cl: str = None,
|
|
||||||
t: str = None,
|
|
||||||
p: bool = False,
|
|
||||||
c: bool = True,
|
|
||||||
title: str = None
|
|
||||||
) -> str:
|
|
||||||
return javadoc_link(cl, t, strip_package=not p, strip_clazz=not c, strip_params=True, title=title)
|
|
||||||
|
|
||||||
@env.macro
|
|
||||||
def jdc(
|
|
||||||
cl: str,
|
|
||||||
p: bool = False
|
|
||||||
) -> str:
|
|
||||||
return javadoc_link(cl, strip_package=not p)
|
|
||||||
|
|
||||||
@env.macro
|
|
||||||
def jdm(
|
|
||||||
cl: str,
|
|
||||||
t: str,
|
|
||||||
p: bool = False,
|
|
||||||
c: bool = False
|
|
||||||
) -> str:
|
|
||||||
return javadoc_link(cl, t, strip_package=not p, strip_clazz=not c)
|
|
||||||
|
|
||||||
@env.macro
|
|
||||||
def javadoc_signature(
|
|
||||||
clazz: str = None,
|
|
||||||
member: str = None,
|
|
||||||
package: str = None,
|
|
||||||
params: List[str] = list(),
|
|
||||||
) -> str:
|
|
||||||
sig = clazz or ""
|
|
||||||
if clazz and package:
|
|
||||||
sig = f"{package}.{sig}"
|
|
||||||
if member:
|
|
||||||
sig = f"{sig}#{member}"
|
|
||||||
|
|
||||||
pparams = ",".join(params)
|
|
||||||
sig = f"{sig}({pparams})"
|
|
||||||
|
|
||||||
return sig
|
|
||||||
|
|
||||||
@env.macro
|
|
||||||
def jds(
|
|
||||||
cl: str = None,
|
|
||||||
m: str = None,
|
|
||||||
pkg: str = None,
|
|
||||||
params: List[str] = list(),
|
|
||||||
) -> str:
|
|
||||||
javadoc_signature(cl, m, pkg, params)
|
|
||||||
|
|
||||||
|
|
||||||
# schule/ngb/zm/Zeichenmaschine.html#setCursor(java.awt.Image,int,int)
|
|
||||||
# schule/ngb/zm/Zeichenmaschine.html#getLayer(java.lang.Class)
|
|
||||||
# schule/ngb/zm/DrawableLayer.html#add(schule.ngb.zm.Drawable...)
|
|
||||||
@@ -1,606 +0,0 @@
|
|||||||
# Schnellstart mit der Zeichenmaschine
|
|
||||||
|
|
||||||
Um die **Zeichenmaschine** in einem Projekt zu nutzen ist nicht mehr nötig, als
|
|
||||||
die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/release/latest)
|
|
||||||
herunterzuladen und
|
|
||||||
dem [Classpath](https://www.delftstack.com/de/howto/java/java-classpath-/)
|
|
||||||
hinzuzufügen. Eine Beschreibung für verschiedene Entwicklungsumgebungen findet
|
|
||||||
sich im Abschnitt [Installation](installation.md).
|
|
||||||
|
|
||||||
## Die Basisklasse
|
|
||||||
|
|
||||||
Eine _Zeichenmaschine_ wird immer als Unterklasse von {{ javadoc_link("
|
|
||||||
schule.ngb.zm.Zeichenmaschine") }} erstellt.
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Die gezeigte Klasse ist schon eine lauffähige Zeichenmaschine und kann gestartet
|
|
||||||
werden.
|
|
||||||
|
|
||||||
!!! note "main Methode"
|
|
||||||
|
|
||||||
Bei einigen Entwicklungsumgebungen muss noch eine `main` Methode erstellt
|
|
||||||
werden, um die Zeichenmaschine zu starten:
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static void main(String[] args) {
|
|
||||||
new Shapes();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Es öffnet sich ein Zeichenfenster in einer vordefinierten Größe. Um die
|
|
||||||
Abmessungen und den Titel des Fensters zu ändern, legen wir einen Konstruktor
|
|
||||||
an.
|
|
||||||
|
|
||||||
???+ example "Quelltext"
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
public Shapes() {
|
|
||||||
super(800, 800, "Shapes");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Starten wir das Projekt, wird eine Zeichenfläche in der Größe 800-mal-800 Pixel
|
|
||||||
erstellt und in einem Fenster mit dem Titel „Shapes“ angezeigt.
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=400 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
### Formen zeichnen
|
|
||||||
|
|
||||||
Eine Zeichenmaschine hat verschiedene Möglichkeiten Inhalte in das
|
|
||||||
Zeichenfenster zu zeichnen. Um ein einfaches statisches Bild zu erzeugen,
|
|
||||||
überschreiben wir die {{ jdl("schule.ngb.zm.Zeichenmaschine", "draw()",
|
|
||||||
c=False) }} Methode.
|
|
||||||
|
|
||||||
???+ example "Quelltext"
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
public Shapes() {
|
|
||||||
super(800, 800, "Shapes");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw() {
|
|
||||||
background.setColor(BLUE);
|
|
||||||
|
|
||||||
drawing.setFillColor(255, 223, 34);
|
|
||||||
drawing.noStroke();
|
|
||||||
drawing.circle(400, 400, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Wir sehen einen gelben Kreis (ohne Konturlinie) auf einem blauen Hintergrund.
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=400 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
### Vorbereitung der Zeichenfläche
|
|
||||||
|
|
||||||
Im Beispiel oben setzen wir die Hintergrundfarbe auf Blau, die Füllfarbe auf
|
|
||||||
Gelb und deaktivieren die Konturlinie. Wenn diese Einstellungen für alle
|
|
||||||
Zeichenobjekte gleich bleiben, können wir sie statt in `draw()` auch in die {{
|
|
||||||
jdl('Zeichenmaschine', 'setup()', c=False) }} Methode schreiben. Diese bereitet
|
|
||||||
die Zeichenfläche vor dem ersten Zeichnen vor.
|
|
||||||
|
|
||||||
???+ example "Quelltext"
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
public Shapes() {
|
|
||||||
super(800, 800, "Shapes");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setup() {
|
|
||||||
background.setColor(BLUE);
|
|
||||||
|
|
||||||
drawing.setFillColor(255, 223, 34);
|
|
||||||
drawing.noStroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw() {
|
|
||||||
for( int i = 0; i < 10; i++ ) {
|
|
||||||
drawing.circle(
|
|
||||||
random(0, canvasWidth),
|
|
||||||
random(0, canvasHeight),
|
|
||||||
random(50, 200)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Im Beispiel setzen wir nun die Grundeinstellungen in der `setup()` Methode. In
|
|
||||||
`draw()` werden zehn gelbe Kreise an Zufallskoordinaten gezeichnet.
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=400 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
!!! tip ""
|
|
||||||
|
|
||||||
Mit {{ jdm("Constants", "canvasWidth") }} und
|
|
||||||
{{ jdm("Constants", "canvasHeight") }} kannst du in der Zeichenmaschine
|
|
||||||
auf die aktuelle Größe der Zeichenfläche zugreifen.
|
|
||||||
{{ jdm("Constants", "random(int,int)") }} erzeugt eine Zufallszahl
|
|
||||||
innerhalb der angegebenen Grenzen.
|
|
||||||
|
|
||||||
## Interaktionen mit der Maus: Whack-a-mole
|
|
||||||
|
|
||||||
Mit der Zeichenmaschine lassen sich Interaktionen mit der Maus leicht umsetzen.
|
|
||||||
Wir wollen das Beispielprogramm zu einem
|
|
||||||
[Whac-A-Mole](https://de.wikipedia.org/wiki/Whac-A-Mole) Spiel erweitern.
|
|
||||||
|
|
||||||
Auf der Zeichenfläche wird nur noch ein gelber Kreis an einer zufälligen Stelle
|
|
||||||
angezeigt. Sobald die Spieler:in auf den Kreis klickt, soll dieser an eine neue
|
|
||||||
Position springen.
|
|
||||||
|
|
||||||
Damit wir den Kreis an eine neue Position springen lassen können, müssen wir
|
|
||||||
zufällige `x`- und `y`-Koordinaten generieren. Dazu erstellen wir zunächst zwei
|
|
||||||
_Objektvariablen_ für die Koordinaten, die in der `setup()` Methode mit
|
|
||||||
zufälligen Werte initialisiert werden. Diese benutzen wir, um die `draw
|
|
||||||
()` Methode anzupassen.
|
|
||||||
|
|
||||||
??? example "Quelltext"
|
|
||||||
|
|
||||||
```Java
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
private int moleRadius = 20;
|
|
||||||
|
|
||||||
private int moleX;
|
|
||||||
|
|
||||||
private int moleY;
|
|
||||||
|
|
||||||
public Shapes() {
|
|
||||||
super(800, 800, "Shapes");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setup() {
|
|
||||||
background.setColor(BLUE);
|
|
||||||
|
|
||||||
drawing.setFillColor(255, 223, 34);
|
|
||||||
drawing.noStroke();
|
|
||||||
|
|
||||||
moleX = random(50, canvasWidth - 50);
|
|
||||||
moleY = random(50, canvasHeight - 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw() {
|
|
||||||
drawing.clear();
|
|
||||||
drawing.circle(moleX, moleY, moleRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=600 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
Als Nächstes prüfen wir bei jedem Mausklick, ob die Mauskoordinaten innerhalb
|
|
||||||
des gelben Kreises (des Maulwurfs) liegen. Die Mauskoordinaten sind jederzeit
|
|
||||||
über die Variablen `mouseX` und `mouseY` abrufbar. Um zu prüfen, ob diese
|
|
||||||
Koordinaten innerhalb des Kreises liegen, vergleichen wir den Abstand zwischen
|
|
||||||
Kreismittelpunkt `(moleX, moleY)` und den Mauskoordinaten
|
|
||||||
`(mouseX, mouseY)` mit dem Radius des Kreises (im Bild grün). Ist die Entfernung
|
|
||||||
kleiner als der Radius (blauer Kreis), wurde innerhalb des Kreises geklickt.
|
|
||||||
Sonst außerhalb (roter Kreis).
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=400 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
Den Abstand vom Mittelpunkt zur Maus lässt sich mithilfe des Satzes des
|
|
||||||
Pythagoras leicht selber berechnen. Die Zeichenmaschine kann uns diese Arbeit
|
|
||||||
aber auch abnehmen und stellt eine Methode dafür bereit
|
|
||||||
({{ jdm("Constants", "distance(double,double,double,double)") }}).
|
|
||||||
|
|
||||||
Um auf einen Mausklick zu reagieren, ergänzen wir die
|
|
||||||
{{ jdm("Zeichenmaschine", "mouseClicked()") }} Methode:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Override
|
|
||||||
public void mouseClicked() {
|
|
||||||
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
|
||||||
moleX = random(50, canvasWidth - 50);
|
|
||||||
moleY = random(50, canvasHeight - 50);
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
??? example "Quelltext"
|
|
||||||
|
|
||||||
```Java
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
private int moleRadius = 20;
|
|
||||||
|
|
||||||
private int moleX;
|
|
||||||
|
|
||||||
private int moleY;
|
|
||||||
|
|
||||||
public Shapes() {
|
|
||||||
super(800, 800, "Shapes");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setup() {
|
|
||||||
background.setColor(BLUE);
|
|
||||||
|
|
||||||
drawing.setFillColor(255, 223, 34);
|
|
||||||
drawing.noStroke();
|
|
||||||
|
|
||||||
moleX = random(50, canvasWidth - 50);
|
|
||||||
moleY = random(50, canvasHeight - 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw() {
|
|
||||||
drawing.clear();
|
|
||||||
drawing.circle(moleX, moleY, moleRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked() {
|
|
||||||
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
|
||||||
moleX = random(50, canvasWidth - 50);
|
|
||||||
moleY = random(50, canvasHeight - 50);
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
Der Aufruf von {{ jdm("Zeichenmaschine", "redraw()") }} zeichnet
|
|
||||||
die Zeichenfläche neu, indem die `draw()` Methode erneut aufgerufen wird.
|
|
||||||
Du solltest `draw()` niemals direkt aufrufen.
|
|
||||||
|
|
||||||
Nun springt der Kreis an eine andere Stelle, wenn er direkt mit der Maus
|
|
||||||
angeklickt wird.
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=400 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
## Ein paar Details zur Zeichenmaschine
|
|
||||||
|
|
||||||
Die _Zeichenmaschine_ wurde stark von der kreativen Programmierumgebung
|
|
||||||
[Processing](https://processing.org) inspiriert. Wenn du Processing schon
|
|
||||||
kennst, dann werden dir einige der Konzepte der _Zeichenmaschine_ schon bekannt
|
|
||||||
vorkommen.
|
|
||||||
|
|
||||||
### Farben
|
|
||||||
|
|
||||||
Farben können auf verschiedene Weisen angegeben werden. Unser Beispiel nutzt
|
|
||||||
bisher zwei Arten:
|
|
||||||
|
|
||||||
1. Die einfachste Möglichkeit sind die _Farbkonstanten_
|
|
||||||
wie {{ jdm('Constants', 'BLUE') }} oder {{ jdm('Constants', 'RED') }}. Im
|
|
||||||
Beispiel setzen wir den Hintergrund auf die Farbe `BLUE`.
|
|
||||||
2. Farben werden häufig im RGB-Farbraum definiert. Dazu wird jeweils der Rot-,
|
|
||||||
Grün- und Blauanteil der Farbe als Wert zwischen 0 und 255 angegeben. Im
|
|
||||||
Beispiel setzen wir die Farbe der Kreise auf `255, 223, 34`, also viel Rot
|
|
||||||
und Grün und nur ein wenig Blau.
|
|
||||||
|
|
||||||
### Ebenen
|
|
||||||
|
|
||||||
Die Zeichenfläche besteht aus einzelnen {{ jdl("Layer", title="Ebenen") }}, die
|
|
||||||
auf übereinander liegen. Bis auf die unterste Ebene sind die Ebenen zunächst
|
|
||||||
durchsichtig, wodurch die Zeichnungen unterer Ebenen durchscheinen.
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=600 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
Eine _Zeichenmaschine_ besitzt zu Beginn drei Ebenen:
|
|
||||||
|
|
||||||
1. Die unterste Ebene ist ein {{ jdc("layers.ColorLayer") }}, die nur aus einer
|
|
||||||
Farbe (oder einem Farbverlauf) besteht und keine durchsichtigen Bereiche
|
|
||||||
besitzt. Im Beispiel setzen wir diese Ebene auf die Farbe `BLUE`.
|
|
||||||
2. Die nächste Ebene ist ein {{ jdc("layers.DrawingLayer") }}, auf die wir
|
|
||||||
unsere Formen zeichnen können. Die Ebene ist zunächst komplett durchsichtig.
|
|
||||||
3. Die oberste Ebene ist ein {{ jdc("layers.ShapesLayer") }}, die zur
|
|
||||||
Darstellung von Form-Objekten der Klasse {{ jdc("shapes.Shape") }} genutzt
|
|
||||||
werden kann.
|
|
||||||
|
|
||||||
Du kannst einer Zeichenfläche aber auch beliebige neue oder selbst programmierte
|
|
||||||
Ebenen hinzufügen.
|
|
||||||
|
|
||||||
### Ablauf
|
|
||||||
|
|
||||||
Die _Zeichenmaschine_ ruft nach dem Start die Methoden in einem festen Ablauf
|
|
||||||
auf.
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=500 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
Erstellst Du eine _Zeichenmaschine_ (beziehungsweise ein Objekt einer
|
|
||||||
Unterklasse), dann wird zuerst die {{ jdm('Zeichenmaschine', 'setup()') }}
|
|
||||||
Methode ausgeführt. Danach folgt einmalig die
|
|
||||||
{{ jdm('Zeichenmaschine', 'draw()') }} Methode und dann endet das Hauptprogramm.
|
|
||||||
|
|
||||||
Die Eingaben der Maus werden in einem parallelen Ablauf (einem _Thread_)
|
|
||||||
abgefangen und daraufhin die {{ jdm('Zeichenmaschine', 'mouseClicked()') }}
|
|
||||||
Methode aufgerufen. In unserem Programm prüfen wir, ob mit der Maus
|
|
||||||
innerhalb des Kreises geklickt wurde und rufen dann
|
|
||||||
{{ jdm('Zeichenmaschine', 'redraw()') }} auf, woraufhin ein weiteres Mal
|
|
||||||
`draw()` ausgeführt wird.
|
|
||||||
|
|
||||||
In _Processing_ wird dies der "statische" Modus genannt, weil das Programm nur
|
|
||||||
einmal abläuft und dann stoppt. "Statisch" trifft es nicht ganz, da das Programm
|
|
||||||
ja zum Beispiel durch Mauseingaben auch verändert werden kann. Wichtig ist aber,
|
|
||||||
dass mit `redraw()` die Zeichenfläche manuell neu gezeichnet werden muss, damit
|
|
||||||
sich der Inhalt ändert.
|
|
||||||
|
|
||||||
## Dynamische Programme
|
|
||||||
|
|
||||||
Wir wollen unser kleines Spiel dynamischer machen, indem die Kreise nur drei
|
|
||||||
Sekunden angezeigt werden und dann von selbst an einen neuen Ort springen.
|
|
||||||
Schafft man es, den Kreis in dieser Zeit anzuklicken, bekommt man einen Punkt.
|
|
||||||
|
|
||||||
Als zusätzliche Herausforderung lassen wir jeden Kreis in den drei Sekunden
|
|
||||||
immer kleiner werden.
|
|
||||||
|
|
||||||
### Die Update-Draw-Schleifen
|
|
||||||
|
|
||||||
{ width=200 align=right }
|
|
||||||
|
|
||||||
Bisher hat die _Zeichenmaschine_ einmalig `draw()` aufgerufen und dann nur noch
|
|
||||||
auf Benutzereingaben mit der Maus reagiert. Nun wollen wir das gezeichnete Bild
|
|
||||||
aber laufend anpassen. Der Kreis soll schrumpfen und nach 3 Sekunden
|
|
||||||
verschwinden.
|
|
||||||
|
|
||||||
Dazu ergänzen wir ein {{ jdl('Zeichenmaschine', 'update(double)', c=False) }}
|
|
||||||
Methode in unserem Programm. Nun schaltet die _Zeichenmaschine_ in einen
|
|
||||||
dynamischen Modus und startet die _Update-Draw-Schleife_. Das beduetet, nach
|
|
||||||
Aufruf von `setup()` wird fortlaufend immer wieder zuerst `update()` und dann
|
|
||||||
`draw()` aufgerufen.
|
|
||||||
|
|
||||||
Jeder Aufruf der `draw()` Methode zeichnet nun die Zeichenfläche neu. Jedes
|
|
||||||
Bild, das gezeichnet wird (auch, wenn es genauso aussieht, wie das davor), nennt
|
|
||||||
man ein _Frame_. Von Videospielen kennst Du vielleicht schon den Begriff "
|
|
||||||
_Frames per second_" (Fps, dt. Bilder pro Sekunde). Er bedeutet, wie oft das
|
|
||||||
Spiel neue Frames zeichnet, oder in der _Zeichenmaschine_, wie oft `draw()` pro
|
|
||||||
Sekunde aufgerufen wird. Normalerweise passiert dies genau 60-mal pro Sekunde.
|
|
||||||
|
|
||||||
### Lebenszeit eines Kreises
|
|
||||||
|
|
||||||
Jeder Kreis soll drei Sekunden zu sehen sein. Daher fügen wir eine neue
|
|
||||||
Objektvariable namens `moleTime` ein, die zunächst auf Drei steht. Da wir auch
|
|
||||||
Bruchteile von Sekunden abziehen wollen, wählen wir als Datentyp `double`:
|
|
||||||
|
|
||||||
```Java
|
|
||||||
private double moleTime=3.0;
|
|
||||||
```
|
|
||||||
|
|
||||||
Der Parameter `delta` der `update()` Methode ist der Zeitraum in Sekunden seit
|
|
||||||
dem letzten Frame. Subtrahieren wir diesen Wert bei jedem Frame von `moleTime`,
|
|
||||||
wird der Wert immer kleiner. Dann müssen wir nur noch prüfen, ob er kleiner Null
|
|
||||||
ist und in dem Fall den Kreis auf eine neue Position springen lassen.
|
|
||||||
|
|
||||||
Die Größe des Kreises passen wir so an, dass der Anteil der vergangenen Zeit die
|
|
||||||
Größe des Kreises bestimmt:
|
|
||||||
|
|
||||||
```Java
|
|
||||||
drawing.circle(moleX,moleY,moleRadius*(moleTime/3.0));
|
|
||||||
```
|
|
||||||
|
|
||||||
??? example "Quelltext"
|
|
||||||
|
|
||||||
```Java
|
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
|
||||||
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
private int moleRadius = 20;
|
|
||||||
|
|
||||||
private int moleX;
|
|
||||||
|
|
||||||
private int moleY;
|
|
||||||
|
|
||||||
private double moleTime;
|
|
||||||
|
|
||||||
public Shapes() {
|
|
||||||
super(800, 800, "Shapes");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setup() {
|
|
||||||
background.setColor(BLUE);
|
|
||||||
|
|
||||||
drawing.setFillColor(255, 223, 34);
|
|
||||||
drawing.noStroke();
|
|
||||||
|
|
||||||
randomizeMole();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void randomizeMole() {
|
|
||||||
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
|
|
||||||
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
|
|
||||||
moleTime = 3.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
moleTime -= delta;
|
|
||||||
|
|
||||||
if( moleTime <= 0 ) {
|
|
||||||
randomizeMole();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw() {
|
|
||||||
drawing.clear();
|
|
||||||
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked() {
|
|
||||||
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
|
||||||
randomizeMole();
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
|
||||||
new Shapes();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=400 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
!!! tip ""
|
|
||||||
|
|
||||||
Der Maulwurf muss mittlerweile an drei verschiedenen Stellen im Programm
|
|
||||||
auf eine zufällige Position gesetzt werden (am Anfang, wenn er angeklickt
|
|
||||||
wurde und wenn die drei Sekunden abgelaufen sind). Daher wurde das Versetzen
|
|
||||||
in eine eigene Methode `randomizeMole()` ausgelagert.
|
|
||||||
|
|
||||||
### Punktezähler
|
|
||||||
|
|
||||||
Zum Schluss wollen wir noch bei jedem Treffer mit der Maus die Punkte zählen und
|
|
||||||
als Text auf die Zeichenfläche schreiben.
|
|
||||||
|
|
||||||
Dazu ergänzen wir eine weitere Objektvariable `score`, die in `mouseClicked()`
|
|
||||||
erhöht wird, falls der Kreis getroffen wurde.
|
|
||||||
|
|
||||||
Um den Punktestand anzuzeigen ergänzen wir in `draw()` einen Aufruf von
|
|
||||||
{{ jdl('layers.DrawingLayer', 'text(java.lang.String,double,double,schule.ngb.zm.Options.Direction)') }}
|
|
||||||
mit dem Inhalt von `score` und den Koordinaten, an denen der Text gezeigt
|
|
||||||
werden soll.
|
|
||||||
Die Zeichenebene zeichnet im Moment alle Formen und Text ausgehend vom Zentrum
|
|
||||||
der Form. Damit der Text 10 Pixel vom Rand entfernt links oben angezeigt wird,
|
|
||||||
können wir der Text Methode (und allen anderen, die etwas zeichnen) eine {{
|
|
||||||
jdl('Options.Direction', title='Richtung') }} übergeben, die festlegt, von
|
|
||||||
welchem Ausgangspunkt (oder _Ankerpunkt_) die Form gezeichnet werden soll.
|
|
||||||
|
|
||||||
```Java
|
|
||||||
drawing.setFillColor(BLACK);
|
|
||||||
drawing.text("Punkte: "+score,10,10,NORTHWEST);
|
|
||||||
```
|
|
||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=400 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
??? example "Quelltext"
|
|
||||||
|
|
||||||
```Java
|
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
|
||||||
|
|
||||||
public class Shapes extends Zeichenmaschine {
|
|
||||||
|
|
||||||
private int moleRadius = 20;
|
|
||||||
|
|
||||||
private int moleX;
|
|
||||||
|
|
||||||
private int moleY;
|
|
||||||
|
|
||||||
private double moleTime;
|
|
||||||
|
|
||||||
private int score = 0;
|
|
||||||
|
|
||||||
public Shapes() {
|
|
||||||
super(800, 800, "Shapes");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setup() {
|
|
||||||
background.setColor(BLUE);
|
|
||||||
|
|
||||||
drawing.noStroke();
|
|
||||||
drawing.setFontSize(24);
|
|
||||||
|
|
||||||
randomizeMole();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void randomizeMole() {
|
|
||||||
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
|
|
||||||
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
|
|
||||||
moleTime = 3.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
moleTime -= delta;
|
|
||||||
|
|
||||||
if( moleTime <= 0 ) {
|
|
||||||
randomizeMole();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw() {
|
|
||||||
drawing.clear();
|
|
||||||
|
|
||||||
drawing.setFillColor(255, 223, 34);
|
|
||||||
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
|
|
||||||
|
|
||||||
drawing.setFillColor(BLACK);
|
|
||||||
drawing.text("Punkte: " + score, 10, 10, NORTHWEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked() {
|
|
||||||
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
|
||||||
score += 1;
|
|
||||||
randomizeMole();
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
|
||||||
new Shapes();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Wie es weitergehen kann
|
|
||||||
|
|
||||||
In diesem Schnellstart-Tutorial hast du die Grundlagen der _Zeichenmaschine_
|
|
||||||
gelernt. Um weiterzumachen, kannst du versuchen, das Whack-a-mole Spiel um diese
|
|
||||||
Funktionen zu erweitern:
|
|
||||||
|
|
||||||
- Mehrere "Maulwürfe" gleichzeitig.
|
|
||||||
- Unterschiedliche Zeiten pro Maulwurf.
|
|
||||||
|
|
||||||
Wenn du mehr über die Möglichkeiten lernen möchtest, die dir die
|
|
||||||
_Zeichenmaschine_ bereitstellt, kannst du dir die weiteren Tutorials in dieser
|
|
||||||
Dokumentation ansehen. Ein guter Startpunkt ist das
|
|
||||||
[Aquarium](tutorials/aquarium/aquarium1.md).
|
|
||||||
|
|
||||||
Viele verschiedene Beispiele, ohne detailliertes Tutorial, findest du in der
|
|
||||||
Kategorie Beispiele und auf GitHub im Repository
|
|
||||||
[jneug/zeichenmaschine-examples](https://github.com/jneug/zeichenmaschine-examples).
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Tutorial: Aquarium
|
|
||||||
|
|
||||||
In diesem Tutorial wollen wir mithilfe der _Zeichenmaschine_ ein (bonbonbuntes)
|
|
||||||
interaktives Aquarium entwickeln. Dabei werden wir in verschiedenen Ausbaustufen
|
|
||||||
zunächst das System Modellieren und dann implementieren.
|
|
||||||
|
|
||||||
!!! info "Mein bonbonbuntes Aquarium"
|
|
||||||
|
|
||||||
Das Projekt [Mein bonbonbuntes Aquarium](http://blog.schockwellenreiter.de/2021/02/2021021201.html)
|
|
||||||
stammt ursprünglich aus dem Blog [Schockwellenreiter](http://blog.schockwellenreiter.de)
|
|
||||||
von [Jörg Kantel](http://cognitiones.kantel-chaos-team.de/cv.html).
|
|
||||||
|
|
||||||
Das Endprodukt soll folgendes umfassen:
|
|
||||||
|
|
||||||
- Darstellung eines hübschen Aquariums mit Fischen, die hin und her schwimmen.
|
|
||||||
- Zur Darstellung wollen wir wie im Original die Sprites aus dem [Fish Pack von Kenny.nl](https://www.kenney.nl/assets/fish-pack) nutzen.
|
|
||||||
- Das Aquarium soll durch passende Geräusche untermalt werden.
|
|
||||||
- Bei einem Klick in das Aquarium soll ein zufälliger Fisch erscheinen.
|
|
||||||
- Bei einem Druck auf die Leertaste soll ein Hai durch das Aquarium schwimmen und alle Fische auf seinem Weg auffressen.
|
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
234
gradlew
vendored
@@ -1,234 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
|
|
||||||
#
|
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
app_path=$0
|
|
||||||
|
|
||||||
# Need this for daisy-chained symlinks.
|
|
||||||
while
|
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
|
||||||
[ -h "$app_path" ]
|
|
||||||
do
|
|
||||||
ls=$( ls -ld "$app_path" )
|
|
||||||
link=${ls#*' -> '}
|
|
||||||
case $link in #(
|
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${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 "$*"
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
# 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 ;; #(
|
|
||||||
MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
|
|
||||||
case $MAX_FD in #(
|
|
||||||
max*)
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
|
||||||
warn "Could not query maximum file descriptor limit"
|
|
||||||
esac
|
|
||||||
case $MAX_FD in #(
|
|
||||||
'' | soft) :;; #(
|
|
||||||
*)
|
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
|
||||||
# * args from the command line
|
|
||||||
# * the main class name
|
|
||||||
# * -classpath
|
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if "$cygwin" || "$msys" ; then
|
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
for arg do
|
|
||||||
if
|
|
||||||
case $arg in #(
|
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
89
gradlew.bat
vendored
@@ -1,89 +0,0 @@
|
|||||||
@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 execute
|
|
||||||
|
|
||||||
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 execute
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
: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 %*
|
|
||||||
|
|
||||||
: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
|
|
||||||
83
mkdocs.yml
@@ -1,83 +0,0 @@
|
|||||||
site_name: Zeichenmaschine.xyz
|
|
||||||
site_description: Eine kleine Java-Bibliothek für grafische Programmierung im Informatikunterricht.
|
|
||||||
site_author: J. Neugebauer
|
|
||||||
repo_url: https://github.com/jneug/zeichenmaschine
|
|
||||||
repo_name: jneug/zeichenmaschine
|
|
||||||
|
|
||||||
site_dir: build/docs/site
|
|
||||||
|
|
||||||
theme:
|
|
||||||
name: material
|
|
||||||
# custom_dir: docs/home_override/
|
|
||||||
language: de
|
|
||||||
logo: assets/icon_64.png
|
|
||||||
favicon: assets/icon_32.png
|
|
||||||
features:
|
|
||||||
- content.code.annotate
|
|
||||||
- navigation.top
|
|
||||||
- navigation.tracking
|
|
||||||
- search.suggest
|
|
||||||
font: false
|
|
||||||
palette:
|
|
||||||
- media: "(prefers-color-scheme: light)"
|
|
||||||
scheme: default
|
|
||||||
primary: blue
|
|
||||||
accent: deep orange
|
|
||||||
toggle:
|
|
||||||
icon: material/weather-sunny
|
|
||||||
name: Dunkles Design aktivieren
|
|
||||||
- media: "(prefers-color-scheme: dark)"
|
|
||||||
scheme: slate
|
|
||||||
primary: blue
|
|
||||||
accent: deep orange
|
|
||||||
toggle:
|
|
||||||
icon: material/weather-night
|
|
||||||
name: Helles Design aktivieren
|
|
||||||
extra_css:
|
|
||||||
- assets/zmstyles.css
|
|
||||||
|
|
||||||
nav:
|
|
||||||
- Einführung: index.md
|
|
||||||
- Schnellstart: schnellstart.md
|
|
||||||
- Installation: installation.md
|
|
||||||
- Tutorials:
|
|
||||||
- Aquarium: tutorials/aquarium/aquarium1.md
|
|
||||||
- Beispiele:
|
|
||||||
- Mondrian: beispiele/mondrian.md
|
|
||||||
|
|
||||||
markdown_extensions:
|
|
||||||
- admonition
|
|
||||||
- attr_list
|
|
||||||
- def_list
|
|
||||||
- footnotes
|
|
||||||
- md_in_html
|
|
||||||
- toc:
|
|
||||||
permalink: true
|
|
||||||
- pymdownx.magiclink
|
|
||||||
- pymdownx.betterem:
|
|
||||||
smart_enable: all
|
|
||||||
- pymdownx.caret
|
|
||||||
- pymdownx.smartsymbols
|
|
||||||
- pymdownx.emoji:
|
|
||||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
|
||||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
|
||||||
- pymdownx.highlight:
|
|
||||||
anchor_linenums: true
|
|
||||||
- pymdownx.inlinehilite
|
|
||||||
- pymdownx.snippets
|
|
||||||
- pymdownx.details
|
|
||||||
- pymdownx.superfences:
|
|
||||||
custom_fences:
|
|
||||||
- name: mermaid
|
|
||||||
class: mermaid
|
|
||||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
|
||||||
plugins:
|
|
||||||
- search:
|
|
||||||
lang: de
|
|
||||||
separator: '[\s\-\.]'
|
|
||||||
- macros:
|
|
||||||
module_name: docs/macros
|
|
||||||
|
|
||||||
extra:
|
|
||||||
javadoc_url: https://zeichenmaschine.xyz/docs/
|
|
||||||
javadoc_default_package: schule.ngb.zm
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
certifi==2022.9.24
|
|
||||||
charset-normalizer==2.1.1
|
|
||||||
click==8.1.3
|
|
||||||
ghp-import==2.1.0
|
|
||||||
idna==3.4
|
|
||||||
Jinja2==3.1.2
|
|
||||||
Markdown==3.3.7
|
|
||||||
MarkupSafe==2.1.1
|
|
||||||
mergedeep==1.3.4
|
|
||||||
mkdocs==1.4.1
|
|
||||||
mkdocs-macros-plugin==0.7.0
|
|
||||||
mkdocs-material==8.5.6
|
|
||||||
mkdocs-material-extensions==1.0.3
|
|
||||||
packaging==21.3
|
|
||||||
Pygments==2.13.0
|
|
||||||
pymdown-extensions==9.6
|
|
||||||
pyparsing==3.0.9
|
|
||||||
python-dateutil==2.8.2
|
|
||||||
PyYAML==6.0
|
|
||||||
pyyaml_env_tag==0.1
|
|
||||||
requests==2.28.1
|
|
||||||
six==1.16.0
|
|
||||||
termcolor==2.0.1
|
|
||||||
urllib3==1.26.12
|
|
||||||
watchdog==2.1.9
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
@@ -1,2 +0,0 @@
|
|||||||
rootProject.name = 'zeichenmaschine'
|
|
||||||
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.MultipleGradientPaint;
|
|
||||||
import java.awt.Paint;
|
|
||||||
import java.awt.Stroke;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs.
|
|
||||||
*
|
|
||||||
* Die Klasse bietet eine Grundlage zur Implementierung eigener Zeichenobjekte,
|
|
||||||
* die eine Füllung und eine Konturlinie haben können.
|
|
||||||
*/
|
|
||||||
public abstract class BasicDrawable extends Constants implements Strokeable, Fillable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob das Objekt gezeichnet werden soll.
|
|
||||||
*/
|
|
||||||
protected boolean visible = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktuelle Farbe der Konturlinie oder {@code null}, wenn das Objekt ohne
|
|
||||||
* Kontur dargestellt werden soll.
|
|
||||||
*/
|
|
||||||
protected schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Dicke der Konturlinie. Wird nicht kleiner als 0.
|
|
||||||
*/
|
|
||||||
protected double strokeWeight = DEFAULT_STROKEWEIGHT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Art der Konturlinie.
|
|
||||||
*/
|
|
||||||
protected Options.StrokeType strokeType = SOLID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Art der Kantenverbindungen von Linien.
|
|
||||||
*/
|
|
||||||
protected Options.StrokeJoin strokeJoin = MITER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
|
||||||
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
|
||||||
* Zeichnen neu erstellt.
|
|
||||||
*/
|
|
||||||
protected Stroke stroke = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die aktuelle Füllfarbe der Form oder {@code null}, wenn das Objekt nicht
|
|
||||||
* gefüllt werden soll.
|
|
||||||
*/
|
|
||||||
protected Color fillColor = DEFAULT_FILLCOLOR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der aktuelle Farbverlauf des Objektes oder {@code null}, wenn es keinen
|
|
||||||
* Farbverlauf besitzt.
|
|
||||||
*/
|
|
||||||
protected MultipleGradientPaint fill = null;
|
|
||||||
|
|
||||||
// TODO: Add TexturePaint fill (https://docs.oracle.com/javase/8/docs//api/java/awt/TexturePaint.html)
|
|
||||||
|
|
||||||
// Implementierung Drawable Interface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob das Objekt angezeigt bzw. gezeichnet werden soll.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn das Objekt angezeigt werden soll,
|
|
||||||
* {@code false} sonst.
|
|
||||||
*/
|
|
||||||
public boolean isVisible() {
|
|
||||||
return visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Versteckt das Objekt.
|
|
||||||
*/
|
|
||||||
public void hide() {
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeigt das Objekt an.
|
|
||||||
*/
|
|
||||||
public void show() {
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Versteckt da Objekt, wenn es derzeit angezeigt wird und zeigt es
|
|
||||||
* andernfalls an.
|
|
||||||
*/
|
|
||||||
public void toggle() {
|
|
||||||
visible = !visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void draw( Graphics2D graphics );
|
|
||||||
|
|
||||||
|
|
||||||
// Implementierung Fillable Interface
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFill( Paint fill ) {
|
|
||||||
if( fill == null ) {
|
|
||||||
this.fill = null;
|
|
||||||
} else if( fill instanceof Color ) {
|
|
||||||
this.fillColor = ((Color) fill);
|
|
||||||
this.fill = null;
|
|
||||||
} else if( fill instanceof MultipleGradientPaint ) {
|
|
||||||
this.fillColor = null;
|
|
||||||
this.fill = (MultipleGradientPaint) fill;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Paint getFill() {
|
|
||||||
if( fill != null ) {
|
|
||||||
return fill;
|
|
||||||
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
|
||||||
return fillColor;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasFillColor() {
|
|
||||||
return fillColor != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasGradient() {
|
|
||||||
return fill != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Color getFillColor() {
|
|
||||||
return fillColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFillColor( Color color ) {
|
|
||||||
fillColor = color;
|
|
||||||
fill = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MultipleGradientPaint getGradient() {
|
|
||||||
return fill;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Implementierung Strokeable Interface
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStroke( Stroke stroke ) {
|
|
||||||
this.stroke = stroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stroke getStroke() {
|
|
||||||
if( stroke == null ) {
|
|
||||||
stroke = Strokeable.createStroke(strokeType, strokeWeight, strokeJoin);
|
|
||||||
}
|
|
||||||
return stroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Color getStrokeColor() {
|
|
||||||
return strokeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStrokeColor( Color color ) {
|
|
||||||
strokeColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getStrokeWeight() {
|
|
||||||
return strokeWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStrokeWeight( double weight ) {
|
|
||||||
strokeWeight = weight;
|
|
||||||
this.stroke = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Options.StrokeType getStrokeType() {
|
|
||||||
return strokeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
|
||||||
strokeType = type;
|
|
||||||
this.stroke = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Options.StrokeJoin getStrokeJoin() {
|
|
||||||
return strokeJoin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStrokeJoin( Options.StrokeJoin join ) {
|
|
||||||
strokeJoin = join;
|
|
||||||
this.stroke = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code Drawable} Objekte können auf eine Zeichenfläche gezeichnet werden. In
|
|
||||||
* der Regel werden sie einmal pro Frame gezeichnet.
|
|
||||||
*/
|
|
||||||
public interface Drawable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob das Objekt derzeit sichtbar ist (also gezeichnet werden
|
|
||||||
* muss).
|
|
||||||
* <p>
|
|
||||||
* Wie mit dieser Information umgegangen wird, ist nicht weiter festgelegt.
|
|
||||||
* In der Regel sollte eine aufrufende Instanz zunächst prüfen, ob das
|
|
||||||
* Objekt aktiv ist, und nur dann{@link #draw(Graphics2D)} aufrufen. Für
|
|
||||||
* implementierende Klassen ist es aber gegebenenfalls auch sinnvoll, bei
|
|
||||||
* Inaktivität den Aufruf von {@code draw(Graphics2D)} schnell abzubrechen:
|
|
||||||
* <pre><code>
|
|
||||||
* void draw( Graphics2D graphics ) {
|
|
||||||
* if( !isVisible() ) {
|
|
||||||
* return;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // Objekt zeichnen..
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn das Objekt sichtbar ist.
|
|
||||||
*/
|
|
||||||
boolean isVisible();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wird aufgerufen, um das Objekt auf die Zeichenfläche <var>graphics</var>
|
|
||||||
* zu zeichnen.
|
|
||||||
* <p>
|
|
||||||
* Das Objekt muss dafür Sorge tragen, dass der Zustand der Zeichenfläche
|
|
||||||
* (Transformationsmatrix, Farbe, ...) erhalten bleibt. Das Objekt sollte
|
|
||||||
* also etwaige Änderungen am Ende des Aufrufs wieder rückgängig machen.
|
|
||||||
*
|
|
||||||
* @param graphics Die Zeichenfläche.
|
|
||||||
*/
|
|
||||||
void draw( Graphics2D graphics );
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Drawable} Klassen, die mit einer Füllung versehen werden können.
|
|
||||||
* <p>
|
|
||||||
* Das {@code Fillable} Interface dient hauptsächlich zur Vereinheitlichung der
|
|
||||||
* API für Füllungen. Durch Implementation wird sichergestellt, dass alle
|
|
||||||
* Objekte, die eine Füllung haben können, dieselben Methoden zur Verfügung
|
|
||||||
* stellen. Wenn eine {@link Shape} eine
|
|
||||||
* {@link Fillable#setFillColor(Color, int)} Methode hat, dann sollte auch eine
|
|
||||||
* {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode anbieten. Im
|
|
||||||
* Einzelfall kann es sinnvoll sein, weitere Methoden für Füllungen zur
|
|
||||||
* Verfügung zu stellen. Allerdings sollte davon nach Möglichkeit zugunsten
|
|
||||||
* einer einheitlichen API abgesehen werden.
|
|
||||||
* <p>
|
|
||||||
* Das Äquivalent für Konturlinien stellt {@link Strokeable} dar.
|
|
||||||
* <p>
|
|
||||||
* Im einfachsten Fall reicht es {@link #setFill(Paint)} und {@link #getFill()}
|
|
||||||
* zu implementieren. Die anderen Methoden besitzen Standardimplementierungen,
|
|
||||||
* die sich auf die beiden Methoden beziehen. Allerdings ist es in vielen Fällen
|
|
||||||
* sinnvoll, einige der Methoden gezielt zu überschreiben, um sie an spezifische
|
|
||||||
* Situationen anzupassen.
|
|
||||||
*/
|
|
||||||
public interface Fillable extends Drawable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung direkt auf das angegebene {@code Paint}-Objekt.
|
|
||||||
*
|
|
||||||
* @param fill Die neue Füllung.
|
|
||||||
*/
|
|
||||||
void setFill( Paint fill );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die aktuell gesetzte Füllung zurück.
|
|
||||||
* <p>
|
|
||||||
* Die Art der Füllung kann anhand der Abfragen {@link #hasFillColor()} und
|
|
||||||
* {@link #hasGradient()} ermittelt werden.
|
|
||||||
*
|
|
||||||
* @return Die aktuelle Füllung.
|
|
||||||
*/
|
|
||||||
Paint getFill();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob aktuell eine sichtbare Füllung konfiguriert ist.
|
|
||||||
* <p>
|
|
||||||
* Eine Füllung gilt als sichtbar, wenn eine nciht transparente Füllfarbe
|
|
||||||
* oder ein Farbverlauf eingestellt ist.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn die Füllung sichtbar ist, {@code false} sonst.
|
|
||||||
*/
|
|
||||||
default boolean hasFill() {
|
|
||||||
return (hasFillColor() && getFillColor().getAlpha() > 0) || hasGradient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob eine Füllfarbe konfiguriert ist.
|
|
||||||
* <p>
|
|
||||||
* Im Gegensatz zu {@link #hasFill()} prüft die Methode <em>nicht</em>, ob
|
|
||||||
* die Füllfarbe transparent ist.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn eine Füllfarbe gesetzt ist.
|
|
||||||
*/
|
|
||||||
default boolean hasFillColor() {
|
|
||||||
Paint fill = getFill();
|
|
||||||
return fill instanceof Color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob ein Farbverlauf konfiguriert ist.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn ein Farbverlauf gesetzt ist.
|
|
||||||
*/
|
|
||||||
default boolean hasGradient() {
|
|
||||||
Paint fill = getFill();
|
|
||||||
return fill instanceof MultipleGradientPaint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die aktuelle Füllfarbe der Form zurück.
|
|
||||||
*
|
|
||||||
* @return Die aktuelle Füllfarbe oder {@code null}.
|
|
||||||
*/
|
|
||||||
default Color getFillColor() {
|
|
||||||
if( hasFillColor() ) {
|
|
||||||
return (Color) getFill();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die angegebene Farbe.
|
|
||||||
*
|
|
||||||
* @param color Die neue Füllfarbe oder {@code null}.
|
|
||||||
* @see Color
|
|
||||||
*/
|
|
||||||
default void setFillColor( Color color ) {
|
|
||||||
setFill(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die angegebene Farbe und setzt die Transparenz
|
|
||||||
* auf den angegebenen Wert. 0 is komplett durchsichtig und 255 komplett
|
|
||||||
* deckend.
|
|
||||||
*
|
|
||||||
* @param color Die neue Füllfarbe oder {@code null}.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(Color, int)
|
|
||||||
*/
|
|
||||||
default void setFillColor( Color color, int alpha ) {
|
|
||||||
setFillColor(new Color(color, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität. 0
|
|
||||||
* entspricht schwarz, 255 entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int)
|
|
||||||
*/
|
|
||||||
default void setFillColor( int gray ) {
|
|
||||||
setFillColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf einen Grauwert mit der angegebenen Intensität und
|
|
||||||
* dem angegebenen Transparenzwert. Der Grauwert 0 entspricht schwarz, 255
|
|
||||||
* entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int)
|
|
||||||
*/
|
|
||||||
default void setFillColor( int gray, int alpha ) {
|
|
||||||
setFillColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
|
||||||
* Blauanteilen.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
default void setFillColor( int red, int green, int blue ) {
|
|
||||||
setFillColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf die Farbe mit den angegebenen Rot-, Grün- und
|
|
||||||
* Blauanteilen und dem angegebenen Transparenzwert.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
|
||||||
* @see Color#Color(int, int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
default void setFillColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setFillColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt die Füllung der Form.
|
|
||||||
*/
|
|
||||||
default void noFill() {
|
|
||||||
setFillColor(null);
|
|
||||||
noGradient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllfarbe auf den Standardwert zurück.
|
|
||||||
*
|
|
||||||
* @see schule.ngb.zm.Constants#DEFAULT_FILLCOLOR
|
|
||||||
*/
|
|
||||||
default void resetFill() {
|
|
||||||
setFillColor(Constants.DEFAULT_FILLCOLOR);
|
|
||||||
noGradient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt den aktuellen Farbverlauf der Form zurück.
|
|
||||||
*
|
|
||||||
* @return Der aktuelle Farbverlauf oder {@code null}.
|
|
||||||
*/
|
|
||||||
default MultipleGradientPaint getGradient() {
|
|
||||||
if( hasGradient() ) {
|
|
||||||
return (MultipleGradientPaint) getFill();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung auf einen linearen Farbverlauf, der am Punkt
|
|
||||||
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
|
||||||
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
|
||||||
*
|
|
||||||
* @param fromX x-Koordinate des Startpunktes.
|
|
||||||
* @param fromY y-Koordinate des Startpunktes.
|
|
||||||
* @param from Farbe am Startpunkt.
|
|
||||||
* @param toX x-Koordinate des Endpunktes.
|
|
||||||
* @param toY y-Koordinate des Endpunktes.
|
|
||||||
* @param to Farbe am Endpunkt.
|
|
||||||
*/
|
|
||||||
default void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
|
||||||
setFill(new LinearGradientPaint(
|
|
||||||
(float) fromX, (float) fromY,
|
|
||||||
(float) toX, (float) toY,
|
|
||||||
new float[]{0f, 1f},
|
|
||||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung auf einen linearen Farbverlauf, der in die angegebene
|
|
||||||
* Richtung verläuft.
|
|
||||||
*
|
|
||||||
* @param from Farbe am Startpunkt.
|
|
||||||
* @param to Farbe am Endpunkt.
|
|
||||||
* @param dir Richtung des Farbverlaufs.
|
|
||||||
*/
|
|
||||||
default void setGradient( Color from, Color to, Options.Direction dir ) {
|
|
||||||
int whalf = (Constants.canvasWidth / 2);
|
|
||||||
int hhalf = (Constants.canvasHeight / 2);
|
|
||||||
setGradient(whalf - dir.x * whalf, hhalf - dir.y * hhalf, from, whalf + dir.x * whalf, hhalf + dir.y * hhalf, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, mit dem
|
|
||||||
* Zentrum im Punkt ({@code centerX}, {@code centerY}) und dem angegebenen
|
|
||||||
* Radius. Der Verlauf starte im Zentrum mit der Farbe {@code from} und
|
|
||||||
* endet am Rand des durch den Radius beschriebenen Kreises mit der Farbe
|
|
||||||
* {@code to}.
|
|
||||||
*
|
|
||||||
* @param centerX x-Koordinate des Kreismittelpunktes.
|
|
||||||
* @param centerY y-Koordinate des Kreismittelpunktes.
|
|
||||||
* @param radius Radius des Kreises.
|
|
||||||
* @param from Farbe im Zentrum des Kreises.
|
|
||||||
* @param to Farbe am Rand des Kreises.
|
|
||||||
*/
|
|
||||||
default void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
|
||||||
setFill(new RadialGradientPaint(
|
|
||||||
(float) centerX, (float) centerY, (float) radius,
|
|
||||||
new float[]{0f, 1f},
|
|
||||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung auf einen kreisförmigen (radialen) Farbverlauf, der im
|
|
||||||
* Zentrum beginnt.
|
|
||||||
*
|
|
||||||
* @param from Farbe im Zentrum.
|
|
||||||
* @param to Farbe am Rand.
|
|
||||||
*/
|
|
||||||
default void setGradient( Color from, Color to ) {
|
|
||||||
int whalf = (Constants.canvasWidth / 2);
|
|
||||||
int hhalf = (Constants.canvasHeight / 2);
|
|
||||||
setGradient(whalf, hhalf, Math.min(whalf, hhalf), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt den Farbverlauf von der Form.
|
|
||||||
*/
|
|
||||||
default void noGradient() {
|
|
||||||
setFill(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import java.awt.AlphaComposite;
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.RenderingHints;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basisklasse für Ebenen der {@link Zeichenleinwand}.
|
|
||||||
* <p>
|
|
||||||
* Die {@code Zeichenleinwand} besteht aus einer Reihe von Ebenen, die
|
|
||||||
* übereinandergelegt und von "unten" nach "oben" gezeichnet werden. Die Inhalte
|
|
||||||
* der oberen Ebenen können also Inhalte der darunterliegenden verdecken.
|
|
||||||
* <p>
|
|
||||||
* Ebenen sind ein zentraler Bestandteil bei der Implementierung einer
|
|
||||||
* {@link Zeichenmaschine}. Sie erben von {@code Constants}, da neue Ebenentypen
|
|
||||||
* von Nutzern implementiert werden können.
|
|
||||||
*/
|
|
||||||
public abstract class Layer extends Constants implements Drawable, Updatable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interner Puffer für die Ebene, der einmal pro Frame auf die
|
|
||||||
* Zeichenleinwand übertragen wird.
|
|
||||||
*/
|
|
||||||
protected BufferedImage buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der Grafikkontext der Ebene, der zum Zeichnen der Inhalte verwendet
|
|
||||||
* wird.
|
|
||||||
*/
|
|
||||||
protected Graphics2D drawing;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Ebene derzeit sichtbar ist.
|
|
||||||
*/
|
|
||||||
protected boolean visible = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Ebene aktiv ist, also {@link #update(double) Updates} empfangen
|
|
||||||
* soll.
|
|
||||||
*/
|
|
||||||
protected boolean active = true;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Ebene mit den Standardmaßen.
|
|
||||||
*/
|
|
||||||
public Layer() {
|
|
||||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Ebene mit den angegebenen Maßen.
|
|
||||||
*
|
|
||||||
* @param width Die Breite der Ebene.
|
|
||||||
* @param height Die Höhe der Ebene.
|
|
||||||
*/
|
|
||||||
public Layer( int width, int height ) {
|
|
||||||
createCanvas(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die Breite der Ebene.
|
|
||||||
*/
|
|
||||||
public int getWidth() {
|
|
||||||
return buffer.getWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die Höhe der Ebene.
|
|
||||||
*/
|
|
||||||
public int getHeight() {
|
|
||||||
return buffer.getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ändert die Größe der Ebene auf die angegebenen Maße.
|
|
||||||
*
|
|
||||||
* @param width Die neue Breite.
|
|
||||||
* @param height Die neue Höhe.
|
|
||||||
*/
|
|
||||||
public void setSize( int width, int height ) {
|
|
||||||
if( buffer != null ) {
|
|
||||||
if( buffer.getWidth() != width || buffer.getHeight() != height ) {
|
|
||||||
recreateCanvas(width, height);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
createCanvas(width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: prevent access to graphics context?
|
|
||||||
public Graphics2D getGraphics() {
|
|
||||||
return this.drawing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Ressourcen der Ebene frei.
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
|
||||||
drawing.dispose();
|
|
||||||
drawing = null;
|
|
||||||
buffer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen Puffer für die Ebene und konfiguriert diesen.
|
|
||||||
*
|
|
||||||
* @param width Breite des neuen Puffers.
|
|
||||||
* @param height Höhe des neuen Puffers.
|
|
||||||
*/
|
|
||||||
private void createCanvas( int width, int height ) {
|
|
||||||
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
drawing = buffer.createGraphics();
|
|
||||||
|
|
||||||
// add antialiasing
|
|
||||||
RenderingHints hints = new RenderingHints(
|
|
||||||
RenderingHints.KEY_ANTIALIASING,
|
|
||||||
RenderingHints.VALUE_ANTIALIAS_ON
|
|
||||||
);
|
|
||||||
hints.put(
|
|
||||||
RenderingHints.KEY_TEXT_ANTIALIASING,
|
|
||||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON
|
|
||||||
);
|
|
||||||
hints.put(
|
|
||||||
RenderingHints.KEY_RENDERING,
|
|
||||||
RenderingHints.VALUE_RENDER_QUALITY
|
|
||||||
);
|
|
||||||
drawing.addRenderingHints(hints);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen Puffer für die Ebene mit der angegebenen Größe und
|
|
||||||
* kopiert den Inhalt des alten Puffers in den Neuen.
|
|
||||||
*
|
|
||||||
* @param width Breite des neuen Puffers.
|
|
||||||
* @param height Höhe des neuen Puffers.
|
|
||||||
*/
|
|
||||||
private void recreateCanvas( int width, int height ) {
|
|
||||||
BufferedImage oldCanvas = buffer;
|
|
||||||
createCanvas(width, height);
|
|
||||||
drawing.drawImage(oldCanvas, 0, 0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Leert die Ebene und löscht alles bisher gezeichnete. Alle Pixel der Ebene
|
|
||||||
* werden transparent, damit unterliegende Ebenen durchscheinen können.
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
// https://stackoverflow.com/questions/31149206/set-pixels-of-bufferedimage-as-transparent
|
|
||||||
Color current = drawing.getColor();
|
|
||||||
drawing.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
|
|
||||||
drawing.setColor(new Color(255, 255, 255, 255));
|
|
||||||
drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
|
|
||||||
drawing.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
|
||||||
drawing.setColor(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeichnet den Puffer auf den Grafikkontext.
|
|
||||||
*
|
|
||||||
* @param graphics Der Grafikkontext, auf den gezeichnet wird.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void draw( Graphics2D graphics ) {
|
|
||||||
if( visible ) {
|
|
||||||
graphics.drawImage(buffer, 0, 0, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isVisible() {
|
|
||||||
return visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Versteckt die Ebene.
|
|
||||||
*/
|
|
||||||
public void hide() {
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeigt die Ebene an, falls sie versteckt war.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public void show() {
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Versteckt oder zeigt die Ebene, je nachdem, welchen Zustand sie derzeit
|
|
||||||
* hat.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public void toggle() {
|
|
||||||
visible = !visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActive() {
|
|
||||||
return active;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob die angegebenen Koordinaten innerhalb der Ebene liegen, oder
|
|
||||||
* nicht.
|
|
||||||
* <p>
|
|
||||||
* Eine Koordinate liegt in der Ebene, wenn die {@code x}- und
|
|
||||||
* {@code y}-Koordinaten größer oder gleich Null und kleiner als die Breite
|
|
||||||
* bzw. Höhe der Ebene sind.
|
|
||||||
*
|
|
||||||
* @param x Die x-Koordinate.
|
|
||||||
* @param y Die y-Koordinate.
|
|
||||||
* @return {@code true}, wenn die Koordinaten innerhalb der Ebene liegen,
|
|
||||||
* {@code false}, wenn sie außerhalb liegen.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public boolean isInBounds( int x, int y ) {
|
|
||||||
return (x >= 0 && y >= 0 && x < getWidth() && y < getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,365 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
|
||||||
import java.awt.geom.Arc2D;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Diese Klasse sammelt Enumerationen, die verschiedene Eigenschaften der zu
|
|
||||||
* zeichnenden Formen darstellen.
|
|
||||||
*/
|
|
||||||
public final class Options {
|
|
||||||
|
|
||||||
private Options() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linienstile für Konturlinien.
|
|
||||||
*/
|
|
||||||
public enum StrokeType {
|
|
||||||
/**
|
|
||||||
* Durchgezogene Linien.
|
|
||||||
*/
|
|
||||||
SOLID,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getrichelte Linien.
|
|
||||||
*/
|
|
||||||
DASHED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gepunktete Linien.
|
|
||||||
*/
|
|
||||||
DOTTED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linienstile für Konturlinien.
|
|
||||||
*/
|
|
||||||
public enum StrokeJoin {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abgerundete Verbindungen.
|
|
||||||
*/
|
|
||||||
ROUND(BasicStroke.JOIN_ROUND),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abgeschnittene Verbindungen.
|
|
||||||
*/
|
|
||||||
BEVEL(BasicStroke.JOIN_BEVEL),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eckige Verbindungen.
|
|
||||||
*/
|
|
||||||
MITER(BasicStroke.JOIN_MITER);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der entsprechende Wert der Konstanten in {@link java.awt}
|
|
||||||
*/
|
|
||||||
public final int awt_type;
|
|
||||||
|
|
||||||
StrokeJoin( int type ) {
|
|
||||||
awt_type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stile für Pfeilspitzen.
|
|
||||||
*/
|
|
||||||
public enum ArrowHead {
|
|
||||||
/**
|
|
||||||
* Einfache Pfeilspitze aus zwei Linien.
|
|
||||||
*/
|
|
||||||
LINES,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gefülltes Dreieck.
|
|
||||||
*/
|
|
||||||
FILLED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Arten von Bögen.
|
|
||||||
* <p>
|
|
||||||
* Die Werte legen fest, wie Bögen geschlossen werden sollen, wenn sie
|
|
||||||
* beispielsweise gefüllt werden.
|
|
||||||
* <p>
|
|
||||||
* Wrapper für die AWT-Konstanten in {@link Arc2D}.
|
|
||||||
*/
|
|
||||||
public enum PathType {
|
|
||||||
/**
|
|
||||||
* Offener Pfad, bei dem die Pfadenden direkt miteinander verbunden
|
|
||||||
* werden ohne eine Linie zu ziehen.
|
|
||||||
*/
|
|
||||||
OPEN(Arc2D.OPEN),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Geschlossener Pfad, bei dem die Pfadenden direkt miteinander
|
|
||||||
* verbunden werden, indem eine Linie gezogen wird.
|
|
||||||
*/
|
|
||||||
CLOSED(Arc2D.CHORD),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Geschlossener Pfad, bei dem Linien von den Pfadenden zum Mittelpunkt
|
|
||||||
* des Kreises, der den Kreisbogen festlegt, gezogen werden.
|
|
||||||
*/
|
|
||||||
PIE(Arc2D.PIE);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der entsprechende Wert der Konstanten in {@link Arc2D}
|
|
||||||
*/
|
|
||||||
public final int awt_type;
|
|
||||||
|
|
||||||
PathType( int type ) {
|
|
||||||
awt_type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zustände in denen sich die Zeichenmaschine befinden kann.
|
|
||||||
*/
|
|
||||||
public enum AppState {
|
|
||||||
/**
|
|
||||||
* Die Zeichenmaschine befindet sich in der Initialisierung. Die
|
|
||||||
* Laufzeitumgebung wird konfiguriert und alle nötigen Komonenten
|
|
||||||
* ({@link Zeichenfenster}, {@link Zeichenleinwand}, ...) werden
|
|
||||||
* erstellt.
|
|
||||||
*/
|
|
||||||
INITIALIZING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Initialisierung der Zeichenmaschine ist beendet, aber der
|
|
||||||
* {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
|
||||||
* wurde noch nicht gestartet.
|
|
||||||
*/
|
|
||||||
INITIALIZED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Zeichenmaschine wurde gestartet und der
|
|
||||||
* {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
|
||||||
* arbeitet.
|
|
||||||
*/
|
|
||||||
RUNNING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
|
||||||
* wurde pausiert.
|
|
||||||
*/
|
|
||||||
PAUSED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
|
||||||
* wurde gestoppt, die Zeichenmaschine ist aber noch nicht vollständig
|
|
||||||
* heruntergefahren und hat noch nicht alle Ressourcen freigegeben.
|
|
||||||
*/
|
|
||||||
STOPPED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
|
||||||
* ist beendet.
|
|
||||||
*/
|
|
||||||
TERMINATED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Zeichenmaschine ist dabei, vollständig herunterzufahren und alle
|
|
||||||
* Ressourcen freizugeben.
|
|
||||||
*/
|
|
||||||
QUITING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der {@link schule.ngb.zm.Zeichenmaschine.Zeichenthread Zeichenthread}
|
|
||||||
* wartet gerade auf den nächsten Frame.
|
|
||||||
*/
|
|
||||||
IDLE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Zeichenmaschine führt gerade
|
|
||||||
* {@link Zeichenmaschine#update(double)} aus.
|
|
||||||
*/
|
|
||||||
UPDATING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Zeichenmaschine führt gerade {@link Zeichenmaschine#draw()} aus.
|
|
||||||
*/
|
|
||||||
DRAWING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Ausführung der Zeichenmaschine wurde mit
|
|
||||||
* {@link Zeichenmaschine#delay(int)} verzögert und wartet auf
|
|
||||||
* Fortsetzung.
|
|
||||||
*/
|
|
||||||
DELAYED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Zeichenmaschine sendet gereade gesammelte Events und führt Tasks
|
|
||||||
* aus.
|
|
||||||
*/
|
|
||||||
DISPATCHING
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Richtungen für die Ausrichtung von Formen. Richtungen sind durch
|
|
||||||
* Einheitsvektoren bzw. deren Kombination im Koordinatensystem der
|
|
||||||
* Zeichenfläche repräsentiert, wodurch mit ihnen gerechnet werden kann. Die
|
|
||||||
* Richtung {@link #DOWN} ist beispielsweise gleich {@code (1, 0)}.
|
|
||||||
* <p>
|
|
||||||
* Jede Richtung ist zusätzlich als Himmelsrichtung definiert. {@link #EAST}
|
|
||||||
* ist äquivalent zu {@link #RIGHT} als {@code (0, 1)} definiert. Auch wenn
|
|
||||||
* beide Werte dieselbe Richtung beschreiben sind sie nicht "gleich"
|
|
||||||
* ({@code EAST != RIGHT}). Um verschiedene Richtungen zuverlässig zu
|
|
||||||
* vergleichen, sollte daher {@link #equals(Direction)} verwendet werden.
|
|
||||||
* <p>
|
|
||||||
* Für zusammengesetzten Richtungen wie {@link #NORTHEAST} bzw
|
|
||||||
* {@link #UPRIGHT} lassen sich mit {@link #in(Direction)} und
|
|
||||||
* {@link #contains(Direction)} Beziehungen zu den anderen Richtungen
|
|
||||||
* prüfen. Beispielsweise ist {@code NORTHEAST.contains(NORTH)} wahr.
|
|
||||||
*/
|
|
||||||
public enum Direction {
|
|
||||||
CENTER(0, 0),
|
|
||||||
|
|
||||||
NORTH(0, -1),
|
|
||||||
EAST(1, 0),
|
|
||||||
SOUTH(0, 1),
|
|
||||||
WEST(-1, 0),
|
|
||||||
|
|
||||||
NORTHEAST(1, -1),
|
|
||||||
SOUTHEAST(1, 1),
|
|
||||||
SOUTHWEST(-1, 1),
|
|
||||||
NORTHWEST(-1, -1),
|
|
||||||
|
|
||||||
MIDDLE(CENTER),
|
|
||||||
UP(NORTH),
|
|
||||||
RIGHT(EAST),
|
|
||||||
DOWN(SOUTH),
|
|
||||||
LEFT(WEST),
|
|
||||||
|
|
||||||
UPLEFT(NORTHWEST),
|
|
||||||
DOWNLEFT(SOUTHWEST),
|
|
||||||
DOWNRIGHT(SOUTHEAST),
|
|
||||||
UPRIGHT(NORTHEAST);
|
|
||||||
|
|
||||||
public final byte x, y;
|
|
||||||
|
|
||||||
Direction( int x, int y ) {
|
|
||||||
this.x = (byte) x;
|
|
||||||
this.y = (byte) y;
|
|
||||||
}
|
|
||||||
|
|
||||||
Direction( Direction original ) {
|
|
||||||
this.x = original.x;
|
|
||||||
this.y = original.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob die angegebene Richtung gleich dieser ist. Dabei werden die
|
|
||||||
* Komponenten des Richtungsvektors geprüft. Daher sind für die Methode
|
|
||||||
* beispielsweise {@link #NORTH} und {@link #UP} gleich.
|
|
||||||
*
|
|
||||||
* @param dir Eine andere Richtung.
|
|
||||||
* @return {@code true}, wenn die Richtungen dieselben Komponenten
|
|
||||||
* haben, {@code false} sonst.
|
|
||||||
*/
|
|
||||||
public boolean equals( Direction dir ) {
|
|
||||||
return this.x == dir.x && this.y == dir.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob diese Richtung Tile der angegebenen Richtung ist.
|
|
||||||
* <p>
|
|
||||||
* Beispielsweise ist {@link #NORTH} Teil von {@link #NORTHWEST}, aber
|
|
||||||
* nicht von {@link #SOUTHWEST}. Dabei wird doe Art der Richtung nicht
|
|
||||||
* beachtet. {@link #UP} ist daher auch Teil von {@link #NORTHWEST}.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* NORTH.in(NORTHWEST) // true
|
|
||||||
* NORTH.in(SOUTHWEST) // false
|
|
||||||
* UP.in(NORTHWEST) // true
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param dir Eine andere Richtung.
|
|
||||||
* @return {@code true}, wenn diese Richtungen Teil der anderen ist,
|
|
||||||
* {@code false} sonst.
|
|
||||||
*/
|
|
||||||
public boolean in( Direction dir ) {
|
|
||||||
return (this.x == dir.x && this.y == 0) || (this.y == dir.y && this.x == 0) || (this.x == dir.x && this.y == dir.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob die angegebene Richtung Teil dieser Richtung ist.
|
|
||||||
* <p>
|
|
||||||
* Beispielsweise ist {@link #NORTH} Teil von {@link #NORTHWEST}, aber
|
|
||||||
* nicht von {@link #SOUTHWEST}. Dabei wird die Art der Richtung nicht
|
|
||||||
* beachtet. {@link #UP} ist daher auch Teil von {@link #NORTHWEST}.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* NORTHWEST.contains(NORTH) // true
|
|
||||||
* SOUTHWEST.in(NORTH) // false
|
|
||||||
* NORTHWEST.in(UP) // true
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param dir Eine andere Richtung.
|
|
||||||
* @return {@code true}, wenn diese Richtungen Teil der anderen ist,
|
|
||||||
* {@code false} sonst.
|
|
||||||
*/
|
|
||||||
public boolean contains( Direction dir ) {
|
|
||||||
return dir.in(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Diese Richtung als Vektor-Objekt.
|
|
||||||
*/
|
|
||||||
public Vector asVector() {
|
|
||||||
return new Vector(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die entgegengesetzte Richtung zu dieser.
|
|
||||||
* <p>
|
|
||||||
* Es wird die Art der Richtung berücksichtigt. Das bedeutet, das
|
|
||||||
* Inverse von {@link #UP} ist {@link #DOWN}, während das Inverse von
|
|
||||||
* {@link #NORTH} zu {@link #SOUTH} wird.
|
|
||||||
*
|
|
||||||
* @return Die entgegengesetzte Richtung zu dieser.
|
|
||||||
*/
|
|
||||||
public Direction inverse() {
|
|
||||||
switch( this ) {
|
|
||||||
case UP:
|
|
||||||
return DOWN;
|
|
||||||
case DOWN:
|
|
||||||
return UP;
|
|
||||||
case LEFT:
|
|
||||||
return RIGHT;
|
|
||||||
case RIGHT:
|
|
||||||
return LEFT;
|
|
||||||
case UPLEFT:
|
|
||||||
return DOWNRIGHT;
|
|
||||||
case UPRIGHT:
|
|
||||||
return DOWNLEFT;
|
|
||||||
case DOWNLEFT:
|
|
||||||
return UPRIGHT;
|
|
||||||
case DOWNRIGHT:
|
|
||||||
return UPLEFT;
|
|
||||||
case MIDDLE:
|
|
||||||
return MIDDLE;
|
|
||||||
case NORTH:
|
|
||||||
return SOUTH;
|
|
||||||
case SOUTH:
|
|
||||||
return NORTH;
|
|
||||||
case EAST:
|
|
||||||
return WEST;
|
|
||||||
case WEST:
|
|
||||||
return EAST;
|
|
||||||
case SOUTHWEST:
|
|
||||||
return NORTHEAST;
|
|
||||||
case SOUTHEAST:
|
|
||||||
return NORTHWEST;
|
|
||||||
case NORTHEAST:
|
|
||||||
return SOUTHWEST;
|
|
||||||
case NORTHWEST:
|
|
||||||
return SOUTHEAST;
|
|
||||||
default:
|
|
||||||
return CENTER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
|
||||||
import java.awt.Stroke;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Drawable} Klassen, die mit einer Konturlinie versehen werden können.
|
|
||||||
* <p>
|
|
||||||
* Das {@code Strokeable} Interface dient hauptsächlich zur Vereinheitlichung
|
|
||||||
* der API für Konturlinien. Durch Implementation wird sichergestellt, dass alle
|
|
||||||
* Objekte, die eine Konturlinie haben können, dieselben Methoden zur Verfügung
|
|
||||||
* stellen. Wenn eine {@link Shape} eine
|
|
||||||
* {@link Strokeable#setStrokeColor(Color, int)} Methode hat, dann sollte auch
|
|
||||||
* eine {@link schule.ngb.zm.layers.TurtleLayer.Turtle} dieselbe Methode
|
|
||||||
* anbieten. Im Einzelfall kann es sinnvoll sein, weitere Methoden für
|
|
||||||
* Konturlinien zur verfügung zu stellen. Allerdings sollte davon nach
|
|
||||||
* Möglichkeit zugunsten einer einheitlichen API abgesehen werden.
|
|
||||||
* <p>
|
|
||||||
* Das Äquivalent für Füllungen stellt {@link Fillable} dar.
|
|
||||||
*/
|
|
||||||
public interface Strokeable extends Drawable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den {@code Stroke} für die Konturlinie direkt.
|
|
||||||
*
|
|
||||||
* @param stroke Ein {@code Stroke}-Objekt.
|
|
||||||
*/
|
|
||||||
void setStroke( Stroke stroke );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt ein {@code Stroke}-Objekt mit den aktuell gesetzten Eigenschaften
|
|
||||||
* zurück.
|
|
||||||
*
|
|
||||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
|
||||||
*/
|
|
||||||
Stroke getStroke();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob die aktuell gesetzten Eigenschaften eine sichtbare
|
|
||||||
* Konturlinie erzeugen.
|
|
||||||
* <p>
|
|
||||||
* Die Konturlinie gilt als sichtbar, wenn sie eine nicht transparente Farbe
|
|
||||||
* und eine Dicke größer 0 besitzt.
|
|
||||||
* <p>
|
|
||||||
* Das bedeutet, falls die Methode {@code false} zurückgibt, dann kann
|
|
||||||
* {@link #getStroke()} trotzdem ein gültiges {@link Stroke}-Objekt
|
|
||||||
* zurückgeben, beispielsweise wenn keine Farbe gesetzt wurde.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn die Konturlinie sichtbar ist, {@code false}
|
|
||||||
* sonst.
|
|
||||||
*/
|
|
||||||
default boolean hasStroke() {
|
|
||||||
Color strokeColor = getStrokeColor();
|
|
||||||
double strokeWeight = getStrokeWeight();
|
|
||||||
return strokeColor != null && strokeColor.getAlpha() > 0 && strokeWeight > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die aktuelle Farbe der Konturlinie zurück.
|
|
||||||
*
|
|
||||||
* @return Die Konturfarbe oder {@code null}.
|
|
||||||
*/
|
|
||||||
Color getStrokeColor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe.
|
|
||||||
*
|
|
||||||
* @param color Die neue Farbe der Konturlinie.
|
|
||||||
* @see Color
|
|
||||||
*/
|
|
||||||
void setStrokeColor( Color color );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Konturlinie auf die angegebene Farbe und setzt die
|
|
||||||
* Transparenz auf den angegebenen Wert. 0 is komplett durchsichtig und 255
|
|
||||||
* komplett deckend.
|
|
||||||
*
|
|
||||||
* @param color Die neue Farbe der Konturlinie oder {@code null}.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(Color, int)
|
|
||||||
*/
|
|
||||||
default void setStrokeColor( Color color, int alpha ) {
|
|
||||||
setStrokeColor(new Color(color, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Konturlinie auf einen Grauwert mit der angegebenen
|
|
||||||
* Intensität. 0 entspricht schwarz, 255 entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int)
|
|
||||||
*/
|
|
||||||
default void setStrokeColor( int gray ) {
|
|
||||||
setStrokeColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Konturlinie auf einen Grauwert mit der angegebenen
|
|
||||||
* Intensität und dem angegebenen Transparenzwert. Der Grauwert 0 entspricht
|
|
||||||
* schwarz, 255 entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int)
|
|
||||||
*/
|
|
||||||
default void setStrokeColor( int gray, int alpha ) {
|
|
||||||
setStrokeColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Konturlinie auf die Farbe mit den angegebenen Rot-,
|
|
||||||
* Grün- und Blauanteilen.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
default void setStrokeColor( int red, int green, int blue ) {
|
|
||||||
setStrokeColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Konturlinie auf die Farbe mit den angegebenen Rot-,
|
|
||||||
* Grün- und Blauanteilen und dem angegebenen Transparenzwert.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
|
||||||
* @see Color#Color(int, int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
default void setStrokeColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setStrokeColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt die Kontur der Form.
|
|
||||||
*/
|
|
||||||
default void noStroke() {
|
|
||||||
setStrokeColor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Konturlinie auf die Standardwerte zurück.
|
|
||||||
*
|
|
||||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKECOLOR
|
|
||||||
* @see schule.ngb.zm.Constants#DEFAULT_STROKEWEIGHT
|
|
||||||
* @see schule.ngb.zm.Constants#SOLID
|
|
||||||
*/
|
|
||||||
default void resetStroke() {
|
|
||||||
setStrokeColor(Constants.DEFAULT_STROKECOLOR);
|
|
||||||
setStrokeWeight(Constants.DEFAULT_STROKEWEIGHT);
|
|
||||||
setStrokeType(Constants.SOLID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Dicke der Konturlinie zurück.
|
|
||||||
*
|
|
||||||
* @return Die aktuelle Dicke der Linie.
|
|
||||||
*/
|
|
||||||
double getStrokeWeight();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Dicke der Konturlinie. Die Dicke muss größer 0 sein. Wird 0
|
|
||||||
* übergeben, dann wird keine Kontur mehr angezeigt.
|
|
||||||
*
|
|
||||||
* @param weight Die Dicke der Konturlinie.
|
|
||||||
*/
|
|
||||||
default void setStrokeWeight( double weight ) {
|
|
||||||
setStroke(createStroke(getStrokeType(), weight, getStrokeJoin()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Art der Konturlinie zurück.
|
|
||||||
*
|
|
||||||
* @return Die aktuelle Art der Konturlinie.
|
|
||||||
* @see Options.StrokeType
|
|
||||||
*/
|
|
||||||
Options.StrokeType getStrokeType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link Constants#DASHED},
|
|
||||||
* {@link Constants#DOTTED} und {@link Constants#SOLID}.
|
|
||||||
*
|
|
||||||
* @param type Eine der möglichen Konturarten.
|
|
||||||
* @see Options.StrokeType
|
|
||||||
*/
|
|
||||||
default void setStrokeType( Options.StrokeType type ) {
|
|
||||||
setStroke(createStroke(type, getStrokeWeight(), getStrokeJoin()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Art der Konturverbindungen zurück.
|
|
||||||
*
|
|
||||||
* @return Die aktuelle Art der Konturverbindungen.
|
|
||||||
* @see Options.StrokeJoin
|
|
||||||
*/
|
|
||||||
Options.StrokeJoin getStrokeJoin();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Typ der Konturverbindungen. Erlaubte Werte sind {@link Constants#ROUND},
|
|
||||||
* {@link Constants#MITER} und {@link Constants#BEVEL}.
|
|
||||||
*
|
|
||||||
* @param join Eine der möglichen Konturverbindungen.
|
|
||||||
* @see Options.StrokeJoin
|
|
||||||
*/
|
|
||||||
default void setStrokeJoin( Options.StrokeJoin join ) {
|
|
||||||
setStroke(createStroke(getStrokeType(), getStrokeWeight(), join));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsmethode, um ein {@link Stroke} Objekt mit den aktuellen
|
|
||||||
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
|
||||||
* zwischengespeichert.
|
|
||||||
*
|
|
||||||
* @param strokeType
|
|
||||||
* @param strokeWeight
|
|
||||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
|
||||||
*/
|
|
||||||
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight, Options.StrokeJoin strokeJoin ) {
|
|
||||||
switch( strokeType ) {
|
|
||||||
case DOTTED:
|
|
||||||
return new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
strokeJoin.awt_type,
|
|
||||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
|
||||||
case DASHED:
|
|
||||||
return new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
strokeJoin.awt_type,
|
|
||||||
10.0f, new float[]{5.0f}, 0.0f);
|
|
||||||
case SOLID:
|
|
||||||
default:
|
|
||||||
return new BasicStroke(
|
|
||||||
(float) strokeWeight,
|
|
||||||
BasicStroke.CAP_ROUND,
|
|
||||||
strokeJoin.awt_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code Updatable} Objekte können in regelmäßigen Intervallen (meist einmal
|
|
||||||
* pro Frame) ihren Zustand aktualisieren. Diese Änderung kann abhängig vom
|
|
||||||
* Zeitintervall (in Sekunden) zum letzten Aufruf passieren.
|
|
||||||
*/
|
|
||||||
public interface Updatable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt an, ob das Objekt gerade auf Aktualisierungen reagiert.
|
|
||||||
* <p>
|
|
||||||
* Wie mit dieser Information umgegangen wird, ist nicht weiter festgelegt.
|
|
||||||
* In der Regel sollte eine aufrufende Instanz zunächst prüfen, ob das
|
|
||||||
* Objekt aktiv ist, und nur dann{@link #update(double)} aufrufen. Für
|
|
||||||
* implementierende Klassen ist es aber gegebenenfalls auch sinnvoll, bei
|
|
||||||
* Inaktivität den Aufruf von {@code update(double)} schnell abzubrechen:
|
|
||||||
* <pre><code>
|
|
||||||
* void update( double delta ) {
|
|
||||||
* if( !isActive() ) {
|
|
||||||
* return;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // Aktualisierung ausführen..
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn das Objekt aktiv ist, {@code false}
|
|
||||||
* andernfalls.
|
|
||||||
*/
|
|
||||||
boolean isActive();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Änderung des Zustandes des Objekts abhängig vom Zeitintervall
|
|
||||||
* {@code delta} in Sekunden.
|
|
||||||
* <p>
|
|
||||||
* Die kann, muss aber nicht, die Rückgabe von {@link #isActive()}
|
|
||||||
* berücksichtigen.
|
|
||||||
*
|
|
||||||
* @param delta Zeitintervall seit dem letzten Aufruf (in Sekunden).
|
|
||||||
*/
|
|
||||||
void update( double delta );
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.event.KeyAdapter;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.awt.event.KeyListener;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein Zeichenfenster ist das Programmfenster für die Zeichenmaschine.
|
|
||||||
* <p>
|
|
||||||
* Das Zeichenfenster implementiert hilfreiche Funktionen, um ein
|
|
||||||
* Programmfenster mit einer Zeichenleinwand als zentrales Element zu erstellen.
|
|
||||||
* Ein Zeichenfenster kann auch ohne eine Zeichenmaschine verwendet werden, um
|
|
||||||
* eigene Programmabläufe zu implementieren.
|
|
||||||
*/
|
|
||||||
public class Zeichenfenster extends JFrame {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt das Look and Feel auf den Standard des Systems.
|
|
||||||
* <p>
|
|
||||||
* Sollte einmalig vor Erstellen des ersten Programmfensters aufgerufen
|
|
||||||
* werden.
|
|
||||||
*/
|
|
||||||
public static final void setLookAndFeel() {
|
|
||||||
try {
|
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
|
||||||
} catch( Exception ex ) {
|
|
||||||
LOG.error(ex, "Couldn't set the look and feel: %s", ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ermittelt ein {@link GraphicsDevice Anzeigegerät}, auf dem ein neues
|
|
||||||
* Zeichenfenster angezeigt werden soll. In der Regel ist dies der
|
|
||||||
* Bildschirm, auf dem sich derzeit der Mauszeiger befindet. Kann kein
|
|
||||||
* solcher Bildschirm ermittelt werden, wird das
|
|
||||||
* {@link GraphicsEnvironment#getDefaultScreenDevice() Standardgerät}
|
|
||||||
* zurückgegeben.
|
|
||||||
*
|
|
||||||
* @return Das Anzeigegerät, auf dem ein neues Fenster angezeigt werden
|
|
||||||
* sollte.
|
|
||||||
*/
|
|
||||||
public static final GraphicsDevice getGraphicsDevice() {
|
|
||||||
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
|
|
||||||
// das Zeichenfenster dort zu zentrieren.
|
|
||||||
// TODO: (ngb) Wenn wir in BlueJ sind, sollte das Fenster neben dem Editor öffnen.
|
|
||||||
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
|
|
||||||
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
|
||||||
GraphicsDevice[] devices = environment.getScreenDevices();
|
|
||||||
GraphicsDevice displayDevice = null;
|
|
||||||
for( GraphicsDevice gd : devices ) {
|
|
||||||
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
|
|
||||||
displayDevice = gd;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Keinen passenden Bildschirm gefunden. Wir nutzen den Standard.
|
|
||||||
if( displayDevice == null ) {
|
|
||||||
displayDevice = environment.getDefaultScreenDevice();
|
|
||||||
}
|
|
||||||
return displayDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Das Anzeigegerät, auf dem die Zeichenmaschine gestartet wurde (muss nicht
|
|
||||||
* gleich dem Aktuellen sein, wenn das Fenster verschoben wurde).
|
|
||||||
*/
|
|
||||||
private final GraphicsDevice displayDevice;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bevorzugte Abmessungen der Zeichenleinwand. Für das Zeichenfenster hat es
|
|
||||||
* Priorität die Leinwand auf dieser Größe zu halten. Gegebenenfalls unter
|
|
||||||
* Missachtung anderer Größenvorgaben. Allerdings kann das Fenster keine
|
|
||||||
* Garantie für die Größe der Leinwand übernehmen.
|
|
||||||
*/
|
|
||||||
private int canvasPreferredWidth, canvasPreferredHeight;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)} in
|
|
||||||
* den Vollbildmodus versetzt wurde.
|
|
||||||
*/
|
|
||||||
private boolean fullscreen = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code KeyListener}, um den Vollbild-Modus mit der Escape-Taste zu
|
|
||||||
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
|
|
||||||
* hinzugefügt und entfernt.
|
|
||||||
*/
|
|
||||||
private final KeyListener fullscreenExitListener = new KeyAdapter() {
|
|
||||||
@Override
|
|
||||||
public void keyPressed( KeyEvent e ) {
|
|
||||||
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
|
||||||
// canvas.removeKeyListener(this);
|
|
||||||
setFullscreen(false);
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Die Zeichenleinwand dieses Fensters.
|
|
||||||
private final Zeichenleinwand canvas;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und einer
|
|
||||||
* {@link Zeichenleinwand} in der angegebenen Größe.
|
|
||||||
*
|
|
||||||
* @param width Die Breite der Zeichenleinwand.
|
|
||||||
* @param height Die Höhe der Zeichenleinwand.
|
|
||||||
* @param title Der Titel des Fensters.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public Zeichenfenster( int width, int height, String title ) {
|
|
||||||
this(new Zeichenleinwand(width, height), title, getGraphicsDevice());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und einer
|
|
||||||
* {@link Zeichenleinwand} in der angegebenen Größe auf dem angegebenen
|
|
||||||
* Anzeigegerät.
|
|
||||||
*
|
|
||||||
* @param width Die Breite der Zeichenleinwand.
|
|
||||||
* @param height Die Höhe der Zeichenleinwand.
|
|
||||||
* @param title Der Titel des Fensters.
|
|
||||||
* @param displayDevice Das Anzeigegerät für das Fenster.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public Zeichenfenster( int width, int height, String title, GraphicsDevice displayDevice ) {
|
|
||||||
this(new Zeichenleinwand(width, height), title, displayDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und der
|
|
||||||
* angegebene {@link Zeichenleinwand}.
|
|
||||||
*
|
|
||||||
* @param canvas Die Zeichenleinwand.
|
|
||||||
* @param title Der Titel des Fensters.
|
|
||||||
*/
|
|
||||||
public Zeichenfenster( Zeichenleinwand canvas, String title ) {
|
|
||||||
this(canvas, title, getGraphicsDevice());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt ein neues Zeichenfenster mit dem angegebenen Titel und der
|
|
||||||
* angegebene {@link Zeichenleinwand} auf dem angegebenen Anzeigegerät.
|
|
||||||
*
|
|
||||||
* @param canvas Die Zeichenleinwand.
|
|
||||||
* @param title Der Titel des Fensters.
|
|
||||||
* @param displayDevice Das Anzeigegerät für das Fenster.
|
|
||||||
*/
|
|
||||||
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
|
|
||||||
super(Validator.requireNotNull(displayDevice, "displayDevice").getDefaultConfiguration());
|
|
||||||
this.displayDevice = displayDevice;
|
|
||||||
|
|
||||||
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");
|
|
||||||
|
|
||||||
this.canvasPreferredWidth = canvas.getWidth();
|
|
||||||
this.canvasPreferredHeight = canvas.getHeight();
|
|
||||||
this.add(canvas, BorderLayout.CENTER);
|
|
||||||
this.canvas = canvas;
|
|
||||||
|
|
||||||
// Konfiguration des Frames
|
|
||||||
this.setTitle(title == null ? "Zeichenfenster " + Constants.APP_VERSION : title);
|
|
||||||
// Kann vom Aufrufenden überschrieben werden
|
|
||||||
this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
|
||||||
|
|
||||||
// Das Icon des Fensters ändern
|
|
||||||
try {
|
|
||||||
if( Zeichenmaschine.MACOS ) {
|
|
||||||
InputStream iconStream = this.getClass().getResourceAsStream("icon_512.png");
|
|
||||||
if( iconStream != null ) {
|
|
||||||
Image icon = ImageIO.read(iconStream);
|
|
||||||
// Dock Icon in macOS setzen
|
|
||||||
Taskbar taskbar = Taskbar.getTaskbar();
|
|
||||||
taskbar.setIconImage(icon);
|
|
||||||
} else {
|
|
||||||
LOG.warn("Could not load dock-icon");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ArrayList<Image> icons = new ArrayList<>(4);
|
|
||||||
for( int size : new int[]{32, 64, 128, 512} ) {
|
|
||||||
URL icnUrl = Zeichenmaschine.class.getResource("icon_" + size + ".png");
|
|
||||||
if( icnUrl != null ) {
|
|
||||||
icons.add(ImageIO.read(icnUrl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( icons.isEmpty() ) {
|
|
||||||
LOG.warn("Could not load dock-icon");
|
|
||||||
} else {
|
|
||||||
this.setIconImages(icons);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch( IllegalArgumentException | IOException e ) {
|
|
||||||
LOG.warn("Could not load image icons: %s", e.getMessage());
|
|
||||||
} catch( SecurityException | UnsupportedOperationException macex ) {
|
|
||||||
// Dock Icon in macOS konnte nicht gesetzt werden :(
|
|
||||||
LOG.warn("Could not set dock icon: %s", macex.getMessage());
|
|
||||||
}
|
|
||||||
// Fenster zusammenbauen, auf dem Bildschirm positionieren ...
|
|
||||||
this.pack();
|
|
||||||
this.setResizable(false);
|
|
||||||
this.setFocusable(true);
|
|
||||||
this.setLocationByPlatform(true);
|
|
||||||
// this.centerFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert das Anzeigegerät, auf dem dieses Fenster erstellt wurde.
|
|
||||||
* <p>
|
|
||||||
* Das Anzeigegerät muss nicht unbedingt gleich dem sein, auf dem sich das
|
|
||||||
* Fenster derzeit befindet, wenn das Fenster verschoben wurde.
|
|
||||||
*
|
|
||||||
* @return Das Anzeigegerät.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public GraphicsDevice getDisplayDevice() {
|
|
||||||
return displayDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die Abmessungen des Anzeigegeräts, auf dem das Fenster gestartet
|
|
||||||
* wurde.
|
|
||||||
*
|
|
||||||
* @return Die Abmessungen des Anzeigegeräts.
|
|
||||||
*/
|
|
||||||
public Rectangle getScreenBounds() {
|
|
||||||
// return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
|
||||||
return displayDevice.getDefaultConfiguration().getBounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die Zeichenleinwand dieses Fensters.
|
|
||||||
*
|
|
||||||
* @return Die Zeichenleinwand.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public Zeichenleinwand getCanvas() {
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die Abmessungen der Zeichenleinwand zurück.
|
|
||||||
*
|
|
||||||
* @return Die Abmessungen der Zeichenleinwand.
|
|
||||||
*/
|
|
||||||
public Rectangle getCanvasBounds() {
|
|
||||||
return canvas.getBounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
|
||||||
*/
|
|
||||||
public final void centerFrame() {
|
|
||||||
java.awt.Rectangle screenBounds = getScreenBounds();
|
|
||||||
java.awt.Rectangle frameBounds = getBounds();
|
|
||||||
this.setLocation(
|
|
||||||
(int) (screenBounds.x + (screenBounds.width - frameBounds.width) / 2.0),
|
|
||||||
(int) (screenBounds.y + (screenBounds.height - frameBounds.height) / 2.0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Größe der Zeichenleinwand auf die angegebenen Werte.
|
|
||||||
*
|
|
||||||
* @param newWidth Neue Breite der Zeichenleinwand.
|
|
||||||
* @param newHeight Neue Höhe der Zeichenleinwand.
|
|
||||||
*/
|
|
||||||
public void setCanvasSize( int newWidth, int newHeight ) {
|
|
||||||
// TODO: (ngb) Put constrains on max/min frame/canvas size
|
|
||||||
if( fullscreen ) {
|
|
||||||
canvasPreferredWidth = newWidth;
|
|
||||||
canvasPreferredHeight = newHeight;
|
|
||||||
setFullscreen(false);
|
|
||||||
} else {
|
|
||||||
canvas.setSize(newWidth, newHeight);
|
|
||||||
canvasPreferredWidth = canvas.getWidth();
|
|
||||||
canvasPreferredHeight = canvas.getHeight();
|
|
||||||
this.pack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktiviert oder deaktiviert den Vollbildmodus für die Zeichenmaschine.
|
|
||||||
* <p>
|
|
||||||
* Der Vollbildmodus wird abhängig von {@code pEnable} entweder aktiviert
|
|
||||||
* oder deaktiviert. Wird die Zeichenmaschine in den Vollbildmodus versetzt,
|
|
||||||
* dann wird automatisch ein {@link KeyListener} aktiviert, der bei
|
|
||||||
* Betätigung der ESCAPE-Taste den Vollbildmodus verlässt. Wird der
|
|
||||||
* Vollbildmodus verlassen, wird die zuletzt gesetzte Fenstergröße
|
|
||||||
* wiederhergestellt.
|
|
||||||
*
|
|
||||||
* @param pEnable Wenn {@code true}, wird der Vollbildmodus aktiviert,
|
|
||||||
* ansonsten deaktiviert.
|
|
||||||
*/
|
|
||||||
public final void setFullscreen( boolean pEnable ) {
|
|
||||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
|
||||||
if( displayDevice.isFullScreenSupported() ) {
|
|
||||||
// Temporarily stop rendering
|
|
||||||
while( canvas.isRendering() ) {
|
|
||||||
try {
|
|
||||||
canvas.suspendRendering();
|
|
||||||
} catch( InterruptedException ex ) {
|
|
||||||
LOG.info(ex, "setFullsceen(true) was interrupted and canceled.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( pEnable && !fullscreen ) {
|
|
||||||
// Activate fullscreen
|
|
||||||
dispose();
|
|
||||||
setUndecorated(true);
|
|
||||||
setResizable(false);
|
|
||||||
displayDevice.setFullScreenWindow(this);
|
|
||||||
|
|
||||||
// Register ESC to exit fullscreen
|
|
||||||
canvas.addKeyListener(fullscreenExitListener);
|
|
||||||
|
|
||||||
// Reset canvas size to its new bounds to recreate buffer and drawing surface
|
|
||||||
java.awt.Rectangle canvasBounds = getCanvasBounds();
|
|
||||||
canvas.setSize(canvasBounds.width, canvasBounds.height);
|
|
||||||
//canvas.requestFocus();
|
|
||||||
canvas.requestFocus();
|
|
||||||
|
|
||||||
fullscreen = true;
|
|
||||||
} else if( !pEnable && fullscreen ) {
|
|
||||||
displayDevice.setFullScreenWindow(null);
|
|
||||||
dispose();
|
|
||||||
setUndecorated(false);
|
|
||||||
setResizable(false);
|
|
||||||
|
|
||||||
canvas.removeKeyListener(fullscreenExitListener);
|
|
||||||
canvas.setSize(canvasPreferredWidth, canvasPreferredHeight);
|
|
||||||
|
|
||||||
setVisible(true);
|
|
||||||
pack();
|
|
||||||
|
|
||||||
//canvas.requestFocus();
|
|
||||||
canvas.requestFocus();
|
|
||||||
|
|
||||||
fullscreen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume rendering
|
|
||||||
canvas.resumeRendering();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob sich dieses Zeichenfenster im Vollbild befindet.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn das Fenster im Vollbild ist, {@code false}
|
|
||||||
* sonst.
|
|
||||||
*/
|
|
||||||
public boolean isFullscreen() {
|
|
||||||
Window win = displayDevice.getFullScreenWindow();
|
|
||||||
return fullscreen && win.equals(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(Zeichenfenster.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
package schule.ngb.zm;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
|
|
||||||
import java.awt.Canvas;
|
|
||||||
import java.awt.Graphics;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Toolkit;
|
|
||||||
import java.awt.image.BufferStrategy;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Leinwand ist die Hauptkomponente einer Zeichenmaschine. Sie besteht aus
|
|
||||||
* mehreren Ebenen, auf denen auf verschiedene Arten gezeichnet werden kann. Die
|
|
||||||
* Ebenen lassen sich beliebig übereinander legen, ausblenden oder wieder
|
|
||||||
* löschen.
|
|
||||||
* <p>
|
|
||||||
* Jede Ebene besitzt eine Zeichenfläche, auf der ihre Zeichnung liegt. Diese
|
|
||||||
* Zeichenflächen werden pro Frame einmal von "unten" nach "oben" auf diese
|
|
||||||
* Leinwand gezeichnet.
|
|
||||||
*/
|
|
||||||
public class Zeichenleinwand extends Canvas {
|
|
||||||
|
|
||||||
// Lokales Lock für Rendervorgänge.
|
|
||||||
private final Object[] renderLock = new Object[0];
|
|
||||||
|
|
||||||
// Liste der hinzugefügten Ebenen.
|
|
||||||
private final List<Layer> layers;
|
|
||||||
|
|
||||||
// Status der Zeichenleinwand.
|
|
||||||
private boolean rendering = false, suspended = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Zeichenleinwand mit einer festen Größe.
|
|
||||||
*
|
|
||||||
* @param width Breite der Zeichenleinwand.
|
|
||||||
* @param height Höhe der Zeichenleinwand.
|
|
||||||
*/
|
|
||||||
public Zeichenleinwand( int width, int height ) {
|
|
||||||
super.setSize(width, height);
|
|
||||||
this.setPreferredSize(getSize());
|
|
||||||
this.setMinimumSize(getSize());
|
|
||||||
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
|
||||||
|
|
||||||
layers = Collections.synchronizedList(new LinkedList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Leinwand ihren Inhalt gerade zeichnet.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn die Inhalte gerade gezeichnet werden.
|
|
||||||
*/
|
|
||||||
public boolean isRendering() {
|
|
||||||
return rendering;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pausiert das Zeichnen der Leinwand kurzzeitig.
|
|
||||||
* <p>
|
|
||||||
* Falls die Leinwand gerade beim Zeichnen ist
|
|
||||||
* ({@code isRendering() == true}, blockt die Methode den aufrufenden Thread
|
|
||||||
* so lange, bis das Rendern beendet ist. Danach wird die Ebene nicht mehr
|
|
||||||
* neu gezeichnet, bis {@link #resumeRendering()} aufgerufen wird.
|
|
||||||
* <p>
|
|
||||||
* Das Zeichnen sollte nur dann unterbrochen werden, wenn sich der Kontext
|
|
||||||
* der Canvas-Komponente in seinem Elterncontainer ändert, um Fehler bei
|
|
||||||
* einer fehlenden Container-Hierarchie zu vermeiden.
|
|
||||||
*
|
|
||||||
* @throws InterruptedException Falls der Thread beim Warten unterbrochen
|
|
||||||
* wird.
|
|
||||||
*/
|
|
||||||
public void suspendRendering() throws InterruptedException {
|
|
||||||
synchronized( renderLock ) {
|
|
||||||
if( isRendering() ) {
|
|
||||||
renderLock.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
suspended = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt das Zeichnen der Leinwand fort, falls es zuvor mit
|
|
||||||
* {@link #suspendRendering()} ausgesetzt wurde.
|
|
||||||
*/
|
|
||||||
public void resumeRendering() {
|
|
||||||
suspended = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ändert die Größe der Zeichenleinwand auf die angegebene Größe in Pixeln.
|
|
||||||
* <p>
|
|
||||||
* Bei einer Größenänderung wird auch die Größe aller bisher hinzugefügter
|
|
||||||
* {@link Layer Ebenen} angepasst, sodass sie die gesamte Leinwand füllen.
|
|
||||||
*
|
|
||||||
* @param width Neue Width der Leinwand in Pixeln.
|
|
||||||
* @param height Neue Höhe der Leinwand in Pixeln.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setSize( int width, int height ) {
|
|
||||||
super.setSize(width, height);
|
|
||||||
this.setPreferredSize(getSize());
|
|
||||||
this.setMinimumSize(getSize());
|
|
||||||
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : layers ) {
|
|
||||||
layer.setSize(width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fügt der Zeichenleinwand eine Ebene hinzu, die oberhalb aller bisherigen
|
|
||||||
* Ebenen eingefügt wird.
|
|
||||||
*
|
|
||||||
* @param layer Die neue Ebene.
|
|
||||||
*/
|
|
||||||
public void addLayer( Layer layer ) {
|
|
||||||
if( layer != null ) {
|
|
||||||
layer.setSize(getWidth(), getHeight());
|
|
||||||
layers.add(layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fügt der Zeichenleinwand eine Ebene an einem bestimmten Index hinzu. Wenn
|
|
||||||
* der Index noch nicht existiert (also größer als die
|
|
||||||
* {@link #getLayerCount() Anzahl der Ebenen} ist), dann wird die neue Ebene
|
|
||||||
* als letzte eingefügt. Die aufrufende Methode kann also nicht sicher sein,
|
|
||||||
* dass die neue Ebene am Ende wirklich am Index {@code i} steht.
|
|
||||||
*
|
|
||||||
* @param i Index der Ebene beginnend mit 0.
|
|
||||||
* @param layer Die neue Ebene.
|
|
||||||
*/
|
|
||||||
public void addLayer( int i, Layer layer ) {
|
|
||||||
if( layer != null ) {
|
|
||||||
layer.setSize(getWidth(), getHeight());
|
|
||||||
if( i > layers.size() ) {
|
|
||||||
layers.add(layer);
|
|
||||||
} else {
|
|
||||||
layers.add(i, layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Anzahl der {@link Layer Ebenen} in dieser Leinwand zurück.
|
|
||||||
*
|
|
||||||
* @return Die Anzahl der Ebenen.
|
|
||||||
*/
|
|
||||||
public int getLayerCount() {
|
|
||||||
return layers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt eine Kopie der Liste der bisher hinzugefügten Ebenen zurück.
|
|
||||||
*
|
|
||||||
* @return Liste der Ebenen.
|
|
||||||
*/
|
|
||||||
public List<Layer> getLayers() {
|
|
||||||
return List.copyOf(layers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holt die Ebene am Index {@code i} (beginnend bei 0).
|
|
||||||
*
|
|
||||||
* @param i Index der Ebene (beginnend bei 0).
|
|
||||||
* @return Die Ebene am Index {@code i} oder {@code null}.
|
|
||||||
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
|
||||||
*/
|
|
||||||
public Layer getLayer( int i ) {
|
|
||||||
if( layers.size() > i ) {
|
|
||||||
return layers.get(i);
|
|
||||||
} else {
|
|
||||||
throw new IndexOutOfBoundsException("No layer at index " + i + " (max: " + (layers.size() - 1) + ").");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
|
||||||
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
|
||||||
*
|
|
||||||
* @param type Klasse der Ebenen, die abgefragt werden.
|
|
||||||
* @param <L> Typ der Ebenen, die abgefragt werden.
|
|
||||||
* @return Erste Ebene vom angegeben Typ.
|
|
||||||
*/
|
|
||||||
public <L extends Layer> L getLayer( Class<L> type ) {
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : layers ) {
|
|
||||||
if( layer.getClass().equals(type) ) {
|
|
||||||
return type.cast(layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht alle Ebenen von einem bestimmten Typ aus der Liste der Ebenen und
|
|
||||||
* gibt diese als Liste zurück. Die Reihenfolge in der Liste entspricht der
|
|
||||||
* Reihenfolge der Ebenen in der Leinwand (von unten nach oben).
|
|
||||||
*
|
|
||||||
* @param type Klasse der Ebenen, die abgefragt werden.
|
|
||||||
* @param <L> Typ der Ebenen, die abgefragt werden.
|
|
||||||
* @return Eine Liste mit den vorhandenen Ebenen des abgefragten Typs.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public <L extends Layer> List<L> getLayers( Class<L> type ) {
|
|
||||||
ArrayList<L> result = new ArrayList<>(layers.size());
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : layers ) {
|
|
||||||
if( layer.getClass().equals(type) ) {
|
|
||||||
result.add(type.cast(layer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt die angegebene Ebene von dieser Zeichenleinwand.
|
|
||||||
*
|
|
||||||
* @param pLayer Die Ebene, die entfernt werden soll.
|
|
||||||
* @return {@code true}, wenn die Liste vorhanden war und entfernt wurde,
|
|
||||||
* {@code false} sonst.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public boolean removeLayer( Layer pLayer ) {
|
|
||||||
return layers.remove(pLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt alle angegebenen Ebenen von dieser Zeichenleinwand.
|
|
||||||
*
|
|
||||||
* @param removeLayers Die Ebenen, die entfernt werden sollen.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public void removeLayers( Layer... removeLayers ) {
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : removeLayers ) {
|
|
||||||
layers.remove(layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt alle vorhandenen Ebenen von dieser Zeichenleinwand.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public void clearLayers() {
|
|
||||||
layers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktualisiert alle {@link Layer Ebenen}, die dieser Zeichenleinwand
|
|
||||||
* hinzugefügt wurden.
|
|
||||||
*
|
|
||||||
* @param delta Die Zeit seit dem letzten Aufruf in Sekunden.
|
|
||||||
* @see Layer#update(double)
|
|
||||||
*/
|
|
||||||
public void updateLayers( double delta ) {
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : List.copyOf(layers) ) {
|
|
||||||
layer.update(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine passende {@link BufferStrategy} für diese Ebene.
|
|
||||||
*/
|
|
||||||
public void allocateBuffer() {
|
|
||||||
this.createBufferStrategy(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Löscht alle Ebenen der Zeichenebene und gibt deren Ressourcen frei.
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : layers ) {
|
|
||||||
layer.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void paint( Graphics g ) {
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafikkontext.
|
|
||||||
*
|
|
||||||
* @param graphics Der Grafikkontext.
|
|
||||||
*/
|
|
||||||
public void draw( Graphics graphics ) {
|
|
||||||
Graphics2D g2d = (Graphics2D) graphics.create();
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : layers ) {
|
|
||||||
layer.draw(g2d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g2d.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
|
||||||
*/
|
|
||||||
public void render() {
|
|
||||||
if( !suspended && isDisplayable() ) {
|
|
||||||
if( getBufferStrategy() == null ) {
|
|
||||||
allocateBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized( renderLock ) {
|
|
||||||
rendering = true;
|
|
||||||
BufferStrategy strategy = this.getBufferStrategy();
|
|
||||||
if( strategy != null ) {
|
|
||||||
do {
|
|
||||||
do {
|
|
||||||
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
|
|
||||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
|
||||||
|
|
||||||
synchronized( layers ) {
|
|
||||||
for( Layer layer : List.copyOf(layers) ) {
|
|
||||||
layer.draw(g2d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g2d.dispose();
|
|
||||||
} while( strategy.contentsRestored() );
|
|
||||||
|
|
||||||
// Display the buffer
|
|
||||||
if( !strategy.contentsLost() ) {
|
|
||||||
strategy.show();
|
|
||||||
|
|
||||||
Toolkit.getDefaultToolkit().sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeat the rendering if the drawing buffer was lost
|
|
||||||
} while( strategy.contentsLost() );
|
|
||||||
}
|
|
||||||
rendering = false;
|
|
||||||
renderLock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(Zeichenleinwand.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.Updatable;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
import schule.ngb.zm.util.events.EventDispatcher;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
public abstract class Animation<T> extends Constants implements Updatable {
|
|
||||||
|
|
||||||
protected int runtime;
|
|
||||||
|
|
||||||
protected int elapsedTime = 0;
|
|
||||||
|
|
||||||
protected boolean running = false, finished = false;
|
|
||||||
|
|
||||||
protected DoubleUnaryOperator easing;
|
|
||||||
|
|
||||||
public Animation() {
|
|
||||||
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
|
||||||
this.easing = Constants.DEFAULT_EASING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Animation( DoubleUnaryOperator easing ) {
|
|
||||||
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
|
||||||
this.easing = Validator.requireNotNull(easing, "easing");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Animation( int runtime ) {
|
|
||||||
this.runtime = runtime;
|
|
||||||
this.easing = Constants.DEFAULT_EASING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
this.runtime = runtime;
|
|
||||||
this.easing = Validator.requireNotNull(easing, "easing");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRuntime() {
|
|
||||||
return runtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRuntime( int pRuntime ) {
|
|
||||||
this.runtime = pRuntime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DoubleUnaryOperator getEasing() {
|
|
||||||
return easing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEasing( DoubleUnaryOperator pEasing ) {
|
|
||||||
this.easing = Validator.requireNotNull(pEasing, "easing");
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract T getAnimationTarget();
|
|
||||||
|
|
||||||
public final void start() {
|
|
||||||
this.initialize();
|
|
||||||
elapsedTime = 0;
|
|
||||||
running = true;
|
|
||||||
finished = false;
|
|
||||||
animate(easing.applyAsDouble(0.0));
|
|
||||||
dispatchEvent("start");
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void stop() {
|
|
||||||
running = false;
|
|
||||||
// Make sure the last animation frame was interpolated correctly
|
|
||||||
animate(easing.applyAsDouble((double) elapsedTime / (double) runtime));
|
|
||||||
this.finish();
|
|
||||||
finished = true;
|
|
||||||
dispatchEvent("stop");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initialize() {
|
|
||||||
// Intentionally left blank
|
|
||||||
}
|
|
||||||
|
|
||||||
public void finish() {
|
|
||||||
// Intentionally left blank
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void await() {
|
|
||||||
while( !finished ) {
|
|
||||||
Thread.yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActive() {
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
elapsedTime += (int) (delta * 1000);
|
|
||||||
if( elapsedTime > runtime )
|
|
||||||
elapsedTime = runtime;
|
|
||||||
|
|
||||||
double t = (double) elapsedTime / (double) runtime;
|
|
||||||
if( t >= 1.0 ) {
|
|
||||||
stop();
|
|
||||||
} else {
|
|
||||||
animate(getEasing().applyAsDouble(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Fortschritt der Animation auf den angegebenen Wert.
|
|
||||||
* <p>
|
|
||||||
* {@code e} liegt in der Regel zwischen 0 und 1. Je nach verwendeten
|
|
||||||
* {@link Easing} Funktion kann der Wert aber in Ausnahmefällen unter 0 oder
|
|
||||||
* über 1 liegen. Die {@code step()} Methode muss dem nicht Rechnung tragen
|
|
||||||
* und kann wenn sinnvoll den {@code e} Wert auf [0, 1] limitieren:
|
|
||||||
* <pre><code>
|
|
||||||
* e = Constants.limit(e, 0, 1);
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @param e Fortschritt der Animation, nachdem die Easing-Funktion angewandt
|
|
||||||
* wurde.
|
|
||||||
*/
|
|
||||||
public abstract void animate( double e );
|
|
||||||
|
|
||||||
EventDispatcher<Animation, AnimationListener> eventDispatcher;
|
|
||||||
|
|
||||||
private EventDispatcher<Animation, AnimationListener> initializeEventDispatcher() {
|
|
||||||
if( eventDispatcher == null ) {
|
|
||||||
eventDispatcher = new EventDispatcher<>();
|
|
||||||
eventDispatcher.registerEventType("start", ( a, l ) -> l.animationStarted(a));
|
|
||||||
eventDispatcher.registerEventType("stop", ( a, l ) -> l.animationStopped(a));
|
|
||||||
}
|
|
||||||
return eventDispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dispatchEvent( String type ) {
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent(type, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addListener( AnimationListener listener ) {
|
|
||||||
initializeEventDispatcher().addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeListener( AnimationListener listener ) {
|
|
||||||
initializeEventDispatcher().removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Wrapper Animation, um die Werte einer anderen Animation (Laufzeit, Easing) zu überschrieben,
|
|
||||||
* ohne die Werte der Originalanimation zu verändern.
|
|
||||||
*
|
|
||||||
* @param <S> Art des Animierten Objektes.
|
|
||||||
*/
|
|
||||||
public class AnimationFacade<S> extends Animation<S> {
|
|
||||||
|
|
||||||
private final Animation<S> anim;
|
|
||||||
|
|
||||||
public AnimationFacade( Animation<S> anim, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
this.anim = Validator.requireNotNull(anim, "anim");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public S getAnimationTarget() {
|
|
||||||
return anim.getAnimationTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
anim.animate(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
anim.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
anim.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
// TODO: (ngb) Maybe use AnimationFacade to override runtime?
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class AnimationGroup<T> extends Animation<T> {
|
|
||||||
|
|
||||||
private final List<Animation<T>> anims;
|
|
||||||
|
|
||||||
private final boolean overrideEasing;
|
|
||||||
|
|
||||||
private int overrideRuntime = -1;
|
|
||||||
|
|
||||||
private final int lag;
|
|
||||||
|
|
||||||
private int active = 0;
|
|
||||||
|
|
||||||
public AnimationGroup( Animation<T>... anims ) {
|
|
||||||
this(0, -1, null, Arrays.asList(anims));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationGroup( Collection<Animation<T>> anims ) {
|
|
||||||
this(0, -1, null, anims);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationGroup( int lag, Collection<Animation<T>> anims ) {
|
|
||||||
this(lag, -1, null, anims);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationGroup( DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
|
|
||||||
this(0, -1, easing, anims);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationGroup( int lag, DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
|
|
||||||
this(lag, -1, easing, anims);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationGroup( int lag, int runtime, DoubleUnaryOperator easing, Collection<Animation<T>> anims ) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.anims = List.copyOf(anims);
|
|
||||||
this.lag = lag;
|
|
||||||
|
|
||||||
if( easing != null ) {
|
|
||||||
this.easing = easing;
|
|
||||||
overrideEasing = true;
|
|
||||||
} else {
|
|
||||||
overrideEasing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( runtime > 0 ) {
|
|
||||||
this.runtime = anims.size() * lag + runtime;
|
|
||||||
this.overrideRuntime = runtime;
|
|
||||||
} else {
|
|
||||||
this.runtime = 0;
|
|
||||||
for( int i = 0; i < this.anims.size(); i++ ) {
|
|
||||||
if( i * lag + this.anims.get(i).getRuntime() > this.runtime ) {
|
|
||||||
this.runtime = i * lag + this.anims.get(i).getRuntime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T getAnimationTarget() {
|
|
||||||
for( Animation<T> anim : anims ) {
|
|
||||||
if( anim.isActive() ) {
|
|
||||||
return anim.getAnimationTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( this.finished ) {
|
|
||||||
return anims.get(anims.size() - 1).getAnimationTarget();
|
|
||||||
} else {
|
|
||||||
return anims.get(0).getAnimationTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DoubleUnaryOperator getEasing() {
|
|
||||||
for( Animation<T> anim : anims ) {
|
|
||||||
if( anim.isActive() ) {
|
|
||||||
return anim.getEasing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( this.finished ) {
|
|
||||||
return anims.get(anims.size() - 1).getEasing();
|
|
||||||
} else {
|
|
||||||
return anims.get(0).getEasing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void update( double delta ) {
|
|
||||||
// elapsedTime += (int) (delta * 1000);
|
|
||||||
//
|
|
||||||
// // Animation is done. Stop all Animations.
|
|
||||||
// if( elapsedTime > runtime ) {
|
|
||||||
// for( int i = 0; i < anims.size(); i++ ) {
|
|
||||||
// if( anims.get(i).isActive() ) {
|
|
||||||
// anims.get(i).elapsedTime = anims.get(i).runtime;
|
|
||||||
// anims.get(i).stop();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// elapsedTime = runtime;
|
|
||||||
// running = false;
|
|
||||||
// this.stop();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// while( active < anims.size() && elapsedTime >= active * lag ) {
|
|
||||||
// anims.get(active).start();
|
|
||||||
// active += 1;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for( int i = 0; i < active; i++ ) {
|
|
||||||
// double t = 0.0;
|
|
||||||
// if( overrideRuntime > 0 ) {
|
|
||||||
// t = (double) (elapsedTime - i*lag) / (double) overrideRuntime;
|
|
||||||
// } else {
|
|
||||||
// t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if( t >= 1.0 ) {
|
|
||||||
// anims.get(i).elapsedTime = anims.get(i).runtime;
|
|
||||||
// anims.get(i).stop();
|
|
||||||
// } else {
|
|
||||||
// double e = overrideEasing ?
|
|
||||||
// easing.applyAsDouble(t) :
|
|
||||||
// anims.get(i).easing.applyAsDouble(t);
|
|
||||||
//
|
|
||||||
// anims.get(i).animate(e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
for( Animation<T> anim : anims ) {
|
|
||||||
if( anim.isActive() ) {
|
|
||||||
anim.elapsedTime = anim.runtime;
|
|
||||||
anim.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
while( active < anims.size() && elapsedTime >= active * lag ) {
|
|
||||||
anims.get(active).start();
|
|
||||||
active += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for( int i = 0; i < active; i++ ) {
|
|
||||||
Animation<T> curAnim = anims.get(i);
|
|
||||||
|
|
||||||
double curRuntime = curAnim.getRuntime();
|
|
||||||
if( overrideRuntime > 0 ) {
|
|
||||||
curRuntime = overrideRuntime;
|
|
||||||
}
|
|
||||||
|
|
||||||
double t = (double) (elapsedTime - i * lag) / (double) curRuntime;
|
|
||||||
if( t >= 1.0 ) {
|
|
||||||
curAnim.elapsedTime = curAnim.getRuntime();
|
|
||||||
curAnim.stop();
|
|
||||||
} else {
|
|
||||||
e = overrideEasing ?
|
|
||||||
easing.applyAsDouble(t) :
|
|
||||||
curAnim.easing.applyAsDouble(t);
|
|
||||||
|
|
||||||
curAnim.elapsedTime = (elapsedTime - i * lag);
|
|
||||||
curAnim.animate(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.events.Listener;
|
|
||||||
|
|
||||||
public interface AnimationListener extends Listener<Animation> {
|
|
||||||
|
|
||||||
void animationStarted( Animation anim );
|
|
||||||
|
|
||||||
void animationStopped( Animation anim );
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Führt eine Liste von Animationen nacheinander aus. Jede Animation startet direkt nachdem die
|
|
||||||
* davor geendet ist. Optional kann zwischen dem Ende einer und dem Start der nächsten Animation
|
|
||||||
* ein
|
|
||||||
* <var>lag</var> eingefügt werden.
|
|
||||||
*
|
|
||||||
* @param <T> Die Art des animierten Objektes.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class AnimationSequence<T> extends Animation<T> {
|
|
||||||
|
|
||||||
private final List<Animation<T>> anims;
|
|
||||||
|
|
||||||
private final int lag;
|
|
||||||
|
|
||||||
private int currentAnimationIndex = -1, currentStart = -1, nextStart = -1;
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public AnimationSequence( Animation<T>... anims ) {
|
|
||||||
this(0, Arrays.asList(anims));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationSequence( Collection<Animation<T>> anims ) {
|
|
||||||
this(0, anims);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnimationSequence( int lag, Collection<Animation<T>> anims ) {
|
|
||||||
super(Easing::linear);
|
|
||||||
|
|
||||||
this.anims = List.copyOf(anims);
|
|
||||||
this.lag = lag;
|
|
||||||
|
|
||||||
this.runtime = (anims.size() - 1) * lag + anims.stream().mapToInt(Animation::getRuntime).sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T getAnimationTarget() {
|
|
||||||
for( Animation<T> anim : anims ) {
|
|
||||||
if( anim.isActive() ) {
|
|
||||||
return anim.getAnimationTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( this.finished ) {
|
|
||||||
return anims.get(anims.size() - 1).getAnimationTarget();
|
|
||||||
} else {
|
|
||||||
return anims.get(0).getAnimationTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DoubleUnaryOperator getEasing() {
|
|
||||||
for( Animation<T> anim : anims ) {
|
|
||||||
if( anim.isActive() ) {
|
|
||||||
return anim.getEasing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( this.finished ) {
|
|
||||||
return anims.get(anims.size() - 1).getEasing();
|
|
||||||
} else {
|
|
||||||
return anims.get(0).getEasing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void update( double delta ) {
|
|
||||||
// elapsedTime += (int) (delta * 1000);
|
|
||||||
//
|
|
||||||
// // Animation is done. Stop all Animations.
|
|
||||||
// if( elapsedTime > runtime ) {
|
|
||||||
// for( int i = 0; i < anims.size(); i++ ) {
|
|
||||||
// if( anims.get(i).isActive() ) {
|
|
||||||
// anims.get(i).elapsedTime = anims.get(i).runtime;
|
|
||||||
// anims.get(i).stop();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// elapsedTime = runtime;
|
|
||||||
// running = false;
|
|
||||||
// this.stop();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Animation<T> curAnim = null;
|
|
||||||
// if( elapsedTime > nextStart ) {
|
|
||||||
// currentAnimation += 1;
|
|
||||||
// curAnim = anims.get(currentAnimation);
|
|
||||||
// currentStart = nextStart;
|
|
||||||
// nextStart += lag + curAnim.getRuntime();
|
|
||||||
// curAnim.start();
|
|
||||||
// } else {
|
|
||||||
// curAnim = anims.get(currentAnimation);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Calculate delta for current animation
|
|
||||||
// double t = (double) (elapsedTime - currentStart) / (double) curAnim.getRuntime();
|
|
||||||
// if( t >= 1.0 ) {
|
|
||||||
// curAnim.elapsedTime = curAnim.runtime;
|
|
||||||
// curAnim.stop();
|
|
||||||
// } else {
|
|
||||||
// curAnim.animate(curAnim.easing.applyAsDouble(t));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
for( Animation<T> anim : anims ) {
|
|
||||||
if( anim.isActive() ) {
|
|
||||||
anim.elapsedTime = anim.runtime;
|
|
||||||
anim.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
Animation<T> curAnim = null;
|
|
||||||
if( running && elapsedTime > nextStart ) {
|
|
||||||
currentAnimationIndex += 1;
|
|
||||||
curAnim = anims.get(currentAnimationIndex);
|
|
||||||
currentStart = nextStart;
|
|
||||||
nextStart += lag + curAnim.getRuntime();
|
|
||||||
curAnim.start();
|
|
||||||
} else {
|
|
||||||
curAnim = anims.get(currentAnimationIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate delta for current animation
|
|
||||||
double t = (double) (elapsedTime - currentStart) / (double) curAnim.getRuntime();
|
|
||||||
if( t >= 1.0 ) {
|
|
||||||
curAnim.elapsedTime = curAnim.runtime;
|
|
||||||
curAnim.stop();
|
|
||||||
} else {
|
|
||||||
curAnim.elapsedTime = (elapsedTime - currentStart);
|
|
||||||
curAnim.animate(curAnim.easing.applyAsDouble(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.Vector;
|
|
||||||
import schule.ngb.zm.util.tasks.FramerateLimitedTask;
|
|
||||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.function.*;
|
|
||||||
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class Animations {
|
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( String propName, T target, double to, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
double from;
|
|
||||||
try {
|
|
||||||
from = callGetter(target, propName, double.class);
|
|
||||||
} catch( InvocationTargetException | NoSuchMethodException |
|
|
||||||
IllegalAccessException ex ) {
|
|
||||||
throw new RuntimeException("Can't access property getter for animation.", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Method propSetter;
|
|
||||||
try {
|
|
||||||
propSetter = findSetter(target, propName, double.class);
|
|
||||||
} catch( NoSuchMethodException ex ) {
|
|
||||||
throw new RuntimeException("Can't find property setter for animation.", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
|
||||||
try {
|
|
||||||
propSetter.invoke(target, d);
|
|
||||||
} catch( IllegalAccessException | InvocationTargetException e ) {
|
|
||||||
throw new RuntimeException("Can't access property setter for animation.", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( String propName, T target, Color to, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
Color from;
|
|
||||||
try {
|
|
||||||
from = callGetter(target, propName, Color.class);
|
|
||||||
} catch( InvocationTargetException | NoSuchMethodException |
|
|
||||||
IllegalAccessException ex ) {
|
|
||||||
throw new RuntimeException("Can't access property getter for animation.", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Method propSetter;
|
|
||||||
try {
|
|
||||||
propSetter = findSetter(target, propName, Color.class);
|
|
||||||
} catch( NoSuchMethodException ex ) {
|
|
||||||
throw new RuntimeException("Can't find property setter for animation.", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
|
||||||
try {
|
|
||||||
propSetter.invoke(target, d);
|
|
||||||
} catch( IllegalAccessException | InvocationTargetException e ) {
|
|
||||||
throw new RuntimeException("Can't access property setter for animation.", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( String propName, T target, Vector to, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
Vector from;
|
|
||||||
try {
|
|
||||||
from = callGetter(target, propName, Vector.class);
|
|
||||||
} catch( InvocationTargetException | NoSuchMethodException |
|
|
||||||
IllegalAccessException ex ) {
|
|
||||||
throw new RuntimeException("Can't access property getter for animation.", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Method propSetter;
|
|
||||||
try {
|
|
||||||
propSetter = findSetter(target, propName, Vector.class);
|
|
||||||
} catch( NoSuchMethodException ex ) {
|
|
||||||
throw new RuntimeException("Can't find property setter for animation.", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
|
||||||
try {
|
|
||||||
propSetter.invoke(target, d);
|
|
||||||
} catch( IllegalAccessException | InvocationTargetException e ) {
|
|
||||||
throw new RuntimeException("Can't access property setter for animation.", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings( "unchecked" )
|
|
||||||
private static <T, R> R callGetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
|
||||||
String getterName = makeMethodName("get", propName);
|
|
||||||
Method getter = target.getClass().getMethod(getterName);
|
|
||||||
if( getter.getReturnType().equals(propType) ) {
|
|
||||||
return (R) getter.invoke(target);
|
|
||||||
} else {
|
|
||||||
throw new NoSuchMethodException(String.format("No getter for property <%s> found.", propName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T, R> Method findSetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException {
|
|
||||||
String setterName = makeMethodName("set", propName);
|
|
||||||
Method setter = target.getClass().getMethod(setterName, propType);
|
|
||||||
if( setter.getReturnType().equals(void.class) && setter.getParameterCount() == 1 ) {
|
|
||||||
return setter;
|
|
||||||
} else {
|
|
||||||
throw new NoSuchMethodException(String.format("No setter for property <%s> found.", propName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String makeMethodName( String prefix, String propName ) {
|
|
||||||
String firstChar = propName.substring(0, 1).toUpperCase();
|
|
||||||
String tail = "";
|
|
||||||
if( propName.length() > 1 ) {
|
|
||||||
tail = propName.substring(1);
|
|
||||||
}
|
|
||||||
return prefix + firstChar + tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
|
||||||
Validator.requireNotNull(target, "target");
|
|
||||||
Validator.requireNotNull(propSetter, "propSetter");
|
|
||||||
return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( T target, final Color from, final Color to, int runtime, DoubleUnaryOperator easing, Consumer<Color> propSetter ) {
|
|
||||||
return play(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( T target, final Vector from, final Vector to, int runtime, DoubleUnaryOperator easing, Consumer<Vector> propSetter ) {
|
|
||||||
return play(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T, R> Future<T> animateProperty( T target, R from, R to, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, Consumer<R> propSetter ) {
|
|
||||||
return play(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static final <T, R> Future<T> play( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, BiConsumer<T, R> applicator ) {
|
|
||||||
return play(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> Future<T> play( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
|
||||||
return TaskRunner.run(new FramerateLimitedTask() {
|
|
||||||
double t = 0.0;
|
|
||||||
|
|
||||||
final long starttime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
// One animation step for t in [0,1]
|
|
||||||
stepper.accept(easing.applyAsDouble(t));
|
|
||||||
t = (double) (System.currentTimeMillis() - starttime) / (double) runtime;
|
|
||||||
running = (t <= 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void finish() {
|
|
||||||
stepper.accept(easing.applyAsDouble(1.0));
|
|
||||||
}
|
|
||||||
}, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T playAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
|
||||||
Future<T> future = play(target, runtime, easing, stepper);
|
|
||||||
while( !future.isDone() ) {
|
|
||||||
try {
|
|
||||||
return future.get();
|
|
||||||
} catch( InterruptedException iex ) {
|
|
||||||
// Keep waiting
|
|
||||||
} catch( ExecutionException eex ) {
|
|
||||||
LOG.error(eex.getCause(), "Animation task terminated with exception");
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*public static final <T, R> Future<T> animate( T target, int runtime, Animator<T, R> animator ) {
|
|
||||||
return animate(
|
|
||||||
target, runtime,
|
|
||||||
animator::easing,
|
|
||||||
animator::interpolator,
|
|
||||||
animator::applicator
|
|
||||||
);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public static <T> Future<Animation<T>> play( Animation<T> animation ) {
|
|
||||||
// TODO: (ngb) Don't start when running
|
|
||||||
return TaskRunner.run(new FramerateLimitedTask() {
|
|
||||||
@Override
|
|
||||||
protected void initialize() {
|
|
||||||
animation.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
animation.update(delta);
|
|
||||||
running = animation.isActive();
|
|
||||||
}
|
|
||||||
}, animation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Animation<T> playAndWait( Animation<T> animation ) {
|
|
||||||
Future<Animation<T>> future = play(animation);
|
|
||||||
animation.await();
|
|
||||||
return animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Future<Animation<T>> play( Animation<T> animation, DoubleUnaryOperator easing ) {
|
|
||||||
final AnimationFacade<T> facade = new AnimationFacade<>(animation, animation.getRuntime(), easing);
|
|
||||||
return TaskRunner.run(new FramerateLimitedTask() {
|
|
||||||
@Override
|
|
||||||
protected void initialize() {
|
|
||||||
facade.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
facade.update(delta);
|
|
||||||
running = facade.isActive();
|
|
||||||
}
|
|
||||||
}, animation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Log LOG = Log.getLogger(Animations.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.Vector;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Animates the {@code target} in a circular motion centered at (<var>cx</var>, <var>cy</var>).
|
|
||||||
*/
|
|
||||||
public class CircleAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
private final Shape target;
|
|
||||||
|
|
||||||
private final double centerX, centerY, rotateTo;
|
|
||||||
|
|
||||||
private double rotationRadius, startAngle;
|
|
||||||
|
|
||||||
private final boolean rotateRight;
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy ) {
|
|
||||||
this(target, cx, cy, 360, true, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, double rotateTo ) {
|
|
||||||
this(target, cx, cy, rotateTo, true, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight ) {
|
|
||||||
this(target, cx, cy, 360, rotateRight, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, double rotateTo, boolean rotateRight ) {
|
|
||||||
this(target, cx, cy, rotateTo, rotateRight, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, int runtime ) {
|
|
||||||
this(target, cx, cy, 360, true, runtime, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight, int runtime ) {
|
|
||||||
this(target, cx, cy, 360, rotateRight, runtime, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, DoubleUnaryOperator easing ) {
|
|
||||||
this(target, cx, cy, 360, true, DEFAULT_ANIM_RUNTIME, easing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight, DoubleUnaryOperator easing ) {
|
|
||||||
this(target, cx, cy, 360, rotateRight, DEFAULT_ANIM_RUNTIME, easing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, double rotateTo, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
this(target, cx, cy, rotateTo, true, runtime, easing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, double rotateTo, boolean rotateRight, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
this.target = target;
|
|
||||||
this.centerX = cx;
|
|
||||||
this.centerY = cy;
|
|
||||||
this.rotateTo = Constants.radians(Constants.limit(rotateTo, 0, 360));
|
|
||||||
this.rotateRight = rotateRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
Vector vec = new Vector(target.getX(), target.getY()).sub(centerX, centerY);
|
|
||||||
startAngle = vec.heading();
|
|
||||||
rotationRadius = vec.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
double angle = startAngle;
|
|
||||||
if( rotateRight ) {
|
|
||||||
angle += Constants.interpolate(0, rotateTo, e);
|
|
||||||
} else {
|
|
||||||
angle -= Constants.interpolate(0, rotateTo, e);
|
|
||||||
}
|
|
||||||
double x = centerX + rotationRadius * Constants.cos(angle);
|
|
||||||
double y = centerY + rotationRadius * Constants.sin(angle);
|
|
||||||
target.moveTo(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class ContinousAnimation<T> extends Animation<T> {
|
|
||||||
|
|
||||||
private final Animation<T> baseAnimation;
|
|
||||||
|
|
||||||
private int lag = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert eine Approximation der aktuellen Steigung der Easing-Funktion, um im Fall
|
|
||||||
* {@code easeInOnly == true} nach dem ersten Durchlauf die passende Geschwindigkeit
|
|
||||||
* beizubehalten.
|
|
||||||
*/
|
|
||||||
private double m = 1.0, lastEase = 0.0;
|
|
||||||
|
|
||||||
private boolean easeInOnly = false;
|
|
||||||
|
|
||||||
public ContinousAnimation( Animation<T> baseAnimation ) {
|
|
||||||
this(baseAnimation, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContinousAnimation( Animation<T> baseAnimation, int lag ) {
|
|
||||||
this(baseAnimation, lag, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContinousAnimation( Animation<T> baseAnimation, boolean easeInOnly ) {
|
|
||||||
this(baseAnimation, 0, easeInOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContinousAnimation( Animation<T> baseAnimation, int lag, boolean easeInOnly ) {
|
|
||||||
super(baseAnimation.getRuntime() + lag, baseAnimation.getEasing());
|
|
||||||
this.baseAnimation = baseAnimation;
|
|
||||||
this.lag = lag;
|
|
||||||
this.easeInOnly = easeInOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T getAnimationTarget() {
|
|
||||||
return baseAnimation.getAnimationTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRuntime() {
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void update( double delta ) {
|
|
||||||
// elapsedTime += (int) (delta * 1000);
|
|
||||||
// if( elapsedTime >= runtime + lag ) {
|
|
||||||
// elapsedTime %= (runtime + lag);
|
|
||||||
//
|
|
||||||
// if( easeInOnly && easing != null ) {
|
|
||||||
// easing = null;
|
|
||||||
// // runtime = (int)((1.0/m)*(runtime + lag));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// double t = (double) elapsedTime / (double) runtime;
|
|
||||||
// if( t >= 1.0 ) {
|
|
||||||
// t = 1.0;
|
|
||||||
// }
|
|
||||||
// if( easing != null ) {
|
|
||||||
// double e = easing.applyAsDouble(t);
|
|
||||||
// animate(e);
|
|
||||||
// m = (e-lastEase)/(delta*1000/(asDouble(runtime)));
|
|
||||||
// lastEase = e;
|
|
||||||
// } else {
|
|
||||||
// animate(t);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
baseAnimation.elapsedTime = baseAnimation.getRuntime();
|
|
||||||
baseAnimation.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
baseAnimation.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRuntime( int pRuntime ) {
|
|
||||||
baseAnimation.setRuntime(pRuntime);
|
|
||||||
runtime = pRuntime + lag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
int currentRuntime = elapsedTime + (int) (delta * 1000);
|
|
||||||
if( currentRuntime >= runtime + lag ) {
|
|
||||||
elapsedTime = currentRuntime % (runtime + lag);
|
|
||||||
|
|
||||||
if( easeInOnly && easing != null ) {
|
|
||||||
easing = Easing.linear();
|
|
||||||
// runtime = (int)((1.0/m)*(runtime + lag));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.update(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
// double t = (double) elapsedTime / (double) runtime;
|
|
||||||
// if( t >= 1.0 ) {
|
|
||||||
// t = 1.0;
|
|
||||||
// }
|
|
||||||
baseAnimation.elapsedTime = elapsedTime;
|
|
||||||
baseAnimation.animate(e);
|
|
||||||
m = (e - lastEase) / (delta * 1000 / (asDouble(runtime)));
|
|
||||||
lastEase = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,320 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see <a href="https://easings.net/de">Cheat Sheet für Easing-Funktionen</a>
|
|
||||||
*/
|
|
||||||
public class Easing {
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator DEFAULT_EASING = Easing::smooth;
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator thereAndBack() {
|
|
||||||
return Easing::thereAndBack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator thereAndBack( final DoubleUnaryOperator baseEasing ) {
|
|
||||||
return ( t ) -> thereAndBack(t, baseEasing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double thereAndBack( double t ) {
|
|
||||||
return thereAndBack(t, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double thereAndBack( double t, DoubleUnaryOperator baseEasing ) {
|
|
||||||
if( t < 0.5 ) {
|
|
||||||
return baseEasing.applyAsDouble(2 * t);
|
|
||||||
} else {
|
|
||||||
return baseEasing.applyAsDouble(2 - 2 * t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing ) {
|
|
||||||
return ( t ) -> halfAndHalf(t, firstEasing, secondEasing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing, final double split ) {
|
|
||||||
return ( t ) -> halfAndHalf(t, firstEasing, secondEasing, split);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing ) {
|
|
||||||
return halfAndHalf(t, firstEasing, secondEasing, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing, double split ) {
|
|
||||||
if( t < split ) {
|
|
||||||
return firstEasing.applyAsDouble(2 * t);
|
|
||||||
} else {
|
|
||||||
return secondEasing.applyAsDouble(1 - 2 * t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Functions taken from easings.net
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator linear() {
|
|
||||||
return Easing::linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double linear( double t ) {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator quadIn() {
|
|
||||||
return Easing::quadIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double quadIn( double t ) {
|
|
||||||
return t * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator quadOut() {
|
|
||||||
return Easing::quadOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double quadOut( double t ) {
|
|
||||||
return 1 - (1 - t) * (1 - t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator quadInOut() {
|
|
||||||
return Easing::quadInOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double quadInOut( double t ) {
|
|
||||||
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator cubicIn() {
|
|
||||||
return Easing::cubicIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double cubicIn( double t ) {
|
|
||||||
return t * t * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator cubicOut() {
|
|
||||||
return Easing::cubicOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double cubicOut( double t ) {
|
|
||||||
return 1 - Math.pow(1 - t, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator cubicInOut() {
|
|
||||||
return Easing::cubicInOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double cubicInOut( double t ) {
|
|
||||||
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator sineIn() {
|
|
||||||
return Easing::sineIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double sineIn( double t ) {
|
|
||||||
return 1 - Math.cos((t * Math.PI) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator sineOut() {
|
|
||||||
return Easing::sineOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double sineOut( double t ) {
|
|
||||||
return Math.sin((t * Math.PI) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator sineInOut() {
|
|
||||||
return Easing::sineInOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double sineInOut( double t ) {
|
|
||||||
return -(Math.cos(Math.PI * t) - 1) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator elasticIn() {
|
|
||||||
return Easing::elasticIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double elasticIn( double t ) {
|
|
||||||
double c4 = (2 * Math.PI) / 3;
|
|
||||||
|
|
||||||
return t == 0
|
|
||||||
? 0
|
|
||||||
: t == 1
|
|
||||||
? 1
|
|
||||||
: -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator elasticOut() {
|
|
||||||
return Easing::elasticOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double elasticOut( double t ) {
|
|
||||||
double c4 = (2 * Math.PI) / 3;
|
|
||||||
|
|
||||||
return t == 0
|
|
||||||
? 0
|
|
||||||
: t == 1
|
|
||||||
? 1
|
|
||||||
: Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator elasticInOut() {
|
|
||||||
return Easing::elasticInOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double elasticInOut( double t ) {
|
|
||||||
double c5 = (2 * Math.PI) / 4.5;
|
|
||||||
|
|
||||||
return t == 0
|
|
||||||
? 0
|
|
||||||
: t == 1
|
|
||||||
? 1
|
|
||||||
: t < 0.5
|
|
||||||
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2
|
|
||||||
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator bounceIn() {
|
|
||||||
return Easing::bounceIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double bounceIn( double t ) {
|
|
||||||
return 1 - bounceOut(1 - t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator bounceOut() {
|
|
||||||
return Easing::bounceOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double bounceOut( double t ) {
|
|
||||||
double n1 = 7.5625;
|
|
||||||
double d1 = 2.75;
|
|
||||||
|
|
||||||
if( t < 1.0 / d1 ) {
|
|
||||||
return n1 * t * t;
|
|
||||||
} else if( t < 2.0 / d1 ) {
|
|
||||||
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
||||||
} else if( t < 2.5 / d1 ) {
|
|
||||||
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
||||||
} else {
|
|
||||||
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator bounceInOut() {
|
|
||||||
return Easing::bounceInOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double bounceInOut( double t ) {
|
|
||||||
return t < 0.5
|
|
||||||
? (1 - bounceOut(1 - 2 * t)) / 2
|
|
||||||
: (1 + bounceOut(2 * t - 1)) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator backIn() {
|
|
||||||
return Easing::backIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double backIn( double t ) {
|
|
||||||
double c1 = 1.70158;
|
|
||||||
double c3 = c1 + 1;
|
|
||||||
|
|
||||||
return c3 * t * t * t - c1 * t * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator backOut() {
|
|
||||||
return Easing::backOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double backOut( double t ) {
|
|
||||||
double c1 = 1.70158;
|
|
||||||
double c3 = c1 + 1;
|
|
||||||
|
|
||||||
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator backInOut() {
|
|
||||||
return Easing::backInOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double backInOut( double t ) {
|
|
||||||
double c1 = 1.70158;
|
|
||||||
double c2 = c1 * 1.525;
|
|
||||||
|
|
||||||
return t < 0.5
|
|
||||||
? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
|
|
||||||
: (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Functions from manim community
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator smooth() {
|
|
||||||
return Easing::smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double smooth( double t ) {
|
|
||||||
double error = sigmoid(-INFLECTION / 2.0);
|
|
||||||
return Math.min(
|
|
||||||
Math.max(
|
|
||||||
(sigmoid(INFLECTION * (t - 0.5)) - error) / (1 - 2 * error),
|
|
||||||
0
|
|
||||||
),
|
|
||||||
1.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double rushIn( double t ) {
|
|
||||||
return 2 * smooth(t / 2.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double rushOut( double t ) {
|
|
||||||
return 2 * smooth(t / 2.0 + 0.5) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double doubleSmooth( double t ) {
|
|
||||||
if( t < 0.5 )
|
|
||||||
return 0.5 * smooth(2 * t);
|
|
||||||
else
|
|
||||||
return 0.5 * (1 + smooth(2 * t - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double hobbit( double t ) {
|
|
||||||
double new_t = t < 0.5 ? 2 * t : 2 * (1 - t);
|
|
||||||
return smooth(new_t);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator wiggle() {
|
|
||||||
return Easing::wiggle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final DoubleUnaryOperator wiggle( final int wiggles ) {
|
|
||||||
return (t) -> Easing.wiggle(t, wiggles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double wiggle( double t ) {
|
|
||||||
return wiggle(t, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final double wiggle( double t, int wiggles ) {
|
|
||||||
return hobbit(t) * Math.sin(wiggles * Math.PI * t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double INFLECTION = 10.0;
|
|
||||||
|
|
||||||
public static final double sigmoid( double x ) {
|
|
||||||
return 1.0 / (1 + Math.exp(-x));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Easing() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class FadeAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
public static final int FADE_IN = 255;
|
|
||||||
|
|
||||||
public static final int FADE_OUT = 0;
|
|
||||||
|
|
||||||
private final Shape target;
|
|
||||||
|
|
||||||
private final int targetAlpha;
|
|
||||||
|
|
||||||
private Color fill, stroke;
|
|
||||||
|
|
||||||
private int fillAlpha, strokeAlpha;
|
|
||||||
|
|
||||||
public FadeAnimation( Shape target, int targetAlpha ) {
|
|
||||||
this(target, targetAlpha, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FadeAnimation( Shape target, int targetAlpha, int runtime ) {
|
|
||||||
this(target, targetAlpha, runtime, DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FadeAnimation( Shape target, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
this(target, 0, runtime, easing);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FadeAnimation( Shape target, int targetAlpha, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
|
|
||||||
this.target = target;
|
|
||||||
this.targetAlpha = targetAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
fill = target.getFillColor();
|
|
||||||
fillAlpha = fill.getAlpha();
|
|
||||||
stroke = target.getStrokeColor();
|
|
||||||
strokeAlpha = stroke.getAlpha();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
target.setFillColor(fill, (int) Constants.interpolate(fillAlpha, targetAlpha, e));
|
|
||||||
target.setStrokeColor(stroke, (int) Constants.interpolate(strokeAlpha, targetAlpha, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
public class FillAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
private final Shape object;
|
|
||||||
|
|
||||||
private Color originFill;
|
|
||||||
|
|
||||||
private final Color targetFill;
|
|
||||||
|
|
||||||
public FillAnimation( Shape target, Color newFill, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
|
|
||||||
this.object = target;
|
|
||||||
targetFill = newFill;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
originFill = object.getFillColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
object.setFillColor(Color.interpolate(originFill, targetFill, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.shapes.Circle;
|
|
||||||
import schule.ngb.zm.shapes.Ellipse;
|
|
||||||
import schule.ngb.zm.shapes.Rectangle;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
public class MorphAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
private Shape object, original, target;
|
|
||||||
|
|
||||||
public MorphAnimation( Shape object, Shape target, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
|
|
||||||
this.original = object.copy();
|
|
||||||
this.object = object;
|
|
||||||
this.target = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
object.setX(Constants.interpolate(original.getX(), target.getX(), e));
|
|
||||||
object.setY(Constants.interpolate(original.getY(), target.getY(), e));
|
|
||||||
object.setFillColor(Color.interpolate(original.getFillColor(), target.getFillColor(), e));
|
|
||||||
object.setStrokeColor(Color.interpolate(original.getStrokeColor(), target.getStrokeColor(), e));
|
|
||||||
object.rotateTo(Constants.interpolate(original.getRotation(), target.getRotation(), e));
|
|
||||||
object.scale(Constants.interpolate(original.getScale(), target.getScale(), e));
|
|
||||||
object.setStrokeWeight(Constants.interpolate(original.getStrokeWeight(), target.getStrokeWeight(), e));
|
|
||||||
|
|
||||||
if( object instanceof Rectangle ) {
|
|
||||||
Rectangle r = (Rectangle)object;
|
|
||||||
r.setWidth(Constants.interpolate(original.getWidth(), target.getWidth(), e));
|
|
||||||
r.setHeight(Constants.interpolate(original.getHeight(), target.getHeight(), e));
|
|
||||||
} else if( object instanceof Circle ) {
|
|
||||||
Circle r = (Circle)object;
|
|
||||||
r.setRadius(Constants.interpolate(original.getWidth()*.5, target.getWidth()*.5, e));
|
|
||||||
} else if( object instanceof Ellipse ) {
|
|
||||||
Ellipse r = (Ellipse)object;
|
|
||||||
r.setWidth(Constants.interpolate(original.getWidth(), target.getWidth(), e));
|
|
||||||
r.setHeight(Constants.interpolate(original.getHeight(), target.getHeight(), e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
public class MoveAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
private final Shape object;
|
|
||||||
|
|
||||||
private final double targetX, targetY;
|
|
||||||
|
|
||||||
private double originX, originY;
|
|
||||||
|
|
||||||
|
|
||||||
public MoveAnimation( Shape target, double targetX, double targetY, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
|
|
||||||
this.object = target;
|
|
||||||
this.targetX = targetX;
|
|
||||||
this.targetY = targetY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
originX = object.getX();
|
|
||||||
originY = object.getY();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
object.setX(Constants.interpolate(originX, targetX, e));
|
|
||||||
object.setY(Constants.interpolate(originY, targetY, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
public class RotateAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
private Shape object;
|
|
||||||
|
|
||||||
private double oA, tA;
|
|
||||||
|
|
||||||
public RotateAnimation( Shape object, double angle, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
|
|
||||||
this.object = object;
|
|
||||||
oA = object.getRotation();
|
|
||||||
tA = angle;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
object.rotateTo(Constants.interpolate(oA, tA, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
public class StrokeAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
private Shape object;
|
|
||||||
|
|
||||||
private Color oFill, tFill;
|
|
||||||
|
|
||||||
public StrokeAnimation( Shape object, Color newStroke, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
|
|
||||||
this.object = object;
|
|
||||||
oFill = object.getFillColor();
|
|
||||||
tFill = newStroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
object.setStrokeColor(Color.interpolate(oFill, tFill, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package schule.ngb.zm.anim;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.Options;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
public class WaveAnimation extends Animation<Shape> {
|
|
||||||
|
|
||||||
private Shape object;
|
|
||||||
|
|
||||||
private double strength, sinOffset, previousDelta = 0.0;
|
|
||||||
|
|
||||||
private Options.Direction dir;
|
|
||||||
|
|
||||||
public WaveAnimation( Shape target, double strength, Options.Direction dir, double sinOffset, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
super(runtime, easing);
|
|
||||||
this.object = target;
|
|
||||||
this.dir = dir;
|
|
||||||
this.strength = strength;
|
|
||||||
this.sinOffset = sinOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getAnimationTarget() {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void animate( double e ) {
|
|
||||||
double delta = this.strength * Constants.sin(Constants.interpolate(0.0, Constants.TWO_PI, e) + sinOffset);
|
|
||||||
object.move((delta - previousDelta) * dir.x, (delta - previousDelta) * dir.y);
|
|
||||||
previousDelta = delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dieses Paket enthält Klassen zur Animation von
|
|
||||||
* {@link schule.ngb.zm.shapes.Shape} Objekten auf einem
|
|
||||||
* {@link schule.ngb.zm.layers.ShapesLayer}.
|
|
||||||
* <p>
|
|
||||||
* Mit den Animationsklassen lassen sich neben {@code Shape} Objekten aber auch
|
|
||||||
* andere Objekte animieren.
|
|
||||||
* <p>
|
|
||||||
* Das Paket setzt auf den funktionalen Programmierschnittstellen von Java auf
|
|
||||||
* und kann als Einführung in das Paradigma dienen.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm.anim;
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
package schule.ngb.zm.layers;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.Layer;
|
|
||||||
import schule.ngb.zm.Options;
|
|
||||||
|
|
||||||
import java.awt.GradientPaint;
|
|
||||||
import java.awt.Paint;
|
|
||||||
import java.awt.RadialGradientPaint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Ebene, die nur aus einer Farbe (oder einem Farbverlauf) besteht.
|
|
||||||
* <p>
|
|
||||||
* Ein {@code ColorLayer} ist eine der drei Standardebenen der
|
|
||||||
* {@link schule.ngb.zm.Zeichenmaschine}.
|
|
||||||
* <p>
|
|
||||||
* Die Farbe der Ebene kann beliebig gesetzt werden und kann gut als
|
|
||||||
* Hintergrundfarbe für eine Szene dienen, oder als halbtransparente
|
|
||||||
* "Abdeckung", wenn ein {@code ColorLayer} über den anderen Ebenen eingefügt
|
|
||||||
* wird.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class ColorLayer extends Layer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Farbe der Ebene.
|
|
||||||
*/
|
|
||||||
private Color color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verlauf der Ebene, falls verwendet.
|
|
||||||
*/
|
|
||||||
private Paint background;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Farbebene mit der angegebenen Farbe.
|
|
||||||
*
|
|
||||||
* @param color Die Hintergrundfarbe.
|
|
||||||
*/
|
|
||||||
public ColorLayer( Color color ) {
|
|
||||||
this.color = color;
|
|
||||||
this.background = color.getJavaColor();
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Farbebene mit der angegebenen Größe und Farbe.
|
|
||||||
*
|
|
||||||
* @param width Breite der Ebene.
|
|
||||||
* @param height Höhe der Ebene.
|
|
||||||
* @param color Die Hintergrundfarbe.
|
|
||||||
*/
|
|
||||||
public ColorLayer( int width, int height, Color color ) {
|
|
||||||
super(width, height);
|
|
||||||
this.color = color;
|
|
||||||
this.background = color.getJavaColor();
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSize( int width, int height ) {
|
|
||||||
super.setSize(width, height);
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die aktuelle Hintergrundfarbe der Ebene.
|
|
||||||
*/
|
|
||||||
public Color getColor() {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Ebene auf die angegebene Farbe.
|
|
||||||
*
|
|
||||||
* @param color Die neue Hintergrundfarbe.
|
|
||||||
*/
|
|
||||||
public void setColor( Color color ) {
|
|
||||||
this.color = color;
|
|
||||||
this.background = color.getJavaColor();
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Ebene auf einen Grauwert mit der angegebenen
|
|
||||||
* Intensität. 0 entspricht schwarz, 255 entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int)
|
|
||||||
*/
|
|
||||||
public void setColor( int gray ) {
|
|
||||||
setColor(gray, gray, gray, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Ebene auf einen Grauwert mit der angegebenen
|
|
||||||
* Intensität und dem angegebenen Transparenzwert. Der Grauwert 0 entspricht
|
|
||||||
* schwarz, 255 entspricht weiß.
|
|
||||||
*
|
|
||||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int)
|
|
||||||
*/
|
|
||||||
public void setColor( int gray, int alpha ) {
|
|
||||||
setColor(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Ebene auf die Farbe mit den angegebenen Rot-, Grün-
|
|
||||||
* und Blauanteilen.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @see Color#Color(int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
public void setColor( int red, int green, int blue ) {
|
|
||||||
setColor(red, green, blue, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Farbe der Ebene auf die Farbe mit den angegebenen Rot-, Grün-
|
|
||||||
* und Blauanteilen und dem angegebenen Transparenzwert.
|
|
||||||
*
|
|
||||||
* @param red Der Rotanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param green Der Grünanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param blue Der Blauanteil der Farbe zwischen 0 und 255.
|
|
||||||
* @param alpha Ein Transparenzwert zwischen 0 und 25
|
|
||||||
* @see Color#Color(int, int, int, int)
|
|
||||||
* @see <a
|
|
||||||
* href="https://de.wikipedia.org/wiki/RGB-Farbraum">https://de.wikipedia.org/wiki/RGB-Farbraum</a>
|
|
||||||
*/
|
|
||||||
public void setColor( int red, int green, int blue, int alpha ) {
|
|
||||||
setColor(new Color(red, green, blue, alpha));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung der Ebene auf einen linearen Farbverlauf, der in die
|
|
||||||
* angegebene Richtung verläuft.
|
|
||||||
*
|
|
||||||
* @param from Farbe am Startpunkt.
|
|
||||||
* @param to Farbe am Endpunkt.
|
|
||||||
* @param dir Richtung des Farbverlaufs.
|
|
||||||
*/
|
|
||||||
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
|
||||||
double halfW = getWidth() * .5;
|
|
||||||
double halfH = getHeight() * .5;
|
|
||||||
|
|
||||||
Options.Direction inv = dir.inverse();
|
|
||||||
int fromX = (int) (halfW + inv.x * halfW);
|
|
||||||
int fromY = (int) (halfH + inv.y * halfH);
|
|
||||||
|
|
||||||
int toX = (int) (halfW + dir.x * halfW);
|
|
||||||
int toY = (int) (halfH + dir.y * halfH);
|
|
||||||
|
|
||||||
setGradient(fromX, fromY, from, toX, toY, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung der Ebene auf einen linearen Farbverlauf, der am Punkt
|
|
||||||
* ({@code fromX}, {@code fromY}) mit der Farbe {@code from} startet und am
|
|
||||||
* Punkt (({@code toX}, {@code toY}) mit der Farbe {@code to} endet.
|
|
||||||
*
|
|
||||||
* @param fromX x-Koordinate des Startpunktes.
|
|
||||||
* @param fromY y-Koordinate des Startpunktes.
|
|
||||||
* @param from Farbe am Startpunkt.
|
|
||||||
* @param toX x-Koordinate des Endpunktes.
|
|
||||||
* @param toY y-Koordinate des Endpunktes.
|
|
||||||
* @param to Farbe am Endpunkt.
|
|
||||||
*/
|
|
||||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
|
||||||
this.color = from;
|
|
||||||
background = new GradientPaint(
|
|
||||||
(float) fromX, (float) fromY, from.getJavaColor(),
|
|
||||||
(float) toX, (float) toY, to.getJavaColor()
|
|
||||||
);
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung der Ebene auf einen kreisförmigen (radialen)
|
|
||||||
* Farbverlauf, der im Zentrum beginnt.
|
|
||||||
*
|
|
||||||
* @param from Farbe im Zentrum.
|
|
||||||
* @param to Farbe am Rand.
|
|
||||||
*/
|
|
||||||
public void setGradient( Color from, Color to ) {
|
|
||||||
setGradient(getWidth() * .5, getHeight() * .5, Math.min(getWidth() * .5, getHeight() * .5), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Füllung der Ebene auf einen kreisförmigen (radialen)
|
|
||||||
* Farbverlauf, mit dem Zentrum im Punkt ({@code centerX}, {@code centerY})
|
|
||||||
* und dem angegebenen Radius. Der Verlauf starte im Zentrum mit der Farbe
|
|
||||||
* {@code from} und endet am Rand des durch den Radius beschriebenen Kreises
|
|
||||||
* mit der Farbe {@code to}.
|
|
||||||
*
|
|
||||||
* @param centerX x-Koordinate des Kreismittelpunktes.
|
|
||||||
* @param centerY y-Koordinate des Kreismittelpunktes.
|
|
||||||
* @param radius Radius des Kreises.
|
|
||||||
* @param from Farbe im Zentrum des Kreises.
|
|
||||||
* @param to Farbe am Rand des Kreises.
|
|
||||||
*/
|
|
||||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
|
||||||
this.color = from;
|
|
||||||
background = new RadialGradientPaint(
|
|
||||||
(float) centerX, (float) centerY, (float) radius,
|
|
||||||
new float[]{0f, 1f},
|
|
||||||
new java.awt.Color[]{from.getJavaColor(), to.getJavaColor()});
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeichnet den Hintergrund der Ebene mit der gesetzten Füllung neu.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
drawing.setPaint(background);
|
|
||||||
drawing.fillRect(0, 0, getWidth(), getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package schule.ngb.zm.layers;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Drawable;
|
|
||||||
import schule.ngb.zm.Layer;
|
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein Layer um {@link Drawable} Objekte zu zeichnen.
|
|
||||||
* <p>
|
|
||||||
* Objekte, die das {@code Drawable} Interface implementieren, können der Ebene
|
|
||||||
* hinzugefügt werden. Die Ebene sorgt dafür, dass alle {@code Drawable}s einmal
|
|
||||||
* pro Frame über ihre {@link Drawable#draw(Graphics2D)} Methode gezeichnet.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class DrawableLayer extends Layer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liste der {@link Drawable}s.
|
|
||||||
*/
|
|
||||||
protected final List<Drawable> drawables;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Ebene bei jedem Aufruf von {@link #draw(Graphics2D)} geleert
|
|
||||||
* werden soll.
|
|
||||||
*/
|
|
||||||
protected boolean clearBeforeDraw = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine Ebene in der Standardgröße.
|
|
||||||
*/
|
|
||||||
public DrawableLayer() {
|
|
||||||
drawables = new LinkedList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine Ebene mit der angegebenen Größe.
|
|
||||||
*
|
|
||||||
* @param width Die Breite der Ebene.
|
|
||||||
* @param height Die Höhe der Ebene.
|
|
||||||
*/
|
|
||||||
public DrawableLayer( int width, int height ) {
|
|
||||||
super(width, height);
|
|
||||||
drawables = new LinkedList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fügt alle angegebenen {@code Drawable}s der Ebene hinzu.
|
|
||||||
*
|
|
||||||
* @param drawables Die {@code Drawable} Objekte.
|
|
||||||
*/
|
|
||||||
public void add( Drawable... drawables ) {
|
|
||||||
synchronized( this.drawables ) {
|
|
||||||
Collections.addAll(this.drawables, drawables);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt eine Liste aller {@code Drawable} Objekte dieser Ebene zurück.
|
|
||||||
*
|
|
||||||
* @return Die Liste der {@code Drawable} Objekte.
|
|
||||||
*/
|
|
||||||
public java.util.List<Drawable> getDrawables() {
|
|
||||||
return drawables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Ebene bei jedem Frame automatisch gelöscht wird.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn die Ebene vorm Zeichnen gelöscht wird,
|
|
||||||
* {@code false} sonst.
|
|
||||||
*/
|
|
||||||
public boolean isClearBeforeDraw() {
|
|
||||||
return clearBeforeDraw;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stellt ein, ob die Ebene vorm Zeichnen gelöscht werden soll.
|
|
||||||
*
|
|
||||||
* @param pClearBeforeDraw Ob die Ebene vorm Zeichnen gelöscht werden
|
|
||||||
* soll.
|
|
||||||
*/
|
|
||||||
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
|
||||||
this.clearBeforeDraw = pClearBeforeDraw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw( Graphics2D graphics ) {
|
|
||||||
if( clearBeforeDraw ) {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Drawable> it = List.copyOf(drawables);
|
|
||||||
for( Drawable d : it ) {
|
|
||||||
if( d.isVisible() ) {
|
|
||||||
d.draw(drawing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.draw(graphics);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
package schule.ngb.zm.layers;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Layer;
|
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Ebene, die ein statisches Bild anzeigt.
|
|
||||||
* <p>
|
|
||||||
* Die Ebene wird mit einem Bild initialisiert und zeigt dieses Bild als
|
|
||||||
* einzigen Inhalt an. Optional kann die Position des Bildes verändert werden,
|
|
||||||
* sodass es nicht im Ursprung der Ebene gezeichnet wird.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class ImageLayer extends Layer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Das Bild, das angezeigt wird.
|
|
||||||
*/
|
|
||||||
protected Image image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* x-Koordinate der oberen linken Ecke auf der Ebene.
|
|
||||||
*/
|
|
||||||
protected double x = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* y-Koordinate der oberen linken Ecke auf der Ebene.
|
|
||||||
*/
|
|
||||||
protected double y = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interner Schalter, ob die Ebene neu gezeichnet werden muss.
|
|
||||||
*/
|
|
||||||
protected boolean redraw = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine Bildebene in der Standardgröße aus der angegebenen
|
|
||||||
* Bildquelle.
|
|
||||||
*
|
|
||||||
* @param source Eine Bildquelle.
|
|
||||||
* @see ImageLoader#loadImage(String)
|
|
||||||
*/
|
|
||||||
public ImageLayer( String source ) {
|
|
||||||
image = ImageLoader.loadImage(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine Bildebene in der Standardgröße aus dem angegebenen Bild.
|
|
||||||
*
|
|
||||||
* @param image Ein Bild-Objekt.
|
|
||||||
*/
|
|
||||||
public ImageLayer( Image image ) {
|
|
||||||
this.image = image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine Bildebene in der angegebenen Größe aus dem angegebenen
|
|
||||||
* Bild.
|
|
||||||
*
|
|
||||||
* @param width Breite der Bildebene.
|
|
||||||
* @param height Höhe der Bildebene.
|
|
||||||
* @param image Ein Bild-Objekt.
|
|
||||||
*/
|
|
||||||
public ImageLayer( int width, int height, Image image ) {
|
|
||||||
super(width, height);
|
|
||||||
this.image = image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt das Bild der Ebene auf das angegebene Bild-Objekt.
|
|
||||||
*
|
|
||||||
* @param image Ein Bild-Objekt.
|
|
||||||
*/
|
|
||||||
public void setImage( Image image ) {
|
|
||||||
this.image = image;
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die x-Koordinate des Bildes in der Ebene.
|
|
||||||
*/
|
|
||||||
public double getX() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die {@code x}-Koordinate des BIldes in der Ebene auf den
|
|
||||||
* angegebenen Wert.
|
|
||||||
*
|
|
||||||
* @param pX Die x-Koordinate des Bildes.
|
|
||||||
*/
|
|
||||||
public void setX( double pX ) {
|
|
||||||
this.x = pX;
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die y-Koordinate des Bildes in der Ebene.
|
|
||||||
*/
|
|
||||||
public double getY() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die {@code y}-Koordinate des BIldes in der Ebene auf den
|
|
||||||
* angegebenen Wert.
|
|
||||||
*
|
|
||||||
* @param pY Die y-Koordinate des Bildes.
|
|
||||||
*/
|
|
||||||
public void setY( double pY ) {
|
|
||||||
this.y = pY;
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Löscht die Ebene und zeichnet das Bild neu.
|
|
||||||
* <p>
|
|
||||||
* In der Regel muss die Ebene nicht gelöscht werden, da sie automatisch neu
|
|
||||||
* gezeichnet wird, sobald sich das zugrundeliegende Bild ändert.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
super.clear();
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw( Graphics2D graphics ) {
|
|
||||||
if( redraw && visible ) {
|
|
||||||
drawing.drawImage(image, (int) x, (int) y, null);
|
|
||||||
redraw = false;
|
|
||||||
}
|
|
||||||
super.draw(graphics);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
package schule.ngb.zm.layers;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Layer;
|
|
||||||
import schule.ngb.zm.Updatable;
|
|
||||||
import schule.ngb.zm.anim.Animation;
|
|
||||||
import schule.ngb.zm.anim.AnimationFacade;
|
|
||||||
import schule.ngb.zm.anim.Easing;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Ebene um {@link Shape} Objekte zu zeichnen.
|
|
||||||
* <p>
|
|
||||||
* Ein {@code ShapesLayer} ist eine der drei Standardebenen der
|
|
||||||
* {@link schule.ngb.zm.Zeichenmaschine}.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class ShapesLayer extends Layer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
protected boolean clearBeforeDraw = true;
|
|
||||||
|
|
||||||
protected boolean updateShapes = true;
|
|
||||||
|
|
||||||
protected final List<Shape> shapes;
|
|
||||||
|
|
||||||
private final List<Animation<? extends Shape>> animations;
|
|
||||||
|
|
||||||
private final List<Updatable> updatables;
|
|
||||||
|
|
||||||
public ShapesLayer() {
|
|
||||||
super();
|
|
||||||
shapes = new LinkedList<>();
|
|
||||||
animations = new LinkedList<>();
|
|
||||||
updatables = new LinkedList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShapesLayer( int width, int height ) {
|
|
||||||
super(width, height);
|
|
||||||
shapes = new LinkedList<>();
|
|
||||||
animations = new LinkedList<>();
|
|
||||||
updatables = new LinkedList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Shape getShape( int index ) {
|
|
||||||
return shapes.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
|
||||||
for( Shape s : shapes ) {
|
|
||||||
if( shapeClass.isInstance(s) ) {
|
|
||||||
return shapeClass.cast(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Shape> getShapes() {
|
|
||||||
return shapes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <ST extends Shape> List<ST> getShapes( Class<ST> shapeClass ) {
|
|
||||||
List<ST> result = new LinkedList<>();
|
|
||||||
for( Shape s : shapes ) {
|
|
||||||
if( shapeClass.isInstance(s) ) {
|
|
||||||
result.add(shapeClass.cast(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add( Shape... shapes ) {
|
|
||||||
synchronized( this.shapes ) {
|
|
||||||
Collections.addAll(this.shapes, shapes);
|
|
||||||
|
|
||||||
for( Shape s : shapes ) {
|
|
||||||
if( Updatable.class.isInstance(s) ) {
|
|
||||||
updatables.add((Updatable) s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add( Collection<Shape> shapes ) {
|
|
||||||
synchronized( this.shapes ) {
|
|
||||||
this.shapes.addAll(shapes);
|
|
||||||
|
|
||||||
for( Shape s : shapes ) {
|
|
||||||
if( Updatable.class.isInstance(s) ) {
|
|
||||||
updatables.add((Updatable) s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove( Shape... shapes ) {
|
|
||||||
synchronized( this.shapes ) {
|
|
||||||
for( Shape s : shapes ) {
|
|
||||||
this.shapes.remove(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove( Collection<Shape> shapes ) {
|
|
||||||
synchronized( this.shapes ) {
|
|
||||||
this.shapes.removeAll(shapes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeAll() {
|
|
||||||
synchronized( shapes ) {
|
|
||||||
shapes.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showAll() {
|
|
||||||
synchronized( shapes ) {
|
|
||||||
for( Shape s : shapes ) {
|
|
||||||
s.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideAll() {
|
|
||||||
synchronized( shapes ) {
|
|
||||||
for( Shape s : shapes ) {
|
|
||||||
s.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S extends Shape> void play( Animation<S> anim ) {
|
|
||||||
this.animations.add(anim);
|
|
||||||
anim.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public final void play( Animation<? extends Shape>... anims ) {
|
|
||||||
for( Animation<? extends Shape> anim : anims ) {
|
|
||||||
this.animations.add(anim);
|
|
||||||
anim.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S extends Shape> void play( Animation<S> anim, int runtime ) {
|
|
||||||
play(anim, runtime, Easing.DEFAULT_EASING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S extends Shape> void play( Animation<S> anim, int runtime, DoubleUnaryOperator easing ) {
|
|
||||||
AnimationFacade<S> facade = new AnimationFacade<>(anim, runtime, easing);
|
|
||||||
play(facade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update( double delta ) {
|
|
||||||
if( updateShapes ) {
|
|
||||||
synchronized( shapes ) {
|
|
||||||
List<Updatable> uit = List.copyOf(updatables);
|
|
||||||
for( Updatable u : uit ) {
|
|
||||||
if( u.isActive() ) {
|
|
||||||
u.update(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Iterator<Updatable> uit = updatables.iterator();
|
|
||||||
while( uit.hasNext() ) {
|
|
||||||
Updatable u = uit.next();
|
|
||||||
if( u.isActive() ) {
|
|
||||||
u.update(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<Animation<? extends Shape>> it = animations.iterator();
|
|
||||||
while( it.hasNext() ) {
|
|
||||||
Animation<? extends Shape> anim = it.next();
|
|
||||||
anim.update(delta);
|
|
||||||
|
|
||||||
if( !anim.isActive() ) {
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw( Graphics2D graphics ) {
|
|
||||||
if( clearBeforeDraw ) {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized( shapes ) {
|
|
||||||
List<Shape> it = List.copyOf(shapes);
|
|
||||||
for( Shape s : it ) {
|
|
||||||
if( s.isVisible() ) {
|
|
||||||
s.draw(drawing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.draw(graphics);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dieses Paket enthält implementationen der abstrakten
|
|
||||||
* {@link schule.ngb.zm.Layer} Klasse.
|
|
||||||
* <p>
|
|
||||||
* {@code Layer} sind Ebenen, die der {@link schule.ngb.zm.Zeichenleinwand}
|
|
||||||
* hinzugefügt und pro Frame gerendert werden.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm.layers;
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package schule.ngb.zm.media;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schnittstelle für Audio-Medien.
|
|
||||||
*
|
|
||||||
* <h2>MP3-Dateien verwenden</h2>
|
|
||||||
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
|
||||||
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
|
||||||
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
|
||||||
* Details zur Verwendung können in der <a
|
|
||||||
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
|
||||||
* der Zeichenmaschine</a> nachgelesen werden.
|
|
||||||
*/
|
|
||||||
public interface Audio {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die Quelle, aus der das Medium geladen wurde.
|
|
||||||
*/
|
|
||||||
String getSource();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob das Medium gerade abgespielt wird.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn das Medium abgespielt wird, {@code false}
|
|
||||||
* sonst.
|
|
||||||
*/
|
|
||||||
boolean isPlaying();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn
|
|
||||||
* {@code isLooping() == true} gilt, dann muss auch immer
|
|
||||||
* {@code isPlaying() == true} gelten.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn das Medium in einer Schleife abgespielt wird,
|
|
||||||
* {@code false} sonst.
|
|
||||||
*/
|
|
||||||
boolean isLooping();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legt die Lautstärke des Mediums beim Abspielen fest.
|
|
||||||
* <p>
|
|
||||||
* Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton
|
|
||||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
|
||||||
* Mediums. Negative Werte setzen die Lautstärke aud 0.
|
|
||||||
*
|
|
||||||
* @param volume Die neue Lautstärke zwischen 0 und 1.
|
|
||||||
* @see <a
|
|
||||||
* href="https://stackoverflow.com/a/40698149">https://stackoverflow.com/a/40698149</a>
|
|
||||||
*/
|
|
||||||
void setVolume( double volume );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die aktuelle Lautstärke dieses Mediums.
|
|
||||||
* <p>
|
|
||||||
* Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton
|
|
||||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
|
||||||
* Mediums.
|
|
||||||
*
|
|
||||||
* @return Die Lautstärke als linear skalierter Wert.
|
|
||||||
*/
|
|
||||||
double getVolume();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Startet die Wiedergabe des Mediums. Das Audio-Medium wird einmal
|
|
||||||
* abgespielt und stoppt dann.
|
|
||||||
* <p>
|
|
||||||
* Die Methode beendet sofort und die Wiedergabe erfolgt im Hintergrund.
|
|
||||||
* Soll die Programmausführung erst nach Wiedergabe des Mediums fortgesetzt
|
|
||||||
* werden, sollte {@link #playAndWait()} verwendet werden.
|
|
||||||
* <p>
|
|
||||||
* Soll die Wiedergabe im Hintergrund ablaufen, aber dennoch auf das Ende
|
|
||||||
* reagiert werden, kann ein
|
|
||||||
* {@link #addAudioListener(AudioListener) AudioListener} verwendet werden.
|
|
||||||
*/
|
|
||||||
void play();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Startet die Wiedergabe des Mediums und blockiert das Programm, bis die
|
|
||||||
* Wiedergabe beendet ist.
|
|
||||||
*/
|
|
||||||
void playAndWait();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
|
|
||||||
* startet die Wiedergabe im Hintergrund und beendet dann sofort. Um die
|
|
||||||
* Wiedergabe zu stoppen, muss {@link #stop()} aufgerufen werden.
|
|
||||||
*/
|
|
||||||
void loop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt
|
|
||||||
* ({@code isPlaying() == false}), dann passiert nichts.
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stoppt die Wiedergabe und gibt alle Ressourcen, die für das Medium
|
|
||||||
* verwendet werden, frei.
|
|
||||||
*/
|
|
||||||
void dispose();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fügt dem Medium das angegebene Objekt als {@code AudioListener} hinzu,
|
|
||||||
* der bei Start und Stopp der Wiedergabe informiert wird.
|
|
||||||
*
|
|
||||||
* @param listener Das Listener-Objekt.
|
|
||||||
*/
|
|
||||||
void addAudioListener( AudioListener listener );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt den angegebenen {@code AudioListener} vom Medium.
|
|
||||||
*
|
|
||||||
* @param listener Das Listener-Objekt.
|
|
||||||
*/
|
|
||||||
void removeAudioListener( AudioListener listener );
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package schule.ngb.zm.media;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.events.Listener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface für Klassen, die auf das starten und stoppen der Wiedergabe von
|
|
||||||
* {@link Audio}-Objekten reagieren möchten.
|
|
||||||
* <p>
|
|
||||||
* Implementierende Klassen können sich bei einem Auido-Objekt mittels
|
|
||||||
* {@link Audio#addAudioListener(AudioListener)} anmelden und werden über die
|
|
||||||
* jeweilige Methode informiert, sobald die Wiedergabe gestartet oder gestoppt
|
|
||||||
* wird.
|
|
||||||
*/
|
|
||||||
public interface AudioListener extends Listener<Audio> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums startet, dem
|
|
||||||
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
|
||||||
* hinzugefügt wurde.
|
|
||||||
*
|
|
||||||
* @param source Das Audio-Medium, dessen Wiedergabe gestartet wurde.
|
|
||||||
*/
|
|
||||||
void playbackStarted( Audio source );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums stoppt, dem
|
|
||||||
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
|
||||||
* hinzugefügt wurde.
|
|
||||||
*
|
|
||||||
* @param source Das Audio-Medium, dessen Wiedergabe gestoppt wurde.
|
|
||||||
*/
|
|
||||||
void playbackStopped( Audio source );
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
package schule.ngb.zm.media;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.util.events.EventDispatcher;
|
|
||||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein Mixer ist eine Sammlung mehrerer {@link Audio Audio-Medien}, die
|
|
||||||
* gemeinsam kontrolliert werden können.
|
|
||||||
* <p>
|
|
||||||
* Im einfachsten Fall kann die Audio-Gruppe gemeinsam gestartet und gestoppt
|
|
||||||
* werden. Ein Mixer kann die Lautstärke der Medien in Relation zueinander
|
|
||||||
* setzen. Dazu wird jedem Medium ein Faktor mitgegeben. Ein Medium mit dem
|
|
||||||
* Faktor 0.5 ist dann halb so laut wie eines, mit dem Faktor 1.0.
|
|
||||||
* <p>
|
|
||||||
* Darüber hinaus kann ein Mixer Effekte wie einen
|
|
||||||
* {@link #fade(double, int) fadeIn} auf die Medien anwenden.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class Mixer implements Audio, AudioListener {
|
|
||||||
|
|
||||||
private List<AudioWrapper> audios;
|
|
||||||
|
|
||||||
private float volume = 0.8f;
|
|
||||||
|
|
||||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
|
||||||
|
|
||||||
public Mixer() {
|
|
||||||
this.audios = new ArrayList<>(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSource() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private AudioWrapper findWrapper( Audio pAudio ) {
|
|
||||||
for( AudioWrapper aw: audios ) {
|
|
||||||
if( aw.audio == pAudio ) {
|
|
||||||
return aw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean contains( Audio pAudio ) {
|
|
||||||
return findWrapper(pAudio) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add( Audio pAudio ) {
|
|
||||||
add(pAudio, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fügt ein Audio-Objekt dem Mixer mit dem angegebenen Lautstärke-Faktor
|
|
||||||
* hinzu.
|
|
||||||
* <p>
|
|
||||||
* Der Lautstärke-Faktor setzt die Lautstärke des Audio-Objektes relativ zur
|
|
||||||
* Lautstärke des Mixers. Bei einem Faktor von 1.0 wird die Lautstärke des
|
|
||||||
* Mixers übernommen. Bei einem Wert von 0.5 wird das Objekt halb so laut
|
|
||||||
* abgespielt. Auf diese Weise lässt sich die Lautstärke aller Audio-Objekte
|
|
||||||
* des Mixers gleichzeitig anpassen, während ihre relative Lautstärke
|
|
||||||
* zueinander gleich bleibt.
|
|
||||||
*
|
|
||||||
* @param pAudio Ein Audio-Objekt.
|
|
||||||
* @param pVolumeFactor Der Lautstärke-Faktor.
|
|
||||||
*/
|
|
||||||
public void add( Audio pAudio, double pVolumeFactor ) {
|
|
||||||
if( !contains(pAudio) ) {
|
|
||||||
audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor));
|
|
||||||
} else {
|
|
||||||
findWrapper(pAudio).volumeFactor = (float) pVolumeFactor;
|
|
||||||
}
|
|
||||||
pAudio.setVolume(pVolumeFactor * volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entfernt die das angegebene Audio-Objekt aus dem Mixer. Ist das Objekt
|
|
||||||
* nicht Teil des Mixers, passiert nichts.
|
|
||||||
*
|
|
||||||
* @param pAudio Ein Audio-Objekt.
|
|
||||||
*/
|
|
||||||
public void remove( Audio pAudio ) {
|
|
||||||
Iterator<AudioWrapper> it = audios.listIterator();
|
|
||||||
while( it.hasNext() ) {
|
|
||||||
AudioWrapper aw = it.next();
|
|
||||||
if( aw.audio == pAudio ) {
|
|
||||||
it.remove();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeAll() {
|
|
||||||
audios.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPlaying() {
|
|
||||||
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLooping() {
|
|
||||||
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVolume( double volume ) {
|
|
||||||
this.volume = volume < 0 ? 0f : (float) volume;
|
|
||||||
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * volume));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getVolume() {
|
|
||||||
return volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void play() {
|
|
||||||
audios.stream().forEach(aw -> aw.audio.play());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playAndWait() {
|
|
||||||
audios.stream().forEach(aw -> aw.audio.play());
|
|
||||||
while( audios.stream().anyMatch(aw -> aw.audio.isPlaying()) ) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(10);
|
|
||||||
} catch( InterruptedException e ) {
|
|
||||||
// Just keep waiting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loop() {
|
|
||||||
audios.stream().forEach(aw -> aw.audio.loop());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
audios.stream().forEach(aw -> aw.audio.stop());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dispose() {
|
|
||||||
if( isPlaying() ) {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
audios.stream().forEach(aw -> aw.audio.dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ändert die Lautstärke aller hinzugefügten Audiomedien in der angegebenen
|
|
||||||
* Zeit schrittweise, bis die angegebene Lautstärke erreicht ist.
|
|
||||||
* <p>
|
|
||||||
* Zu beachten ist, dass die Lautstärke des Mixers angepasst wird. Das
|
|
||||||
* bedeutet, dass die Lautstärke der hinzugefügten Medien mit ihrem
|
|
||||||
* Lautstärkefaktor multipliziert werden. Die Medien haben am Ende also
|
|
||||||
* nicht unbedingt die Lautstärke {@code to}.
|
|
||||||
*
|
|
||||||
* @param to Der Zielwert für die Lautstärke.
|
|
||||||
* @param time Die Zeit, nach der die Änderung abgeschlossen sein soll.
|
|
||||||
*/
|
|
||||||
public void fade( final double to, final int time ) {
|
|
||||||
TaskRunner.run(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
final long start = System.currentTimeMillis();
|
|
||||||
double t = 0.0;
|
|
||||||
double from = volume;
|
|
||||||
do {
|
|
||||||
setVolume(Constants.interpolate(from, to, t));
|
|
||||||
t = (double) (System.currentTimeMillis() - start) / (double) time;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000 / Constants.framesPerSecond);
|
|
||||||
} catch( InterruptedException e ) {
|
|
||||||
// Keep waiting
|
|
||||||
}
|
|
||||||
} while( t < 1.0 );
|
|
||||||
setVolume(to);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playbackStarted( Audio source ) {
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent("start", Mixer.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playbackStopped( Audio source ) {
|
|
||||||
if( !isPlaying() ) {
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent("stop", Mixer.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAudioListener( AudioListener listener ) {
|
|
||||||
initializeEventDispatcher().addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAudioListener( AudioListener listener ) {
|
|
||||||
initializeEventDispatcher().removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
|
||||||
* aufgerufen, sobald sich auch ein Listener registrieren möchte.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
|
||||||
if( eventDispatcher == null ) {
|
|
||||||
eventDispatcher = new EventDispatcher<>();
|
|
||||||
eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
|
|
||||||
eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
|
|
||||||
}
|
|
||||||
return eventDispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AudioWrapper {
|
|
||||||
|
|
||||||
Audio audio;
|
|
||||||
|
|
||||||
float volumeFactor;
|
|
||||||
|
|
||||||
public AudioWrapper( Audio audio, float volumeFactor ) {
|
|
||||||
this.audio = audio;
|
|
||||||
this.volumeFactor = volumeFactor;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
package schule.ngb.zm.media;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
import schule.ngb.zm.util.events.EventDispatcher;
|
|
||||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
|
||||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Musik, die abgespielt werden kann.
|
|
||||||
* <p>
|
|
||||||
* Im Gegensatz zu einem {@link Sound} wird {@code Music} für längere
|
|
||||||
* Audiodateien benutzt, die zum Beispiel als Hintergrundmusik gespielt werden.
|
|
||||||
* Die Audiodaten werden daher nicht vollständig in den Speicher geladen,
|
|
||||||
* sondern direkt aus der Quelle gestreamt und direkt wiedergegeben.
|
|
||||||
* <p>
|
|
||||||
* Daher ist es nicht möglich, die länge der Musik im Vorfeld abzufragen oder zu
|
|
||||||
* einer bestimmten Stelle im Stream zu springen.
|
|
||||||
*
|
|
||||||
* <h2>MP3-Dateien verwenden</h2>
|
|
||||||
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
|
||||||
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
|
||||||
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
|
||||||
* Details zur Verwendung können in der <a
|
|
||||||
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
|
||||||
* der Zeichenmaschine</a> nachgelesen werden.
|
|
||||||
*/
|
|
||||||
// TODO: Wann sollten Listener beim Loopen informiert werden? Nach jedem Loop oder erst ganz am Ende?
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class Music implements Audio {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Größe des verwendeten Input-Puffers für die Audiodaten.
|
|
||||||
*/
|
|
||||||
private static final int BUFFER_SIZE = 4096;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob der Sound aktuell abgespielt wird.
|
|
||||||
*/
|
|
||||||
private boolean playing = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
|
||||||
*/
|
|
||||||
private boolean looping = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Quelle der Audiodaten.
|
|
||||||
*/
|
|
||||||
private String audioSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der {@link AudioInputStream}, um die Audiosdaten zu lesen. {@code null},
|
|
||||||
* falls noch kein Stream geöffnet wurde.
|
|
||||||
*/
|
|
||||||
private AudioInputStream audioStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die {@link SourceDataLine} für die Ausgabe. {@code null}, falls die
|
|
||||||
* Audiodatei noch nicht geöffnet wurde.
|
|
||||||
*/
|
|
||||||
private SourceDataLine audioLine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die aktuelle Lautstärke des Mediums.
|
|
||||||
*/
|
|
||||||
private float volume = 0.8f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatcher für Audio-Events (start und stop).
|
|
||||||
*/
|
|
||||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine Musik aus der angegebenen Audioquelle.
|
|
||||||
*
|
|
||||||
* @param audioSource Quelle der Audiodaten.
|
|
||||||
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
|
||||||
* @see ResourceStreamProvider#getResourceURL(String)
|
|
||||||
*/
|
|
||||||
public Music( String audioSource ) {
|
|
||||||
Validator.requireNotNull(audioSource, "audioSource");
|
|
||||||
this.audioSource = audioSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSource() {
|
|
||||||
return audioSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPlaying() {
|
|
||||||
return playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLooping() {
|
|
||||||
if( !playing ) {
|
|
||||||
looping = false;
|
|
||||||
}
|
|
||||||
return looping;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVolume( double volume ) {
|
|
||||||
this.volume = volume < 0 ? 0f : (float) volume;
|
|
||||||
if( audioLine != null ) {
|
|
||||||
applyVolume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getVolume() {
|
|
||||||
return volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wendet die Lautstärke vor dem Abspielen auf den Audiostream an.
|
|
||||||
*/
|
|
||||||
private void applyVolume() {
|
|
||||||
FloatControl gainControl =
|
|
||||||
(FloatControl) audioLine.getControl(FloatControl.Type.MASTER_GAIN);
|
|
||||||
|
|
||||||
float vol = 20f * (float) Math.log10(volume);
|
|
||||||
// vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum());
|
|
||||||
gainControl.setValue(vol);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void play() {
|
|
||||||
if( openLine() ) {
|
|
||||||
TaskRunner.run(this::stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playAndWait() {
|
|
||||||
if( openLine() ) {
|
|
||||||
stream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loop() {
|
|
||||||
looping = true;
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
playing = false;
|
|
||||||
looping = false;
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void dispose() {
|
|
||||||
if( audioLine != null ) {
|
|
||||||
if( audioLine.isRunning() ) {
|
|
||||||
playing = false;
|
|
||||||
audioLine.stop();
|
|
||||||
}
|
|
||||||
if( audioLine.isOpen() ) {
|
|
||||||
audioLine.drain();
|
|
||||||
audioLine.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if( audioStream != null ) {
|
|
||||||
audioStream.close();
|
|
||||||
}
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
}
|
|
||||||
|
|
||||||
audioLine = null;
|
|
||||||
audioStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Startet den Stream der Audiodaten und damit die Wiedergabe.
|
|
||||||
* <p>
|
|
||||||
* Die {@link #audioLine} muss vorher mit {@link #openLine()} geöffnet
|
|
||||||
* werden, ansonsten passiert nichts.
|
|
||||||
*/
|
|
||||||
private synchronized void stream() {
|
|
||||||
if( audioLine == null ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
audioLine.start();
|
|
||||||
playing = true;
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent("start", Music.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bytesBuffer = new byte[BUFFER_SIZE];
|
|
||||||
int bytesRead = -1;
|
|
||||||
|
|
||||||
try {
|
|
||||||
while( playing && (bytesRead = audioStream.read(bytesBuffer)) != -1 ) {
|
|
||||||
audioLine.write(bytesBuffer, 0, bytesRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
audioLine.drain();
|
|
||||||
audioLine.stop();
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.warn(ex, "Error while playing Music source <%s>", audioSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the remaining audio to play
|
|
||||||
/*while( audioLine.isRunning() ) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(10);
|
|
||||||
} catch( InterruptedException ex ) {
|
|
||||||
// Just keep waiting ...
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
playing = false;
|
|
||||||
streamingStopped();
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent("stop", Music.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Öffnet eine {@link SourceDataLine} für die
|
|
||||||
* {@link #audioSource Audioquelle} und bereitet die Wiedergabe vor. Es wird
|
|
||||||
* noch nichts abgespielt.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn die Line geöffnet werden konnte, {@code false}
|
|
||||||
* sonst.
|
|
||||||
*/
|
|
||||||
private boolean openLine() {
|
|
||||||
if( audioLine != null ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
URL url = ResourceStreamProvider.getResourceURL(audioSource);
|
|
||||||
if( url != null ) {
|
|
||||||
final AudioInputStream inStream = AudioSystem.getAudioInputStream(url);
|
|
||||||
AudioFormat format = inStream.getFormat();
|
|
||||||
|
|
||||||
final int ch = format.getChannels();
|
|
||||||
final float rate = format.getSampleRate();
|
|
||||||
AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch * 2, rate, false);
|
|
||||||
|
|
||||||
DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat);
|
|
||||||
|
|
||||||
audioLine = (SourceDataLine) AudioSystem.getLine(info);
|
|
||||||
audioLine.open(outFormat);
|
|
||||||
applyVolume();
|
|
||||||
|
|
||||||
audioStream = AudioSystem.getAudioInputStream(outFormat, inStream);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource);
|
|
||||||
}
|
|
||||||
} catch( UnsupportedAudioFileException ex ) {
|
|
||||||
LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource);
|
|
||||||
} catch( LineUnavailableException ex ) {
|
|
||||||
LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource);
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
|
|
||||||
* Aufruf von {@link #stop()} oder weil keine Audiodaten mehr vorhanden
|
|
||||||
* sind.
|
|
||||||
* <p>
|
|
||||||
* Nach dem Ende des Streams wird {@link #dispose()} aufgerufen und, falls
|
|
||||||
* das Musikstück in einer Schleife abgespielt wird, der Stream direkt
|
|
||||||
* wieder gestartet.
|
|
||||||
*/
|
|
||||||
private void streamingStopped() {
|
|
||||||
dispose();
|
|
||||||
|
|
||||||
if( looping ) {
|
|
||||||
if( openLine() ) {
|
|
||||||
stream();
|
|
||||||
} else {
|
|
||||||
playing = false;
|
|
||||||
looping = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
playing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAudioListener( AudioListener listener ) {
|
|
||||||
initializeEventDispatcher().addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAudioListener( AudioListener listener ) {
|
|
||||||
initializeEventDispatcher().removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
|
||||||
* aufgerufen, sobald sich der erste Listener anmelden möchte.
|
|
||||||
*
|
|
||||||
* @return Der {@code EventDispatcher} für dieses Objekt.
|
|
||||||
*/
|
|
||||||
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
|
||||||
if( eventDispatcher == null ) {
|
|
||||||
eventDispatcher = new EventDispatcher<>();
|
|
||||||
eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
|
|
||||||
eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
|
|
||||||
}
|
|
||||||
return eventDispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(Music.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,349 +0,0 @@
|
|||||||
package schule.ngb.zm.media;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
import schule.ngb.zm.util.events.EventDispatcher;
|
|
||||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein kurzer Soundclip, der mehrmals wiederverwendet werden kann.
|
|
||||||
* <p>
|
|
||||||
* In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer
|
|
||||||
* Soundclips, die zusammen mit bestimmten Aktionen wiedergegeben werden (zum
|
|
||||||
* Beispiel, wenn die Spielfigur springt, wenn zwei Objekte kollidieren, usw.).
|
|
||||||
* Sounds werden vollständig in den Speicher geladen und können immer wieder,
|
|
||||||
* als Schleife oder auch nur Abschnittsweise, abgespielt werden.
|
|
||||||
* <p>
|
|
||||||
* Für längere Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher
|
|
||||||
* die Klasse {@link Music} an.
|
|
||||||
*
|
|
||||||
* <h2>MP3-Dateien verwenden</h2>
|
|
||||||
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
|
||||||
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
|
||||||
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
|
||||||
* Details zur Verwendung können in der <a
|
|
||||||
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
|
||||||
* der Zeichenmaschine</a> nachgelesen werden.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class Sound implements Audio {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob der Sound aktuell abgespielt wird.
|
|
||||||
*/
|
|
||||||
private boolean playing = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
|
||||||
*/
|
|
||||||
private boolean looping = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Quelle der Audiodaten.
|
|
||||||
*/
|
|
||||||
private String audioSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Der Clip, falls er schon geladen wurde. Ansonsten {@code null}.
|
|
||||||
*/
|
|
||||||
private Clip audioClip;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Ressourcen des Clips im Speicher nach dem nächsten Abspielen
|
|
||||||
* freigegeben werden sollen.
|
|
||||||
*/
|
|
||||||
private boolean disposeAfterPlay = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die aktuelle Lautstärke des Clips.
|
|
||||||
*/
|
|
||||||
private float volume = 0.8f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatcher für Audio-Events (start und stop).
|
|
||||||
*/
|
|
||||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen Sound aus der angegebene Quelle.
|
|
||||||
*
|
|
||||||
* @param source Quelle der Audiodaten.
|
|
||||||
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
|
||||||
* @see ResourceStreamProvider#getResourceURL(String)
|
|
||||||
*/
|
|
||||||
public Sound( String source ) {
|
|
||||||
Validator.requireNotNull(source, "source");
|
|
||||||
this.audioSource = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSource() {
|
|
||||||
return audioSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPlaying() {
|
|
||||||
// return audioClip != null && audioClip.isRunning();
|
|
||||||
return playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLooping() {
|
|
||||||
if( !playing ) {
|
|
||||||
looping = false;
|
|
||||||
}
|
|
||||||
return looping;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVolume( double volume ) {
|
|
||||||
this.volume = volume < 0 ? 0f : (float) volume;
|
|
||||||
if( audioClip != null ) {
|
|
||||||
applyVolume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getVolume() {
|
|
||||||
return volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wendet die Lautstärke vor dem Abspielen auf den Clip an.
|
|
||||||
*/
|
|
||||||
private void applyVolume() {
|
|
||||||
FloatControl gainControl =
|
|
||||||
(FloatControl) audioClip.getControl(FloatControl.Type.MASTER_GAIN);
|
|
||||||
|
|
||||||
float vol = 20f * (float) Math.log10(volume);
|
|
||||||
// vol = (float) Constants.limit(vol, gainControl.getMinimum(), gainControl.getMaximum());
|
|
||||||
gainControl.setValue(vol);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
looping = false;
|
|
||||||
if( audioClip.isRunning() ) {
|
|
||||||
audioClip.stop();
|
|
||||||
}
|
|
||||||
playing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void play() {
|
|
||||||
if( this.openClip() ) {
|
|
||||||
audioClip.start();
|
|
||||||
playing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playAndWait() {
|
|
||||||
this.play();
|
|
||||||
|
|
||||||
long audioLen = audioClip.getMicrosecondLength();
|
|
||||||
while( playing ) {
|
|
||||||
try {
|
|
||||||
long ms = (audioLen - audioClip.getMicrosecondPosition()) / 1000L;
|
|
||||||
Thread.sleep(ms);
|
|
||||||
} catch( InterruptedException ex ) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audioClip.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
|
||||||
* frei.
|
|
||||||
* <p>
|
|
||||||
* Der Aufruf ist effektiv gleich zu
|
|
||||||
* <pre><code>
|
|
||||||
* clip.playAndWait();
|
|
||||||
* clip.dispose();
|
|
||||||
* </code></pre>
|
|
||||||
* allerdings wird der aufrufende Thread nicht blockiert und
|
|
||||||
* {@link #dispose()} automatisch am Ende aufgerufen.
|
|
||||||
*/
|
|
||||||
public void playOnce() {
|
|
||||||
disposeAfterPlay = true;
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
|
||||||
* frei.
|
|
||||||
* <p>
|
|
||||||
* Der Aufruf entspricht
|
|
||||||
* <pre><code>
|
|
||||||
* clip.playAndWait();
|
|
||||||
* clip.dispose();
|
|
||||||
* </code></pre>
|
|
||||||
*/
|
|
||||||
public void playOnceAndWait() {
|
|
||||||
disposeAfterPlay = true;
|
|
||||||
playAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loop() {
|
|
||||||
loop(Clip.LOOP_CONTINUOUSLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen und stoppt
|
|
||||||
* die Wiedergabe dann.
|
|
||||||
* <p>
|
|
||||||
* Wird {@code count} auf {@link Clip#LOOP_CONTINUOUSLY} gesetzt (-1), wird
|
|
||||||
* der Clip unendlich oft wiederholt. Der Aufruf entspricht dann
|
|
||||||
* {@link #loop()}.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Wiederholungen.
|
|
||||||
*/
|
|
||||||
public void loop( int count ) {
|
|
||||||
if( count > 0 ) {
|
|
||||||
int loopCount = count;
|
|
||||||
if( loopCount != Clip.LOOP_CONTINUOUSLY ) {
|
|
||||||
if( loopCount <= 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Adjust Number of loops
|
|
||||||
loopCount -= 1;
|
|
||||||
}
|
|
||||||
if( openClip() ) {
|
|
||||||
looping = true;
|
|
||||||
audioClip.loop(loopCount);
|
|
||||||
playing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void dispose() {
|
|
||||||
if( audioClip != null ) {
|
|
||||||
if( audioClip.isRunning() ) {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
audioClip.close();
|
|
||||||
audioClip = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt, falls nötig, den {@link Clip} für die
|
|
||||||
* {@link #audioSource Audioquelle}.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn der Clip geöffnet werden konnte, {@code false}
|
|
||||||
* sonst.
|
|
||||||
*/
|
|
||||||
private synchronized boolean openClip() {
|
|
||||||
if( audioClip != null ) {
|
|
||||||
audioClip.setFramePosition(0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
URL url = ResourceStreamProvider.getResourceURL(audioSource);
|
|
||||||
if( url != null ) {
|
|
||||||
final AudioInputStream audioStream = AudioSystem.getAudioInputStream(url);
|
|
||||||
AudioFormat format = audioStream.getFormat();
|
|
||||||
DataLine.Info info = new DataLine.Info(Clip.class, format);
|
|
||||||
|
|
||||||
audioClip = (Clip) AudioSystem.getLine(info);
|
|
||||||
audioClip.addLineListener(new LineListener() {
|
|
||||||
@Override
|
|
||||||
public void update( LineEvent event ) {
|
|
||||||
if( event.getType() == LineEvent.Type.START ) {
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent("start", Sound.this);
|
|
||||||
}
|
|
||||||
} else if( event.getType() == LineEvent.Type.STOP ) {
|
|
||||||
playbackStopped();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
audioClip.open(audioStream);
|
|
||||||
applyVolume();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource);
|
|
||||||
}
|
|
||||||
} catch( UnsupportedAudioFileException ex ) {
|
|
||||||
LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource);
|
|
||||||
} catch( LineUnavailableException ex ) {
|
|
||||||
LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource);
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*@Override
|
|
||||||
public void update( LineEvent event ) {
|
|
||||||
LineEvent.Type type = event.getType();
|
|
||||||
|
|
||||||
if( type == LineEvent.Type.START ) {
|
|
||||||
playing = true;
|
|
||||||
} else if( type == LineEvent.Type.STOP ) {
|
|
||||||
playing = false;
|
|
||||||
if( disposeAfterPlay ) {
|
|
||||||
this.dispose();
|
|
||||||
disposeAfterPlay = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
|
|
||||||
* Aufruf von {@link #stop()} oder, weil die Wiedergabe nach
|
|
||||||
* {@link #playOnce()} beendet wurde.
|
|
||||||
* <p>
|
|
||||||
* Falls {@link #disposeAfterPlay} gesetzt ist, wird nach dem Ende der
|
|
||||||
* Wiedergabe {@link #dispose()} aufgerufen.
|
|
||||||
*/
|
|
||||||
private void playbackStopped() {
|
|
||||||
playing = false;
|
|
||||||
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent("stop", Sound.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( disposeAfterPlay ) {
|
|
||||||
this.dispose();
|
|
||||||
disposeAfterPlay = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAudioListener( AudioListener listener ) {
|
|
||||||
initializeEventDispatcher().addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAudioListener( AudioListener listener ) {
|
|
||||||
initializeEventDispatcher().removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
|
||||||
* aufgerufen, sobald sich der erste Listener anmelden möchte.
|
|
||||||
*
|
|
||||||
* @return Der {@code EventDispatcher} für dieses Objekt.
|
|
||||||
*/
|
|
||||||
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
|
||||||
if( eventDispatcher == null ) {
|
|
||||||
eventDispatcher = new EventDispatcher<>();
|
|
||||||
eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
|
|
||||||
eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
|
|
||||||
}
|
|
||||||
return eventDispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(Sound.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dieses Paket enthält Klassen zur Einbettung von Mediendateien.
|
|
||||||
* <p>
|
|
||||||
* Mit Medien sind vor allem Audio und Videodateien gemeint. Aktuell kann die
|
|
||||||
* Zeichenmaschine Audiodateien verwenden.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm.media;
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
package schule.ngb.zm.ml;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine einfache Implementierung der {@link MLMatrix} zur Verwendung in
|
|
||||||
* {@link NeuralNetwork}s.
|
|
||||||
* <p>
|
|
||||||
* Diese Klasse stellt die interne Implementierung der Matrixoperationen dar,
|
|
||||||
* die zur Berechnung der Gewichte in einem {@link NeuronLayer} notwendig sind.
|
|
||||||
* <p>
|
|
||||||
* Die Klasse ist nur minimal optimiert und sollte nur für kleine Netze
|
|
||||||
* verwendet werden. Für größere Netze sollte auf eine der optionalen
|
|
||||||
* Bibliotheken wie
|
|
||||||
* <a href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a> zurückgegriffen
|
|
||||||
* werden.
|
|
||||||
*/
|
|
||||||
public final class DoubleMatrix implements MLMatrix {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Anzahl Zeilen der Matrix.
|
|
||||||
*/
|
|
||||||
private int rows;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Anzahl Spalten der Matrix.
|
|
||||||
*/
|
|
||||||
private int columns;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Koeffizienten der Matrix.
|
|
||||||
* <p>
|
|
||||||
* Um den Overhead bei Speicher und Zugriffszeiten von zweidimensionalen
|
|
||||||
* Arrays zu vermeiden wird ein eindimensionales Array verwendet und die
|
|
||||||
* Indizes mit Spaltenpriorität berechnet. Der Index i des Koeffizienten
|
|
||||||
* {@code r,c} in Zeile {@code r} und Spalte {@code c} wird bestimmt durch
|
|
||||||
* <pre>
|
|
||||||
* i = c * rows + r
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Die Werte einer Spalte liegen also hintereinander im Array. Dies sollte
|
|
||||||
* einen leichten Vorteil bei der {@link #colSums() Spaltensummen} geben.
|
|
||||||
* Generell sollte eine Iteration über die Matrix der Form
|
|
||||||
* <pre><code>
|
|
||||||
* for( int j = 0; j < columns; j++ ) {
|
|
||||||
* for( int i = 0; i < rows; i++ ) {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
* etwas schneller sein als
|
|
||||||
* <pre><code>
|
|
||||||
* for( int i = 0; i < rows; i++ ) {
|
|
||||||
* for( int j = 0; j < columns; j++ ) {
|
|
||||||
* // ...
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
*/
|
|
||||||
double[] coefficients;
|
|
||||||
|
|
||||||
public DoubleMatrix( int rows, int cols ) {
|
|
||||||
this.rows = rows;
|
|
||||||
this.columns = cols;
|
|
||||||
coefficients = new double[rows * cols];
|
|
||||||
}
|
|
||||||
|
|
||||||
public DoubleMatrix( double[][] coefficients ) {
|
|
||||||
this.rows = coefficients.length;
|
|
||||||
this.columns = coefficients[0].length;
|
|
||||||
this.coefficients = new double[rows * columns];
|
|
||||||
for( int j = 0; j < columns; j++ ) {
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
this.coefficients[idx(i, j)] = coefficients[i][j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialisiert diese Matrix als Kopie der angegebenen Matrix.
|
|
||||||
*
|
|
||||||
* @param other Die zu kopierende Matrix.
|
|
||||||
*/
|
|
||||||
public DoubleMatrix( DoubleMatrix other ) {
|
|
||||||
this.rows = other.rows();
|
|
||||||
this.columns = other.columns();
|
|
||||||
this.coefficients = new double[rows * columns];
|
|
||||||
System.arraycopy(
|
|
||||||
other.coefficients, 0,
|
|
||||||
this.coefficients, 0,
|
|
||||||
rows * columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int columns() {
|
|
||||||
return columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int rows() {
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
int idx( int r, int c ) {
|
|
||||||
return c * rows + r;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get( int row, int col ) {
|
|
||||||
try {
|
|
||||||
return coefficients[idx(row, col)];
|
|
||||||
} catch( ArrayIndexOutOfBoundsException ex ) {
|
|
||||||
throw new IllegalArgumentException("No element at row=" + row + ", column=" + col, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix set( int row, int col, double value ) {
|
|
||||||
try {
|
|
||||||
coefficients[idx(row, col)] = value;
|
|
||||||
} catch( ArrayIndexOutOfBoundsException ex ) {
|
|
||||||
throw new IllegalArgumentException("No element at row=" + row + ", column=" + col, ex);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeRandom() {
|
|
||||||
return initializeRandom(-1.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeRandom( double lower, double upper ) {
|
|
||||||
applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeOne() {
|
|
||||||
applyInPlace(( d ) -> 1.0);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeZero() {
|
|
||||||
applyInPlace(( d ) -> 0.0);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix duplicate() {
|
|
||||||
return new DoubleMatrix(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, B.rows()).mapToDouble(
|
|
||||||
( j ) -> IntStream.range(0, columns).mapToDouble(
|
|
||||||
( k ) -> get(i, k) * B.get(j, k)
|
|
||||||
).sum()
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new));*/
|
|
||||||
DoubleMatrix result = new DoubleMatrix(rows, B.rows());
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
for( int j = 0; j < B.rows(); j++ ) {
|
|
||||||
result.coefficients[result.idx(i, j)] = 0.0;
|
|
||||||
for( int k = 0; k < columns; k++ ) {
|
|
||||||
result.coefficients[result.idx(i, j)] += get(i, k) * B.get(j, k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) {
|
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, B.columns()).mapToDouble(
|
|
||||||
( j ) -> IntStream.range(0, columns).mapToDouble(
|
|
||||||
( k ) -> get(i, k) * B.get(k, j)
|
|
||||||
).sum() + C.get(0, j)
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new));*/
|
|
||||||
DoubleMatrix result = new DoubleMatrix(rows, B.columns());
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
for( int j = 0; j < B.columns(); j++ ) {
|
|
||||||
result.coefficients[result.idx(i, j)] = 0.0;
|
|
||||||
for( int k = 0; k < columns; k++ ) {
|
|
||||||
result.coefficients[result.idx(i, j)] += get(i, k) * B.get(k, j);
|
|
||||||
}
|
|
||||||
result.coefficients[result.idx(i, j)] += C.get(0, j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
|
||||||
/*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, B.columns()).mapToDouble(
|
|
||||||
( j ) -> IntStream.range(0, rows).mapToDouble(
|
|
||||||
( k ) -> get(k, i) * B.get(k, j) * scalar
|
|
||||||
).sum()
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new));*/
|
|
||||||
DoubleMatrix result = new DoubleMatrix(columns, B.columns());
|
|
||||||
for( int i = 0; i < columns; i++ ) {
|
|
||||||
for( int j = 0; j < B.columns(); j++ ) {
|
|
||||||
result.coefficients[result.idx(i, j)] = 0.0;
|
|
||||||
for( int k = 0; k < rows; k++ ) {
|
|
||||||
result.coefficients[result.idx(i, j)] += get(k, i) * B.get(k, j);
|
|
||||||
}
|
|
||||||
result.coefficients[result.idx(i, j)] *= scalar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix add( MLMatrix B ) {
|
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, columns).mapToDouble(
|
|
||||||
( j ) -> get(i, j) + B.get(i, j)
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new));*/
|
|
||||||
DoubleMatrix sum = new DoubleMatrix(rows, columns);
|
|
||||||
for( int j = 0; j < columns; j++ ) {
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
sum.coefficients[idx(i, j)] = coefficients[idx(i, j)] + B.get(i, j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix addInPlace( MLMatrix B ) {
|
|
||||||
for( int j = 0; j < columns; j++ ) {
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
coefficients[idx(i, j)] += B.get(i, j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix sub( MLMatrix B ) {
|
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, columns).mapToDouble(
|
|
||||||
( j ) -> get(i, j) - B.get(i, j)
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new));*/
|
|
||||||
DoubleMatrix diff = new DoubleMatrix(rows, columns);
|
|
||||||
for( int j = 0; j < columns; j++ ) {
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
diff.coefficients[idx(i, j)] = coefficients[idx(i, j)] - B.get(i, j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix colSums() {
|
|
||||||
/*DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
|
||||||
colSums.coefficients = IntStream.range(0, columns).parallel().mapToDouble(
|
|
||||||
( j ) -> IntStream.range(0, rows).mapToDouble(
|
|
||||||
( i ) -> get(i, j)
|
|
||||||
).sum()
|
|
||||||
).toArray();
|
|
||||||
return colSums;*/
|
|
||||||
DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
|
||||||
for( int j = 0; j < columns; j++ ) {
|
|
||||||
colSums.coefficients[j] = 0.0;
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
colSums.coefficients[j] += coefficients[idx(i, j)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return colSums;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix scaleInPlace( final double scalar ) {
|
|
||||||
for( int i = 0; i < coefficients.length; i++ ) {
|
|
||||||
coefficients[i] *= scalar;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix scaleInPlace( final MLMatrix S ) {
|
|
||||||
for( int j = 0; j < columns; j++ ) {
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
coefficients[idx(i, j)] *= S.get(i, j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix apply( DoubleUnaryOperator op ) {
|
|
||||||
DoubleMatrix result = new DoubleMatrix(rows, columns);
|
|
||||||
for( int i = 0; i < coefficients.length; i++ ) {
|
|
||||||
result.coefficients[i] = op.applyAsDouble(coefficients[i]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
|
||||||
for( int i = 0; i < coefficients.length; i++ ) {
|
|
||||||
coefficients[i] = op.applyAsDouble(coefficients[i]);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(rows);
|
|
||||||
sb.append(" x ");
|
|
||||||
sb.append(columns);
|
|
||||||
sb.append(" Matrix");
|
|
||||||
sb.append('\n');
|
|
||||||
for( int i = 0; i < rows; i++ ) {
|
|
||||||
for( int j = 0; j < columns; j++ ) {
|
|
||||||
sb.append(get(i, j));
|
|
||||||
if( j < columns - 1 )
|
|
||||||
sb.append(' ');
|
|
||||||
}
|
|
||||||
sb.append('\n');
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
package schule.ngb.zm.ml;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
// See https://github.com/wheresvic/neuralnet
|
|
||||||
public final class MLMath {
|
|
||||||
|
|
||||||
public static double sigmoid( double x ) {
|
|
||||||
return 1 / (1 + Math.exp(-x));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double sigmoidDerivative( double x ) {
|
|
||||||
return x * (1 - x);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double tanh( double x ) {
|
|
||||||
return Math.tanh(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double tanhDerivative( double x ) {
|
|
||||||
return 1 - Math.tanh(x) * Math.tanh(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static double[] normalize( double[] vector ) {
|
|
||||||
final double sum = Arrays.stream(vector).sum();
|
|
||||||
return Arrays.stream(vector).map(( d ) -> d / sum).toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] matrixMultiply( double[][] A, double[][] B ) {
|
|
||||||
int a = A.length, b = A[0].length, c = B[0].length;
|
|
||||||
if( B.length != b ) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
String.format("Matrix A needs equal columns to matrix B rows. (Currently <%d> vs <%d>)", a, B.length)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IntStream.range(0, a).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, c).mapToDouble(
|
|
||||||
( j ) -> IntStream.range(0, b).mapToDouble(
|
|
||||||
( k ) -> A[i][k] * B[k][j]
|
|
||||||
).sum()
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] matrixScale( final double[][] A, final double[][] S ) {
|
|
||||||
if( A.length != S.length || A[0].length != S[0].length ) {
|
|
||||||
throw new IllegalArgumentException("Matrices need to be same size.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return IntStream.range(0, A.length).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, A[i].length).mapToDouble(
|
|
||||||
( j ) -> A[i][j] * S[i][j]
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] matrixSub( double[][] A, double[][] B ) {
|
|
||||||
if( A.length != B.length || A[0].length != B[0].length ) {
|
|
||||||
throw new IllegalArgumentException("Cannot subtract unequal matrices");
|
|
||||||
}
|
|
||||||
|
|
||||||
return IntStream.range(0, A.length).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, A[i].length).mapToDouble(
|
|
||||||
( j ) -> A[i][j] - B[i][j]
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] matrixAdd( double[][] A, double[][] B ) {
|
|
||||||
if( A.length != B.length || A[0].length != B[0].length ) {
|
|
||||||
throw new IllegalArgumentException("Cannot add unequal matrices");
|
|
||||||
}
|
|
||||||
|
|
||||||
return IntStream.range(0, A.length).parallel().mapToObj(
|
|
||||||
( i ) -> IntStream.range(0, A[i].length).mapToDouble(
|
|
||||||
( j ) -> A[i][j] + B[i][j]
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] matrixTranspose( double[][] matrix ) {
|
|
||||||
int a = matrix.length, b = matrix[0].length;
|
|
||||||
|
|
||||||
double[][] result = new double[matrix[0].length][matrix.length];
|
|
||||||
for( int i = 0; i < a; i++ ) {
|
|
||||||
for( int j = 0; j < b; ++j ) {
|
|
||||||
result[j][i] = matrix[i][j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] matrixApply( double[][] A, DoubleUnaryOperator op ) {
|
|
||||||
return Arrays.stream(A).parallel().map(
|
|
||||||
( arr ) -> Arrays.stream(arr).map(op).toArray()
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] copyMatrix( double[][] matrix ) {
|
|
||||||
/*return Arrays.stream(matrix).map(
|
|
||||||
(arr) -> Arrays.copyOf(arr, arr.length)
|
|
||||||
).toArray(double[][]::new);*/
|
|
||||||
|
|
||||||
double[][] result = new double[matrix.length][matrix[0].length];
|
|
||||||
for( int i = 0; i < matrix.length; i++ ) {
|
|
||||||
result[i] = Arrays.copyOf(matrix[i], matrix[i].length);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[] toVector( double[][] matrix ) {
|
|
||||||
return Arrays.stream(matrix).mapToDouble(
|
|
||||||
( arr ) -> arr[0]
|
|
||||||
).toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] toMatrix( double[] vector ) {
|
|
||||||
return Arrays.stream(vector).mapToObj(
|
|
||||||
( d ) -> new double[]{d}
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double entropy(double[][] A, double[][] Y, int batch_size) {
|
|
||||||
int m = A.length;
|
|
||||||
int n = A[0].length;
|
|
||||||
double[][] z = new double[m][n];
|
|
||||||
|
|
||||||
for (int i = 0; i < m; i++) {
|
|
||||||
for (int j = 0; j < n; j++) {
|
|
||||||
z[i][j] = (Y[i][j] * Math.log(A[i][j])) + ((1 - Y[i][j]) * Math.log(1 - A[i][j]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double sum = 0;
|
|
||||||
for (int i = 0; i < m; i++) {
|
|
||||||
for (int j = 0; j < n; j++) {
|
|
||||||
sum += z[i][j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -sum / batch_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] biasAdd( double[][] A, double[] V ) {
|
|
||||||
if( A[0].length != V.length ) {
|
|
||||||
throw new IllegalArgumentException("Can't add bias vector to matrix with wrong column count");
|
|
||||||
}
|
|
||||||
|
|
||||||
double[][] result = new double[A.length][A[0].length];
|
|
||||||
for( int j = 0; j < A[0].length; j++ ) {
|
|
||||||
for( int i = 0; i < A.length; i++ ) {
|
|
||||||
result[i][j] = A[i][j] + V[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[] biasAdjust( double[] biases, double[][] delta ) {
|
|
||||||
if( biases.length != delta[0].length ) {
|
|
||||||
throw new IllegalArgumentException("Can't add adjust bias vector by delta with wrong column count");
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] result = new double[biases.length];
|
|
||||||
for( int j = 0; j < delta[0].length; j++ ) {
|
|
||||||
for( int i = 0; i < delta.length; i++ ) {
|
|
||||||
result[j] += biases[j] - delta[i][j];
|
|
||||||
}
|
|
||||||
result[j] /= delta.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MLMath() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
package schule.ngb.zm.ml;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface für Matrizen, die in {@link NeuralNetwork} Klassen verwendet
|
|
||||||
* werden.
|
|
||||||
* <p>
|
|
||||||
* Eine implementierende Klasse muss generell zwei Konstruktoren bereitstellen:
|
|
||||||
* <ol>
|
|
||||||
* <li> {@code MLMatrix(int rows, int columns)} erstellt eine Matrix mit den
|
|
||||||
* angegebenen Dimensionen und setzt alle Koeffizienten auf 0.
|
|
||||||
* <li> {@code MLMatrix(double[][] coefficients} erstellt eine Matrix mit der
|
|
||||||
* durch das Array gegebenen Dimensionen und setzt die Werte auf die
|
|
||||||
* jeweiligen Werte des Arrays.
|
|
||||||
* </ol>
|
|
||||||
* <p>
|
|
||||||
* Das Interface ist nicht dazu gedacht eine allgemeine Umsetzung für
|
|
||||||
* Matrizen-Algebra abzubilden, sondern soll gezielt die im Neuralen Netzwerk
|
|
||||||
* verwendeten Algorithmen umsetzen. Einerseits würde eine ganz allgemeine
|
|
||||||
* Matrizen-Klasse nicht im Rahmen der Zeichenmaschine liegen und auf der
|
|
||||||
* anderen Seite bietet eine Konzentration auf die verwendeten Algorithmen mehr
|
|
||||||
* Spielraum zur Optimierung.
|
|
||||||
* <p>
|
|
||||||
* Intern wird das Interface von {@link DoubleMatrix} implementiert. Die Klasse
|
|
||||||
* ist eine weitestgehend naive Implementierung der Algorithmen mit kleineren
|
|
||||||
* Optimierungen. Die Verwendung eines generalisierten Interfaces erlaubt aber
|
|
||||||
* zukünftig die optionale Integration spezialisierterer Algebra-Bibliotheken
|
|
||||||
* wie
|
|
||||||
* <a href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a>, um auch große
|
|
||||||
* Netze effizient berechnen zu können.
|
|
||||||
*/
|
|
||||||
public interface MLMatrix {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Anzahl der Spalten der Matrix.
|
|
||||||
*
|
|
||||||
* @return Spaltenzahl.
|
|
||||||
*/
|
|
||||||
int columns();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Anzahl der Zeilen der Matrix.
|
|
||||||
*
|
|
||||||
* @return Zeilenzahl.
|
|
||||||
*/
|
|
||||||
int rows();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt den Wert an der angegebenen Stelle der Matrix zurück.
|
|
||||||
*
|
|
||||||
* @param row Die Spaltennummer zwischen 0 und {@code rows()-1}.
|
|
||||||
* @param col Die Zeilennummer zwischen 0 und {@code columns()-1}
|
|
||||||
* @return Den Koeffizienten in der Zeile {@code row} und der Spalte
|
|
||||||
* {@code col}.
|
|
||||||
* @throws IllegalArgumentException Falls {@code row >= rows()} oder
|
|
||||||
* {@code col >= columns()}.
|
|
||||||
*/
|
|
||||||
double get( int row, int col ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Wert an der angegebenen Stelle der Matrix.
|
|
||||||
*
|
|
||||||
* @param row Die Spaltennummer zwischen 0 und {@code rows()-1}.
|
|
||||||
* @param col Die Zeilennummer zwischen 0 und {@code columns()-1}
|
|
||||||
* @param value Der neue Wert.
|
|
||||||
* @return Diese Matrix selbst (method chaining).
|
|
||||||
* @throws IllegalArgumentException Falls {@code row >= rows()} oder
|
|
||||||
* {@code col >= columns()}.
|
|
||||||
*/
|
|
||||||
MLMatrix set( int row, int col, double value ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt jeden Wert in der Matrix auf eine Zufallszahl zwischen -1 und 1.
|
|
||||||
* <p>
|
|
||||||
* Nach Möglichkeit sollte der
|
|
||||||
* {@link schule.ngb.zm.Constants#random(int, int) Zufallsgenerator der
|
|
||||||
* Zeichenmaschine} verwendet werden.
|
|
||||||
*
|
|
||||||
* @return Diese Matrix selbst (method chaining).
|
|
||||||
*/
|
|
||||||
MLMatrix initializeRandom();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt jeden Wert in der Matrix auf eine Zufallszahl innerhalb der
|
|
||||||
* angegebenen Grenzen.
|
|
||||||
* <p>
|
|
||||||
* Nach Möglichkeit sollte der
|
|
||||||
* {@link schule.ngb.zm.Constants#random(int, int) Zufallsgenerator der
|
|
||||||
* Zeichenmaschine} verwendet werden.
|
|
||||||
*
|
|
||||||
* @param lower Untere Grenze der Zufallszahlen.
|
|
||||||
* @param upper Obere Grenze der Zufallszahlen.
|
|
||||||
* @return Diese Matrix selbst (method chaining).
|
|
||||||
*/
|
|
||||||
MLMatrix initializeRandom( double lower, double upper );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt alle Werte der Matrix auf 1.
|
|
||||||
*
|
|
||||||
* @return Diese Matrix selbst (method chaining).
|
|
||||||
*/
|
|
||||||
MLMatrix initializeOne();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt alle Werte der Matrix auf 0.
|
|
||||||
*
|
|
||||||
* @return Diese Matrix selbst (method chaining).
|
|
||||||
*/
|
|
||||||
MLMatrix initializeZero();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
|
|
||||||
* <pre>
|
|
||||||
* C = this . B + V'
|
|
||||||
* </pre>
|
|
||||||
* wobei {@code this} dieses Matrixobjekt ist und {@code .} für die
|
|
||||||
* Matrixmultiplikation steht. {@code V'} ist die Matrix {@code V}
|
|
||||||
* {@code rows()}-mal untereinander wiederholt.
|
|
||||||
* <p>
|
|
||||||
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
|
|
||||||
* die Dimension c x m haben und {@code V} eine 1 x m Matrix sein. Die
|
|
||||||
* Matrix {@code V'} hat also die Dimension r x m, ebenso wie das Ergebnis
|
|
||||||
* der Operation.
|
|
||||||
*
|
|
||||||
* @param B Eine {@code columns()} x m Matrix mit der Multipliziert wird.
|
|
||||||
* @param V Eine 1 x {@code B.columns()} Matrix mit den Bias-Werten.
|
|
||||||
* @return Eine {@code rows()} x m Matrix.
|
|
||||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
|
||||||
* zur Operation passen. Also
|
|
||||||
* {@code this.columns() != B.rows()} oder
|
|
||||||
* {@code B.columns() != V.columns()} oder
|
|
||||||
* {@code V.rows() != 1}.
|
|
||||||
*/
|
|
||||||
MLMatrix multiplyAddBias( MLMatrix B, MLMatrix V ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
|
|
||||||
* <pre>
|
|
||||||
* C = this . t(B)
|
|
||||||
* </pre>
|
|
||||||
* wobei {@code this} dieses Matrixobjekt ist, {@code t(B)} die
|
|
||||||
* Transposition der Matrix {@code B} ist und {@code .} für die
|
|
||||||
* Matrixmultiplikation steht.
|
|
||||||
* <p>
|
|
||||||
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
|
|
||||||
* die Dimension m x c haben und das Ergebnis ist eine r x m Matrix.
|
|
||||||
*
|
|
||||||
* @param B Eine m x {@code columns()} Matrix.
|
|
||||||
* @return Eine {@code rows()} x m Matrix.
|
|
||||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
|
||||||
* zur Operation passen. Also
|
|
||||||
* {@code this.columns() != B.columns()}.
|
|
||||||
*/
|
|
||||||
MLMatrix multiplyTransposed( MLMatrix B ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der Matrixoperation
|
|
||||||
* <pre>
|
|
||||||
* C = t(this) . B * scalar
|
|
||||||
* </pre>
|
|
||||||
* wobei {@code this} dieses Matrixobjekt ist, {@code t(this)} die
|
|
||||||
* Transposition dieser Matrix ist und {@code .} für die
|
|
||||||
* Matrixmultiplikation steht. {@code *} bezeichnet die
|
|
||||||
* Skalarmultiplikation, bei der jeder Wert der Matrix mit {@code scalar}
|
|
||||||
* multipliziert wird.
|
|
||||||
* <p>
|
|
||||||
* Wenn diese Matrix die Dimension r x c hat, dann muss die Matrix {@code B}
|
|
||||||
* die Dimension r x m haben und das Ergebnis ist eine c x m Matrix.
|
|
||||||
*
|
|
||||||
* @param B Eine m x {@code columns()} Matrix.
|
|
||||||
* @return Eine {@code rows()} x m Matrix.
|
|
||||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
|
||||||
* zur Operation passen. Also
|
|
||||||
* {@code this.rows() != B.rows()}.
|
|
||||||
*/
|
|
||||||
MLMatrix transposedMultiplyAndScale( MLMatrix B, double scalar ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der komponentenweisen
|
|
||||||
* Matrix-Addition
|
|
||||||
* <pre>
|
|
||||||
* C = this + B
|
|
||||||
* </pre>
|
|
||||||
* wobei {@code this} dieses Matrixobjekt ist. Für ein Element {@code C_ij}
|
|
||||||
* in {@code C} gilt
|
|
||||||
* <pre>
|
|
||||||
* C_ij = A_ij + B_ij
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
|
|
||||||
*
|
|
||||||
* @param B Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
|
||||||
* zur Operation passen. Also
|
|
||||||
* {@code this.rows() != B.rows()} oder
|
|
||||||
* {@code this.columns() != B.columns()}.
|
|
||||||
*/
|
|
||||||
MLMatrix add( MLMatrix B ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt diese Matrix auf das Ergebnis der komponentenweisen
|
|
||||||
* Matrix-Addition
|
|
||||||
* <pre>
|
|
||||||
* A' = A + B
|
|
||||||
* </pre>
|
|
||||||
* wobei {@code A} dieses Matrixobjekt ist und {@code A'} diese Matrix nach
|
|
||||||
* der Operation. Für ein Element {@code A'_ij} in {@code A'} gilt
|
|
||||||
* <pre>
|
|
||||||
* A'_ij = A_ij + B_ij
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
|
|
||||||
*
|
|
||||||
* @param B Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
|
||||||
* zur Operation passen. Also
|
|
||||||
* {@code this.rows() != B.rows()} oder
|
|
||||||
* {@code this.columns() != B.columns()}.
|
|
||||||
*/
|
|
||||||
MLMatrix addInPlace( MLMatrix B ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine neue Matrix {@code C} mit dem Ergebnis der komponentenweisen
|
|
||||||
* Matrix-Subtraktion
|
|
||||||
* <pre>
|
|
||||||
* C = A - B
|
|
||||||
* </pre>
|
|
||||||
* wobei {@code A} dieses Matrixobjekt ist. Für ein Element {@code C_ij} in
|
|
||||||
* {@code C} gilt
|
|
||||||
* <pre>
|
|
||||||
* C_ij = A_ij - B_ij
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Die Matrix {@code B} muss dieselbe Dimension wie diese Matrix haben.
|
|
||||||
*
|
|
||||||
* @param B Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
|
||||||
* zur Operation passen. Also
|
|
||||||
* {@code this.rows() != B.rows()} oder
|
|
||||||
* {@code this.columns() != B.columns()}.
|
|
||||||
*/
|
|
||||||
MLMatrix sub( MLMatrix B ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multipliziert jeden Wert dieser Matrix mit dem angegebenen Skalar.
|
|
||||||
* <p>
|
|
||||||
* Ist {@code A} dieses Matrixobjekt und {@code A'} diese Matrix nach der
|
|
||||||
* Operation, dann gilt für ein Element {@code A'_ij} in {@code A'}
|
|
||||||
* <pre>
|
|
||||||
* A'_ij = A_ij * scalar
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param scalar Ein Skalar.
|
|
||||||
* @return Diese Matrix selbst (method chaining)
|
|
||||||
*/
|
|
||||||
MLMatrix scaleInPlace( double scalar );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multipliziert jeden Wert dieser Matrix mit dem entsprechenden Wert in der
|
|
||||||
* Matrix {@code S}.
|
|
||||||
* <p>
|
|
||||||
* Ist {@code A} dieses Matrixobjekt und {@code A'} diese Matrix nach der
|
|
||||||
* Operation, dann gilt für ein Element {@code A'_ij} in {@code A'}
|
|
||||||
* <pre>
|
|
||||||
* A'_ij = A_ij * S_ij
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param S Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
* @return Diese Matrix selbst (method chaining)
|
|
||||||
* @throws IllegalArgumentException Falls die Dimensionen der Matrizen nicht
|
|
||||||
* zur Operation passen. Also
|
|
||||||
* {@code this.rows() != B.rows()} oder
|
|
||||||
* {@code this.columns() != B.columns()}.
|
|
||||||
*/
|
|
||||||
MLMatrix scaleInPlace( MLMatrix S ) throws IllegalArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Berechnet eine neue Matrix mit nur einer Zeile, die die Spaltensummen
|
|
||||||
* dieser Matrix enthalten.
|
|
||||||
*
|
|
||||||
* @return Eine 1 x {@code columns()} Matrix.
|
|
||||||
*/
|
|
||||||
MLMatrix colSums();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine neue Matrix, deren Werte gleich den Werten dieser Matrix
|
|
||||||
* nach der Anwendung der angegebenen Funktion sind.
|
|
||||||
*
|
|
||||||
* @param op Eine Operation {@code (double) -> double}.
|
|
||||||
* @return Eine {@code rows()} x {@code columns()} Matrix.
|
|
||||||
*/
|
|
||||||
MLMatrix apply( DoubleUnaryOperator op );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Endet die gegebene Funktion auf jeden Wert der Matrix an.
|
|
||||||
*
|
|
||||||
* @param op Eine Operation {@code (double) -> double}.
|
|
||||||
* @return Diese Matrix selbst (method chaining)
|
|
||||||
*/
|
|
||||||
MLMatrix applyInPlace( DoubleUnaryOperator op );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine neue Matrix mit denselben Dimensionen und Koeffizienten wie
|
|
||||||
* diese Matrix.
|
|
||||||
*
|
|
||||||
* @return Eine Kopie dieser Matrix.
|
|
||||||
*/
|
|
||||||
MLMatrix duplicate();
|
|
||||||
|
|
||||||
String toString();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
package schule.ngb.zm.ml;
|
|
||||||
|
|
||||||
import cern.colt.matrix.DoubleFactory2D;
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zentrale Klasse zur Erstellung neuer Matrizen. Generell sollten neue Matrizen
|
|
||||||
* nicht direkt erstellt werden, sondern durch den Aufruf von
|
|
||||||
* {@link #create(int, int)} oder {@link #create(double[][])}. Die Fabrik
|
|
||||||
* ermittelt automatisch die beste verfügbare Implementierung und initialisiert
|
|
||||||
* eine entsprechende Implementierung von {@link MLMatrix}.
|
|
||||||
* <p>
|
|
||||||
* Derzeit werden die optionale Bibliothek <a
|
|
||||||
* href="https://dst.lbl.gov/ACSSoftware/colt/">Colt</a> und die interne
|
|
||||||
* Implementierung {@link DoubleMatrix} unterstützt.
|
|
||||||
*/
|
|
||||||
public class MatrixFactory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Matrix mit den angegebenen Dimensionen und
|
|
||||||
* initialisiert alle Werte mit 0.
|
|
||||||
*
|
|
||||||
* @param rows Anzahl der Zeilen.
|
|
||||||
* @param cols Anzahl der Spalten.
|
|
||||||
* @return Eine {@code rows} x {@code cols} Matrix.
|
|
||||||
*/
|
|
||||||
public static final MLMatrix create( int rows, int cols ) {
|
|
||||||
try {
|
|
||||||
return getMatrixType().getDeclaredConstructor(int.class, int.class).newInstance(rows, cols);
|
|
||||||
} catch( Exception ex ) {
|
|
||||||
LOG.error(ex, "Could not initialize matrix implementation for class <%s>. Using internal implementation.", matrixType);
|
|
||||||
}
|
|
||||||
return new DoubleMatrix(rows, cols);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Matrix mit den Dimensionen des angegebenen Arrays und
|
|
||||||
* initialisiert die Werte mit den entsprechenden Werten des Arrays.
|
|
||||||
*
|
|
||||||
* @param values Die Werte der Matrix.
|
|
||||||
* @return Eine {@code values.length} x {@code values[0].length} Matrix mit
|
|
||||||
* den Werten des Arrays.
|
|
||||||
*/
|
|
||||||
public static final MLMatrix create( double[][] values ) {
|
|
||||||
try {
|
|
||||||
return getMatrixType().getDeclaredConstructor(double[][].class).newInstance((Object) values);
|
|
||||||
} catch( Exception ex ) {
|
|
||||||
LOG.error(ex, "Could not initialize matrix implementation for class <%s>. Using internal implementation.", matrixType);
|
|
||||||
}
|
|
||||||
return new DoubleMatrix(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die verwendete {@link MLMatrix} Implementierung, aus der Matrizen erzeugt
|
|
||||||
* werden.
|
|
||||||
*/
|
|
||||||
static Class<? extends MLMatrix> matrixType = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ermittelt die beste verfügbare Implementierung von {@link MLMatrix}.
|
|
||||||
*
|
|
||||||
* @return Die verwendete {@link MLMatrix} Implementierung.
|
|
||||||
*/
|
|
||||||
private static final Class<? extends MLMatrix> getMatrixType() {
|
|
||||||
if( matrixType == null ) {
|
|
||||||
try {
|
|
||||||
Class<?> clazz = Class.forName("cern.colt.matrix.impl.DenseDoubleMatrix2D", false, MatrixFactory.class.getClassLoader());
|
|
||||||
matrixType = ColtMatrix.class;
|
|
||||||
LOG.info("Colt library found. Using <cern.colt.matrix.impl.DenseDoubleMatrix2D> as matrix implementation.");
|
|
||||||
} catch( ClassNotFoundException e ) {
|
|
||||||
LOG.info("Colt library not found. Falling back on internal implementation.");
|
|
||||||
matrixType = DoubleMatrix.class;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matrixType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(MatrixFactory.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interner Wrapper der DoubleMatrix2D Klasse aus der Colt Bibliothek, um
|
|
||||||
* das {@link MLMatrix} Interface zu implementieren.
|
|
||||||
*/
|
|
||||||
static class ColtMatrix implements MLMatrix {
|
|
||||||
|
|
||||||
cern.colt.matrix.DoubleMatrix2D matrix;
|
|
||||||
|
|
||||||
public ColtMatrix( double[][] doubles ) {
|
|
||||||
matrix = new cern.colt.matrix.impl.DenseDoubleMatrix2D(doubles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColtMatrix( int rows, int cols ) {
|
|
||||||
matrix = new cern.colt.matrix.impl.DenseDoubleMatrix2D(rows, cols);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColtMatrix( ColtMatrix matrix ) {
|
|
||||||
this.matrix = matrix.matrix.copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int columns() {
|
|
||||||
return matrix.columns();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int rows() {
|
|
||||||
return matrix.rows();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double get( int row, int col ) {
|
|
||||||
return matrix.get(row, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix set( int row, int col, double value ) {
|
|
||||||
matrix.set(row, col, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeRandom() {
|
|
||||||
return initializeRandom(-1.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeRandom( double lower, double upper ) {
|
|
||||||
matrix.assign(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeOne() {
|
|
||||||
this.matrix.assign(1.0);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix initializeZero() {
|
|
||||||
this.matrix.assign(0.0);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix duplicate() {
|
|
||||||
ColtMatrix newMatrix = new ColtMatrix(matrix.rows(), matrix.columns());
|
|
||||||
newMatrix.matrix.assign(this.matrix);
|
|
||||||
return newMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
|
||||||
ColtMatrix CB = (ColtMatrix) B;
|
|
||||||
ColtMatrix newMatrix = new ColtMatrix(0, 0);
|
|
||||||
newMatrix.matrix = matrix.zMult(CB.matrix, null, 1.0, 0.0, false, true);
|
|
||||||
return newMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix multiplyAddBias( MLMatrix B, MLMatrix C ) {
|
|
||||||
ColtMatrix CB = (ColtMatrix) B;
|
|
||||||
ColtMatrix newMatrix = new ColtMatrix(0, 0);
|
|
||||||
newMatrix.matrix = DoubleFactory2D.dense.repeat(((ColtMatrix) C).matrix, rows(), 1);
|
|
||||||
matrix.zMult(CB.matrix, newMatrix.matrix, 1.0, 1.0, false, false);
|
|
||||||
return newMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
|
||||||
ColtMatrix CB = (ColtMatrix) B;
|
|
||||||
ColtMatrix newMatrix = new ColtMatrix(0, 0);
|
|
||||||
newMatrix.matrix = matrix.zMult(CB.matrix, null, scalar, 0.0, true, false);
|
|
||||||
return newMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix add( MLMatrix B ) {
|
|
||||||
ColtMatrix CB = (ColtMatrix) B;
|
|
||||||
ColtMatrix newMatrix = new ColtMatrix(this);
|
|
||||||
newMatrix.matrix.assign(CB.matrix, ( d1, d2 ) -> d1 + d2);
|
|
||||||
return newMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix addInPlace( MLMatrix B ) {
|
|
||||||
ColtMatrix CB = (ColtMatrix) B;
|
|
||||||
matrix.assign(CB.matrix, ( d1, d2 ) -> d1 + d2);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix sub( MLMatrix B ) {
|
|
||||||
ColtMatrix CB = (ColtMatrix) B;
|
|
||||||
ColtMatrix newMatrix = new ColtMatrix(this);
|
|
||||||
newMatrix.matrix.assign(CB.matrix, ( d1, d2 ) -> d1 - d2);
|
|
||||||
return newMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix colSums() {
|
|
||||||
double[][] sums = new double[1][matrix.columns()];
|
|
||||||
for( int c = 0; c < matrix.columns(); c++ ) {
|
|
||||||
for( int r = 0; r < matrix.rows(); r++ ) {
|
|
||||||
sums[0][c] += matrix.getQuick(r, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new ColtMatrix(sums);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix scaleInPlace( double scalar ) {
|
|
||||||
this.matrix.assign(( d ) -> d * scalar);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix scaleInPlace( MLMatrix S ) {
|
|
||||||
this.matrix.forEachNonZero(( r, c, d ) -> d * S.get(r, c));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix apply( DoubleUnaryOperator op ) {
|
|
||||||
ColtMatrix newMatrix = new ColtMatrix(matrix.rows(), matrix.columns());
|
|
||||||
newMatrix.matrix.assign(matrix);
|
|
||||||
newMatrix.matrix.assign(( d ) -> op.applyAsDouble(d));
|
|
||||||
return newMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
|
||||||
this.matrix.assign(( d ) -> op.applyAsDouble(d));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return matrix.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
package schule.ngb.zm.ml;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
public class NeuralNetwork {
|
|
||||||
|
|
||||||
public static void saveToFile( String source, NeuralNetwork network ) {
|
|
||||||
try(
|
|
||||||
Writer writer = ResourceStreamProvider.getWriter(source);
|
|
||||||
PrintWriter out = new PrintWriter(writer)
|
|
||||||
) {
|
|
||||||
for( NeuronLayer layer : network.layers ) {
|
|
||||||
out.print(layer.getNeuronCount());
|
|
||||||
out.print(' ');
|
|
||||||
out.print(layer.getInputCount());
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
|
||||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
|
||||||
out.print(layer.weights.get(i, j));
|
|
||||||
out.print(' ');
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
}
|
|
||||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
|
||||||
out.print(layer.biases.get(0, j));
|
|
||||||
out.print(' ');
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
}
|
|
||||||
out.flush();
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.error(ex, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void saveToDataFile( String source, NeuralNetwork network ) {
|
|
||||||
try(
|
|
||||||
OutputStream stream = ResourceStreamProvider.getOutputStream(source);
|
|
||||||
DataOutputStream out = new DataOutputStream(stream)
|
|
||||||
) {
|
|
||||||
for( NeuronLayer layer : network.layers ) {
|
|
||||||
out.writeInt(layer.getNeuronCount());
|
|
||||||
out.writeInt(layer.getInputCount());
|
|
||||||
|
|
||||||
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
|
||||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
|
||||||
out.writeDouble(layer.weights.get(i, j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
|
||||||
out.writeDouble(layer.biases.get(0, j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.flush();
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.error(ex, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NeuralNetwork loadFromFile( String source ) {
|
|
||||||
try(
|
|
||||||
Reader reader = ResourceStreamProvider.getReader(source);
|
|
||||||
BufferedReader in = new BufferedReader(reader)
|
|
||||||
) {
|
|
||||||
List<NeuronLayer> layers = new LinkedList<>();
|
|
||||||
String line;
|
|
||||||
while( (line = in.readLine()) != null ) {
|
|
||||||
String[] split = line.split(" ");
|
|
||||||
int neurons = Integer.parseInt(split[0]);
|
|
||||||
int inputs = Integer.parseInt(split[1]);
|
|
||||||
|
|
||||||
NeuronLayer layer = new NeuronLayer(neurons, inputs);
|
|
||||||
for( int i = 0; i < inputs; i++ ) {
|
|
||||||
split = in.readLine().split(" ");
|
|
||||||
for( int j = 0; j < neurons; j++ ) {
|
|
||||||
layer.weights.set(i, j, Double.parseDouble(split[j]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Load Biases
|
|
||||||
split = in.readLine().split(" ");
|
|
||||||
for( int j = 0; j < neurons; j++ ) {
|
|
||||||
layer.biases.set(0, j, Double.parseDouble(split[j]));
|
|
||||||
}
|
|
||||||
|
|
||||||
layers.add(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NeuralNetwork(layers);
|
|
||||||
} catch( IOException | NoSuchElementException ex ) {
|
|
||||||
LOG.error(ex, "Could not load neural network from source <%s>", source);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NeuralNetwork loadFromDataFile( String source ) {
|
|
||||||
try(
|
|
||||||
InputStream stream = ResourceStreamProvider.getInputStream(source);
|
|
||||||
DataInputStream in = new DataInputStream(stream)
|
|
||||||
) {
|
|
||||||
List<NeuronLayer> layers = new LinkedList<>();
|
|
||||||
while( in.available() > 0 ) {
|
|
||||||
int neurons = in.readInt();
|
|
||||||
int inputs = in.readInt();
|
|
||||||
|
|
||||||
NeuronLayer layer = new NeuronLayer(neurons, inputs);
|
|
||||||
for( int i = 0; i < inputs; i++ ) {
|
|
||||||
for( int j = 0; j < neurons; j++ ) {
|
|
||||||
layer.weights.set(i, j, in.readDouble());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Load Biases
|
|
||||||
for( int j = 0; j < neurons; j++ ) {
|
|
||||||
layer.biases.set(0, j, in.readDouble());
|
|
||||||
}
|
|
||||||
|
|
||||||
layers.add(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NeuralNetwork(layers);
|
|
||||||
} catch( IOException | NoSuchElementException ex ) {
|
|
||||||
LOG.error(ex, "Could not load neural network from source <%s>", source);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NeuronLayer[] layers;
|
|
||||||
|
|
||||||
private MLMatrix output;
|
|
||||||
|
|
||||||
private double learningRate = 0.1;
|
|
||||||
|
|
||||||
public NeuralNetwork( int inputs, int layer1, int outputs ) {
|
|
||||||
this(new NeuronLayer(layer1, inputs), new NeuronLayer(outputs, layer1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuralNetwork( int inputs, int layer1, int layer2, int outputs ) {
|
|
||||||
this(new NeuronLayer(layer1, inputs), new NeuronLayer(layer2, layer1), new NeuronLayer(outputs, layer2));
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuralNetwork( int inputs, int layer1, int layer2, int layer3, int outputs ) {
|
|
||||||
this(new NeuronLayer(layer1, inputs), new NeuronLayer(layer2, layer1), new NeuronLayer(layer3, layer2), new NeuronLayer(outputs, layer3));
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuralNetwork( List<NeuronLayer> layers ) {
|
|
||||||
this.layers = new NeuronLayer[layers.size()];
|
|
||||||
for( int i = 0; i < layers.size(); i++ ) {
|
|
||||||
this.layers[i] = layers.get(i);
|
|
||||||
if( i > 0 ) {
|
|
||||||
this.layers[i - 1].setNextLayer(this.layers[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuralNetwork( NeuronLayer... layers ) {
|
|
||||||
this.layers = new NeuronLayer[layers.length];
|
|
||||||
for( int i = 0; i < layers.length; i++ ) {
|
|
||||||
this.layers[i] = layers[i];
|
|
||||||
if( i > 0 ) {
|
|
||||||
this.layers[i - 1].setNextLayer(this.layers[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLayerCount() {
|
|
||||||
return layers.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuronLayer[] getLayers() {
|
|
||||||
return layers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuronLayer getLayer( int i ) {
|
|
||||||
return layers[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLearningRate() {
|
|
||||||
return learningRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLearningRate( double pLearningRate ) {
|
|
||||||
this.learningRate = pLearningRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MLMatrix getOutput() {
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MLMatrix predict( double[] inputs ) {
|
|
||||||
return predict(MatrixFactory.create(new double[][]{inputs}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MLMatrix predict( double[][] inputs ) {
|
|
||||||
return predict(MatrixFactory.create(inputs));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MLMatrix predict( MLMatrix inputs ) {
|
|
||||||
this.output = layers[0].apply(inputs);
|
|
||||||
return this.output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void learn( double[][] expected ) {
|
|
||||||
learn(MatrixFactory.create(expected));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void learn( MLMatrix expected ) {
|
|
||||||
layers[layers.length - 1].backprop(expected, learningRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void train( double[][] inputs, double[][] expected, int iterations/*, double minChange, int timeout */ ) {
|
|
||||||
for( int i = 0; i < iterations; i++ ) {
|
|
||||||
// pass the training set through the network
|
|
||||||
predict(inputs);
|
|
||||||
// start backpropagation through all layers
|
|
||||||
learn(expected);
|
|
||||||
|
|
||||||
if( i % 10000 == 0 ) {
|
|
||||||
LOG.trace("Training iteration %d of %d", i, iterations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(NeuralNetwork.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
package schule.ngb.zm.ml;
|
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementierung einer Neuronenebene in einem Neuonalen Netz.
|
|
||||||
* <p>
|
|
||||||
* Eine Ebene besteht aus einer Anzahl an <em>Neuronen</em> die jeweils eine
|
|
||||||
* Anzahl <em>Eingänge</em> haben. Die Eingänge erhalten als Signal die Ausgabe
|
|
||||||
* der vorherigen Ebene und berechnen die Ausgabe des jeweiligen Neurons.
|
|
||||||
*/
|
|
||||||
public class NeuronLayer implements Function<MLMatrix, MLMatrix> {
|
|
||||||
|
|
||||||
public static NeuronLayer fromArray( double[][] weights, boolean transpose ) {
|
|
||||||
NeuronLayer layer;
|
|
||||||
if( transpose ) {
|
|
||||||
layer = new NeuronLayer(weights.length, weights[0].length);
|
|
||||||
} else {
|
|
||||||
layer = new NeuronLayer(weights[0].length, weights.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
for( int i = 0; i < weights[0].length; i++ ) {
|
|
||||||
for( int j = 0; j < weights.length; j++ ) {
|
|
||||||
if( transpose ) {
|
|
||||||
layer.weights.set(j, i, weights[i][j]);
|
|
||||||
} else {
|
|
||||||
layer.weights.set(i, j, weights[i][j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NeuronLayer fromArray( double[][] weights, double[] biases, boolean transpose ) {
|
|
||||||
NeuronLayer layer = fromArray(weights, transpose);
|
|
||||||
|
|
||||||
for( int j = 0; j < biases.length; j++ ) {
|
|
||||||
layer.biases.set(0, j, biases[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
MLMatrix weights;
|
|
||||||
|
|
||||||
MLMatrix biases;
|
|
||||||
|
|
||||||
NeuronLayer previous, next;
|
|
||||||
|
|
||||||
DoubleUnaryOperator activationFunction, activationFunctionDerivative;
|
|
||||||
|
|
||||||
MLMatrix lastOutput, lastInput;
|
|
||||||
|
|
||||||
public NeuronLayer( int neurons, int inputs ) {
|
|
||||||
weights = MatrixFactory
|
|
||||||
.create(inputs, neurons)
|
|
||||||
.initializeRandom();
|
|
||||||
|
|
||||||
biases = MatrixFactory
|
|
||||||
.create(1, neurons)
|
|
||||||
.initializeZero();
|
|
||||||
|
|
||||||
activationFunction = MLMath::sigmoid;
|
|
||||||
activationFunctionDerivative = MLMath::sigmoidDerivative;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connect( NeuronLayer prev, NeuronLayer next ) {
|
|
||||||
setPreviousLayer(prev);
|
|
||||||
setNextLayer(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuronLayer getPreviousLayer() {
|
|
||||||
return previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPreviousLayer() {
|
|
||||||
return previous != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreviousLayer( NeuronLayer pPreviousLayer ) {
|
|
||||||
this.previous = pPreviousLayer;
|
|
||||||
if( pPreviousLayer != null ) {
|
|
||||||
pPreviousLayer.next = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuronLayer getNextLayer() {
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasNextLayer() {
|
|
||||||
return next != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNextLayer( NeuronLayer pNextLayer ) {
|
|
||||||
this.next = pNextLayer;
|
|
||||||
if( pNextLayer != null ) {
|
|
||||||
pNextLayer.previous = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MLMatrix getWeights() {
|
|
||||||
return weights;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MLMatrix getBiases() {
|
|
||||||
return biases;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNeuronCount() {
|
|
||||||
return weights.columns();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInputCount() {
|
|
||||||
return weights.rows();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MLMatrix getLastOutput() {
|
|
||||||
return lastOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWeights( MLMatrix newWeights ) {
|
|
||||||
weights = newWeights.duplicate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "weights:\n" + weights.toString() + "\nbiases:\n" + biases.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MLMatrix apply( MLMatrix inputs ) {
|
|
||||||
lastInput = inputs.duplicate();
|
|
||||||
lastOutput = inputs
|
|
||||||
.multiplyAddBias(weights, biases)
|
|
||||||
.applyInPlace(activationFunction);
|
|
||||||
|
|
||||||
if( next != null ) {
|
|
||||||
return next.apply(lastOutput);
|
|
||||||
} else {
|
|
||||||
return lastOutput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <V> Function<V, MLMatrix> compose( Function<? super V, ? extends MLMatrix> before ) {
|
|
||||||
return ( in ) -> apply(before.apply(in));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <V> Function<MLMatrix, V> andThen( Function<? super MLMatrix, ? extends V> after ) {
|
|
||||||
return ( in ) -> after.apply(apply(in));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void backprop( MLMatrix expected, double learningRate ) {
|
|
||||||
MLMatrix error, adjustment;
|
|
||||||
if( next == null ) {
|
|
||||||
error = expected.sub(lastOutput);
|
|
||||||
} else {
|
|
||||||
error = expected.multiplyTransposed(next.weights);
|
|
||||||
}
|
|
||||||
|
|
||||||
error.scaleInPlace(
|
|
||||||
lastOutput.apply(this.activationFunctionDerivative)
|
|
||||||
);
|
|
||||||
// Hier schon leraningRate anwenden?
|
|
||||||
// See https://towardsdatascience.com/understanding-and-implementing-neural-networks-in-java-from-scratch-61421bb6352c
|
|
||||||
//delta = MLMath.matrixApply(delta, ( x ) -> learningRate * x);
|
|
||||||
if( previous != null ) {
|
|
||||||
previous.backprop(error, learningRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
biases.addInPlace(
|
|
||||||
error.colSums().scaleInPlace(
|
|
||||||
-learningRate / (double) error.rows()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
adjustment = lastInput.transposedMultiplyAndScale(error, learningRate);
|
|
||||||
weights.addInPlace(adjustment);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dieses Paekt enthält Klassen für Experimente mit Verfahren des maschinellen
|
|
||||||
* Lernens (ML).
|
|
||||||
* <p>
|
|
||||||
* Die hier implementierten Klassen sind eine prototypische Umsetzung von
|
|
||||||
* einfachen neuronalen Netzwerken, mit denen an kleinen Problemstellungen
|
|
||||||
* experimentell Modelle trainiert und angewandt werden können.
|
|
||||||
* <p>
|
|
||||||
* Die Implementierungen sind nicht optimiert und setzen auf native
|
|
||||||
* Java-Methoden. Daher sind sie nur für die Anwendung auf extrem kleine Modelle
|
|
||||||
* in Bildungskontexten gedacht.
|
|
||||||
* <p>
|
|
||||||
* Durch Einbettung wissenschaftlicher Bibliotheken mit optimierten Operationen
|
|
||||||
* lassen sich bessere Ergebnisse erreichen.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm.ml;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* <h2>Die Zeichenmaschine</h2>
|
|
||||||
* <p>
|
|
||||||
* Die <b>Zeichenmaschine</b> ist eine für den Informatikunterricht entwickelte
|
|
||||||
* Bibliothek, die unter anderem an <a
|
|
||||||
* href="https://processing.org/">Processing</a> angelehnt ist. Die Bibliothek
|
|
||||||
* soll einige der üblichen Anfängerschwierigkeiten mit Java vereinfachen und
|
|
||||||
* für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
|
||||||
* <p>
|
|
||||||
* Eine umfassende Dokumentation ist unter <a
|
|
||||||
* href="https://zeichenmaschine.xyz">zeichenmaschine.xyz</a> verfügbar.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm;
|
|
||||||
@@ -1,816 +0,0 @@
|
|||||||
package schule.ngb.zm.shapes;
|
|
||||||
|
|
||||||
import schule.ngb.zm.BasicDrawable;
|
|
||||||
import schule.ngb.zm.Fillable;
|
|
||||||
import schule.ngb.zm.Options;
|
|
||||||
import schule.ngb.zm.Strokeable;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.geom.Point2D;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dies ist die Basisklasse für alle Formen in der Zeichenmaschine.
|
|
||||||
* <p>
|
|
||||||
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
|
||||||
* <p>
|
|
||||||
* Neben den abstrakten Methoden implementieren Unterklassen mindestens zwei
|
|
||||||
* Konstruktoren. Einen Konstruktor, der die Form mit vom Nutzer gegebenen
|
|
||||||
* Parametern initialisiert und einen, der die Werte einer anderen Form
|
|
||||||
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
|
||||||
* beispielsweise so implementiert:
|
|
||||||
* <pre><code>
|
|
||||||
* public Circle( double x, double y, double radius ) {
|
|
||||||
* super(x, y);
|
|
||||||
* this.radius = radius;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public Circle( Circle circle ) {
|
|
||||||
* super(circle.x, circle.y);
|
|
||||||
* copyFrom(circle);
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
* <p>
|
|
||||||
* Außerdem implementieren Unterklassen eine passende {@link #toString()} und
|
|
||||||
* eine {@link #equals(Object)} Methode.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public abstract class Shape extends BasicDrawable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert die x-Koordinate der Form.
|
|
||||||
*/
|
|
||||||
protected double x;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert die y-Koordinate der Form.
|
|
||||||
*/
|
|
||||||
protected double y;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert die Rotation in Grad um den Punkt (x, y).
|
|
||||||
*/
|
|
||||||
protected double rotation = 0.0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert den Skalierungsfaktor.
|
|
||||||
*/
|
|
||||||
protected double scale = 1.0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert den Ankerpunkt.
|
|
||||||
*/
|
|
||||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine neue Form mit den Koordinaten {@code (0,0)}.
|
|
||||||
*/
|
|
||||||
public Shape() {
|
|
||||||
this(0.0, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt eine Form mit den angegebenen Koordinaten.
|
|
||||||
*
|
|
||||||
* @param x Die x-Koordinate.
|
|
||||||
* @param y Die y-Koordinate.
|
|
||||||
*/
|
|
||||||
public Shape( double x, double y ) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die aktuelle x-Koordinate der Form.
|
|
||||||
*
|
|
||||||
* @return Die x-Koordinate.
|
|
||||||
*/
|
|
||||||
public double getX() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die x-Koordinate der Form.
|
|
||||||
*
|
|
||||||
* @param x Die neue x-Koordinate.
|
|
||||||
*/
|
|
||||||
public void setX( double x ) {
|
|
||||||
this.x = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die aktuelle y-Koordinate der Form.
|
|
||||||
*
|
|
||||||
* @return Die y-Koordinate.
|
|
||||||
*/
|
|
||||||
public double getY() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die x-Koordinate der Form.
|
|
||||||
*
|
|
||||||
* @param y Die neue y-Koordinate der Form.
|
|
||||||
*/
|
|
||||||
public void setY( double y ) {
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die aktuelle Breite dieser Form.
|
|
||||||
* <p>
|
|
||||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
|
||||||
* angewandt wurden.
|
|
||||||
* <p>
|
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
|
||||||
* {@link #getBounds()} abgerufen.
|
|
||||||
*
|
|
||||||
* @return Die Breite der Form.
|
|
||||||
*/
|
|
||||||
public abstract double getWidth();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die aktuelle Höhe dieser Form.
|
|
||||||
* <p>
|
|
||||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
|
||||||
* angewandt wurden.
|
|
||||||
* <p>
|
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
|
||||||
* {@link #getBounds()} abgerufen.
|
|
||||||
*
|
|
||||||
* @return Die Höhe der Form.
|
|
||||||
*/
|
|
||||||
public abstract double getHeight();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert die Rotation in Grad.
|
|
||||||
*
|
|
||||||
* @return Rotation in Grad.
|
|
||||||
*/
|
|
||||||
public double getRotation() {
|
|
||||||
return rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dreht die Form um den angegebenen Winkel um ihren Ankerpunkt.
|
|
||||||
*
|
|
||||||
* @param angle Drehwinkel in Grad.
|
|
||||||
*/
|
|
||||||
public void rotate( double angle ) {
|
|
||||||
this.rotation += angle % 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dreht die Form um den angegebenen Winkel um das angegebene Drehzentrum.
|
|
||||||
*
|
|
||||||
* @param center Das Drehzentrum der Drehung.
|
|
||||||
* @param angle Der Drehwinkel.
|
|
||||||
*/
|
|
||||||
public void rotate( Point2D center, double angle ) {
|
|
||||||
Validator.requireNotNull(center, "center");
|
|
||||||
rotate(center.getX(), center.getY(), angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dreht die Form um den angegebenen Drehwinkel um die angegbenen
|
|
||||||
* Koordinaten als Drehzentrum.
|
|
||||||
*
|
|
||||||
* @param x x-Koordiante des Drehzentrums.
|
|
||||||
* @param y y-Koordiante des Drehzentrums.
|
|
||||||
* @param angle Drehwinkel in Grad.
|
|
||||||
*/
|
|
||||||
public void rotate( double x, double y, double angle ) {
|
|
||||||
this.rotation += angle % 360;
|
|
||||||
|
|
||||||
// Rotate x/y position
|
|
||||||
double x1 = this.x - x, y1 = this.y - y;
|
|
||||||
|
|
||||||
double rad = Math.toRadians(angle);
|
|
||||||
double x2 = x1 * Math.cos(rad) - y1 * Math.sin(rad);
|
|
||||||
double y2 = x1 * Math.sin(rad) + y1 * Math.cos(rad);
|
|
||||||
|
|
||||||
this.x = x2 + x;
|
|
||||||
this.y = y2 + y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt die Drehung der Form auf den angegebenen Winkel.
|
|
||||||
*
|
|
||||||
* @param angle Drehwinkel in Grad.
|
|
||||||
*/
|
|
||||||
public void rotateTo( double angle ) {
|
|
||||||
this.rotation = angle % 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt den aktuellen Skalierungsfaktor zurück.
|
|
||||||
*
|
|
||||||
* @return Der Skalierungsfaktor.
|
|
||||||
*/
|
|
||||||
public double getScale() {
|
|
||||||
return scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Skalierungsfaktor auf den angegebenen Faktor.
|
|
||||||
* <p>
|
|
||||||
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
|
||||||
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
|
||||||
* bzw. y-Achse gespiegelt.
|
|
||||||
* <p>
|
|
||||||
* Das Seitenverhältnis wird immer beibehalten.
|
|
||||||
*
|
|
||||||
* @param factor Der Skalierungsfaktor.
|
|
||||||
*/
|
|
||||||
public void scale( double factor ) {
|
|
||||||
scale = factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skaliert die Form um den angegebenen Faktor.
|
|
||||||
* <p>
|
|
||||||
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
|
||||||
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
|
||||||
* bzw. y-Achse gespiegelt.
|
|
||||||
* <p>
|
|
||||||
* Die Skalierung wird zusätzlich zur aktuellen Skalierung angewandt. Wurde
|
|
||||||
* die Form zuvor um den Faktor 0.5 verkleinert und wird dann um 1.5
|
|
||||||
* vergrößert, dann ist die Form im Anschluss ein Drittel kleiner als zu
|
|
||||||
* Beginn ({@code 0.5 * 1.5 = 0.75}).
|
|
||||||
*
|
|
||||||
* @param factor Der Skalierungsfaktor.
|
|
||||||
*/
|
|
||||||
public void scaleBy( double factor ) {
|
|
||||||
scale(scale * factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liefert den aktuellen Ankerpunkt der Form.
|
|
||||||
*
|
|
||||||
* @return Der Ankerpunkt.
|
|
||||||
*/
|
|
||||||
public Options.Direction getAnchor() {
|
|
||||||
return anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Ankerpunkt der Form auf die angegebene Richtung.
|
|
||||||
* <p>
|
|
||||||
* Jede Form hat einen Ankerpunkt, von dem aus sie gezeichnet wird. Jede
|
|
||||||
* {@link schule.ngb.zm.Options.Direction Richtung} beschreibt einen der
|
|
||||||
* Neun Ankerpunkte:
|
|
||||||
* <pre>
|
|
||||||
* NW────N────NE
|
|
||||||
* │ │
|
|
||||||
* │ │
|
|
||||||
* W C E
|
|
||||||
* │ │
|
|
||||||
* │ │
|
|
||||||
* SW────S────SE
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Für den Ankerpunkt {@link #CENTER} wird die Form also ausgehend von den
|
|
||||||
* Koordinaten {@link #x} und {@link #y} um die Hälfte der Breite nach links
|
|
||||||
* und rechts, sowie um die Hälfte der Höhe nach oben und unten gezeichnet.
|
|
||||||
* Fpr den Ankerpunkt {@link #NORTHWEST} dagegen um die gesamte Breite nach
|
|
||||||
* rechts und die Höhe nach unten.
|
|
||||||
* <pre>
|
|
||||||
* setAnchor(CENTER) │ setAnchor(NORTHWEST)
|
|
||||||
* ┌───────────┐ │
|
|
||||||
* │ │ │
|
|
||||||
* │ │ │
|
|
||||||
* │ (x,y) │ │ (x,y)─────────┐
|
|
||||||
* │ │ │ │ │
|
|
||||||
* │ │ │ │ │
|
|
||||||
* └───────────┘ │ │ │
|
|
||||||
* │ │ │
|
|
||||||
* │ │ │
|
|
||||||
* │ └───────────┘
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* Der Ankerpunkt der Form bestimmt bei Transformationen auch die Position
|
|
||||||
* des Drehzentrums und anderer relativer Koordinaten bezüglich der Form.
|
|
||||||
*
|
|
||||||
* @param anchor Der Ankerpunkt.
|
|
||||||
*/
|
|
||||||
public void setAnchor( Options.Direction anchor ) {
|
|
||||||
Validator.requireNotNull(anchor, "anchor");
|
|
||||||
this.anchor = anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
|
||||||
* auf der angegebenen Breite und Höhe des umschließenden Rechtecks.
|
|
||||||
* <p>
|
|
||||||
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
|
|
||||||
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
|
||||||
* bestimmt. Der Ankerpunkt {@link #NORTHWEST} hat daher immer das Ergebnis
|
|
||||||
* {@code (0,0)} und {@link #SOUTHEAST} {@code (width, height)}.
|
|
||||||
* <pre>
|
|
||||||
* (0,0)───(w/2,0)───(w,0)
|
|
||||||
* │ │
|
|
||||||
* │ │
|
|
||||||
* │ │
|
|
||||||
* (0,h/2) (w/2,h/2) (w,h/2)
|
|
||||||
* │ │
|
|
||||||
* │ │
|
|
||||||
* │ │
|
|
||||||
* (0,h)───(w/2,h)───(w,h)
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param width Breite des umschließenden Rechtecks.
|
|
||||||
* @param height Höhe des umschließenden Rechtecks.
|
|
||||||
* @param anchor Gesuchter Ankerpunkt.
|
|
||||||
* @return Ein {@link Point2D} mit den relativen Koordinaten.
|
|
||||||
*/
|
|
||||||
public static Point2D.Double getAnchorPoint( double width, double height, Options.Direction anchor ) {
|
|
||||||
double wHalf = width * .5, hHalf = height * .5;
|
|
||||||
|
|
||||||
return new Point2D.Double(
|
|
||||||
wHalf + wHalf * anchor.x,
|
|
||||||
hHalf + hHalf * anchor.y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bestimmt die Koordinaten des angegebenen Ankers der Form relativ zum
|
|
||||||
* aktuellen {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
|
||||||
*
|
|
||||||
* @param anchor Die Richtung des Ankers.
|
|
||||||
* @return Der relative Ankerpunkt.
|
|
||||||
* @see #getAnchorPoint(double, double, Options.Direction)
|
|
||||||
*/
|
|
||||||
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
|
||||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
|
||||||
|
|
||||||
// anchor == CENTER
|
|
||||||
return new Point2D.Double(
|
|
||||||
wHalf * (anchor.x - this.anchor.x),
|
|
||||||
hHalf * (anchor.y - this.anchor.y)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ermittelt die absoluten Koordinaten des angegebenen
|
|
||||||
* {@link #setAnchor(Options.Direction) Ankers}.
|
|
||||||
* <p>
|
|
||||||
* Die absoluten Koordinaten werden bestimmt durch die Position der Form
|
|
||||||
* {@code (x,y)} plus die
|
|
||||||
* {@link #getAnchorPoint(Options.Direction) relativen Koordinaten} des
|
|
||||||
* Ankers.
|
|
||||||
*
|
|
||||||
* <b>Wichtig:</b> Die Berechnung berücksichtigt derzeit keine Rotationen
|
|
||||||
* und Transformationen der Form.
|
|
||||||
*
|
|
||||||
* @param anchor Die Richtung des Ankers.
|
|
||||||
* @return Der absolute Ankerpunkt.
|
|
||||||
* @see #getAnchorPoint(double, double, Options.Direction)
|
|
||||||
*/
|
|
||||||
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
|
||||||
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
|
||||||
Point2D.Double ap = getAnchorPoint(anchor);
|
|
||||||
ap.x += getX();
|
|
||||||
ap.y += getY();
|
|
||||||
return ap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
|
||||||
* <p>
|
|
||||||
* Unterklassen überschreiben diese Methode, um weitere Eigenschaften zu
|
|
||||||
* kopieren (zum Beispiel den Radius eines Kreises). Überschreibende
|
|
||||||
* Methoden sollten immer mit dem Aufruf {@code super.copyFrom(shape)} die
|
|
||||||
* Basiseigenschaften kopieren.
|
|
||||||
* <p>
|
|
||||||
* Die Methode kopiert so viele Eigenschaften wie möglich von der
|
|
||||||
* angegebenen Form in diese. Wenn die andere Form einen anderen Typ hat,
|
|
||||||
* dann werden trotzdem die Basiseigenschaften (Konturlinie, Füllung,
|
|
||||||
* Position, Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese
|
|
||||||
* Form kopiert. Soweit sinnvoll übernehmen implementierende Unterklassen
|
|
||||||
* auch andere Werte. Eine {@link Ellipse} kopiert beispielsweise auch die
|
|
||||||
* Breite und Höhe eines {@link Rectangle}.
|
|
||||||
* <p>
|
|
||||||
* Wird {@code null} übergeben, dann passiert nichts.
|
|
||||||
*
|
|
||||||
* @param shape Die Originalform, von der kopiert wird.
|
|
||||||
*/
|
|
||||||
public void copyFrom( Shape shape ) {
|
|
||||||
if( shape != null ) {
|
|
||||||
moveTo(shape.x, shape.y);
|
|
||||||
setFillColor(shape.getFillColor());
|
|
||||||
setStrokeColor(shape.getStrokeColor());
|
|
||||||
setStrokeWeight(shape.getStrokeWeight());
|
|
||||||
setStrokeType(shape.getStrokeType());
|
|
||||||
setStrokeJoin(shape.getStrokeJoin());
|
|
||||||
visible = shape.isVisible();
|
|
||||||
rotation = shape.getRotation();
|
|
||||||
scale(shape.getScale());
|
|
||||||
setAnchor(shape.getAnchor());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine Kopie dieser Form mit denselben Eigenschaften.
|
|
||||||
* <p>
|
|
||||||
* Unterklassen implementieren diese Methode mit dem genauen Typ der
|
|
||||||
* Unterklasse. In {@link Rectangle} sieht die Umsetzung beispielsweise so
|
|
||||||
* aus:
|
|
||||||
* <pre><code>
|
|
||||||
* public Rectangle copy() {
|
|
||||||
* return new Rectangle(this);
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
* <p>
|
|
||||||
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
|
||||||
* Form zu erhalten. In der Regel besitzt aber jede Form einen Konstruktor,
|
|
||||||
* der alle Werte einer andern Form übernimmt. Die gezeigte Implementierung
|
|
||||||
* ist daher im Regelfall ausreichend.
|
|
||||||
*
|
|
||||||
* @return Eine genaue Kopie dieser Form.
|
|
||||||
*/
|
|
||||||
public abstract Shape copy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt eine {@link java.awt.Shape Java-AWT Shape} Version dieser Form
|
|
||||||
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
|
||||||
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
|
||||||
* <p>
|
|
||||||
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, liefert die
|
|
||||||
* Methode {@code null}.
|
|
||||||
*
|
|
||||||
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
|
||||||
* {@code null}.
|
|
||||||
*/
|
|
||||||
public abstract java.awt.Shape getShape();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Begrenzungen der Form zurück.
|
|
||||||
* <p>
|
|
||||||
* Ein {@code Bounds}-Objekt beschreibt eine "<a
|
|
||||||
* href="https://gdbooks.gitbooks.io/3dcollisions/content/Chapter1/aabb.html">Axis
|
|
||||||
* Aligned Bounding Box</a>" (AABB).
|
|
||||||
*
|
|
||||||
* @return Die Abgrenzungen der Form nach Anwendung der Transformationen.
|
|
||||||
*/
|
|
||||||
public Bounds getBounds() {
|
|
||||||
return new Bounds(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verschiebt die Form um die angegebenen Werte entlang der
|
|
||||||
* Koordinatenachsen.
|
|
||||||
*
|
|
||||||
* @param dx Verschiebung entlang der x-Achse.
|
|
||||||
* @param dy Verschiebung entlang der y-Achse.
|
|
||||||
*/
|
|
||||||
public void move( double dx, double dy ) {
|
|
||||||
x += dx;
|
|
||||||
y += dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt die Form an die angegebenen Koordinaten.
|
|
||||||
*
|
|
||||||
* @param x Die neue x-Koordinate.
|
|
||||||
* @param y Die neue y-Koordinate.
|
|
||||||
*/
|
|
||||||
public void moveTo( double x, double y ) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt die Form an dieselben Koordinaten wie die angegebene Form.
|
|
||||||
*
|
|
||||||
* @param shape Eine andere Form.
|
|
||||||
*/
|
|
||||||
public void moveTo( Shape shape ) {
|
|
||||||
moveTo(shape.getX(), shape.getY());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt die Form zum angegebenen Ankerpunkt der angegebenen Form.
|
|
||||||
*
|
|
||||||
* @param shape Die andere Form.
|
|
||||||
* @param dir Die Richtung des Ankerpunktes.
|
|
||||||
* @see #moveTo(Shape, Options.Direction, double)
|
|
||||||
*/
|
|
||||||
public void moveTo( Shape shape, Options.Direction dir ) {
|
|
||||||
moveTo(shape, dir, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen
|
|
||||||
* Form.
|
|
||||||
* <p>
|
|
||||||
* Mit {@code buff} wird ein zusätzlicher Abstand angegeben, um den die Form
|
|
||||||
* entlang des Ankerpunktes {@code anchor} verschoben wird.
|
|
||||||
* <p>
|
|
||||||
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
|
||||||
* {@code buff} oberhalb der oberen Kante der zweiten Form verschoben.
|
|
||||||
* <p>
|
|
||||||
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
|
||||||
* <pre>
|
|
||||||
* ┌─────────┐
|
|
||||||
* │ │
|
|
||||||
* W B │
|
|
||||||
* ┌─────┐ │ │
|
|
||||||
* │ │ └─────────┘
|
|
||||||
* W A │
|
|
||||||
* │ │
|
|
||||||
* └─────┘
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* bringt sie der Aufruf {@code B.moveTo(A, DOWN, 0)} in diese Ausrichtung:
|
|
||||||
* <pre>
|
|
||||||
* B.moveTo(A, WEST, 0) │ B.moveTo(A, WEST, 10)
|
|
||||||
* │
|
|
||||||
* ┌─────┬───┐ │ ┌┬────┬────┐
|
|
||||||
* │ │ │ │ ││ │ │
|
|
||||||
* │ A B│ │ │ ││ A B │
|
|
||||||
* │ │ │ │ ││ │ │
|
|
||||||
* └─────┴───┘ │ └┴────┴────┘
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param shape Die andere Form.
|
|
||||||
* @param dir Die Richtung des Ankerpunktes.
|
|
||||||
* @param buff Der Abstand zum angegebenen Ankerpunkt.
|
|
||||||
*/
|
|
||||||
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
|
||||||
Point2D ap = shape.getAbsAnchorPoint(dir);
|
|
||||||
|
|
||||||
this.x = ap.getX() + dir.x * buff;
|
|
||||||
this.y = ap.getY() + dir.y * buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt die Form an den Rand der Zeichenfläche in der angegebenen
|
|
||||||
* Richtung.
|
|
||||||
*
|
|
||||||
* @param dir Die Richtung.
|
|
||||||
*/
|
|
||||||
public void alignTo( Options.Direction dir ) {
|
|
||||||
alignTo(dir, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt die Form mit dem angegebenen Abstand an den Rand der Zeichenfläche
|
|
||||||
* in der angegebenen Richtung aus.
|
|
||||||
*
|
|
||||||
* @param dir Die Richtung.
|
|
||||||
* @param buff Der Abstand zum Rand.
|
|
||||||
*/
|
|
||||||
public void alignTo( Options.Direction dir, double buff ) {
|
|
||||||
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
|
||||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
|
||||||
|
|
||||||
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
|
||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt den Ankerpunkt dieser Form in der angegebenen Richtung zum
|
|
||||||
* Gleichen Ankerpunkt der anderen Form.
|
|
||||||
*
|
|
||||||
* @param shape Die andere Form.
|
|
||||||
* @param dir Die Richtung des Ankerpunktes.
|
|
||||||
* @see #alignTo(Shape, Options.Direction, double)
|
|
||||||
*/
|
|
||||||
public void alignTo( Shape shape, Options.Direction dir ) {
|
|
||||||
alignTo(shape, dir, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
|
||||||
* aus.
|
|
||||||
* <p>
|
|
||||||
* {@code buff} gibt einen Abstand ab, um den diese From versetzt
|
|
||||||
* ausgerichtet wird.
|
|
||||||
* <p>
|
|
||||||
* Für {@link #DOWN} wird beispielsweise die y-Koordinate der unteren Kante
|
|
||||||
* dieser Form an der unteren Kante von {@code shape} ausgerichtet. Die
|
|
||||||
* x-Koordinate wird in dem Fall nicht verändert.
|
|
||||||
* <p>
|
|
||||||
* Befinden sich die Formen beispielsweise in folgender Position:
|
|
||||||
* <pre>
|
|
||||||
* ┌─────┐
|
|
||||||
* │ │
|
|
||||||
* │ B │
|
|
||||||
* ┌─────┐ │ │
|
|
||||||
* │ │ └──D──┘
|
|
||||||
* │ A │
|
|
||||||
* │ │
|
|
||||||
* └──D──┘
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* <p>
|
|
||||||
* werden sie durch {@code alignTo} so positioniert:
|
|
||||||
* <pre>
|
|
||||||
* B.alignTo(A, EAST, 0) │ B.alignTo(A, EAST, 10)
|
|
||||||
* │
|
|
||||||
* ┌─────┐ ┌─────┐ │ ┌─────┐
|
|
||||||
* │ │ │ │ │ │ │ ┌─────┐
|
|
||||||
* │ A │ │ B │ │ │ A │ │ │
|
|
||||||
* │ │ │ │ │ │ │ │ B │
|
|
||||||
* └──D──┘ └──D──┘ │ └──D──┘ │ │
|
|
||||||
* │ └──D──┘
|
|
||||||
* │
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param shape Die andere Form.
|
|
||||||
* @param dir Die Richtung.
|
|
||||||
* @param buff Der Abstand.
|
|
||||||
*/
|
|
||||||
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
|
||||||
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
|
||||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
|
||||||
|
|
||||||
this.x += Math.abs(dir.x) * (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
|
||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param shape
|
|
||||||
* @param dir
|
|
||||||
* @see #nextTo(Shape, Options.Direction, double)
|
|
||||||
*/
|
|
||||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
|
||||||
nextTo(shape, dir, DEFAULT_BUFFER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
|
||||||
* Ankerpunktes.
|
|
||||||
* <p>
|
|
||||||
* Im Gegensatz zu {@link #moveTo(Shape, Options.Direction, double)} wird
|
|
||||||
* die Breite bzw. Höhe der Formen berücksichtigt und die Formen so
|
|
||||||
* platziert, dass keine Überlappungen vorhanden sind.
|
|
||||||
* <p>
|
|
||||||
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
|
||||||
* <pre>
|
|
||||||
* ┌─────┐
|
|
||||||
* │ │
|
|
||||||
* W B │
|
|
||||||
* ┌──────┐ │ │
|
|
||||||
* │ │ └─────┘
|
|
||||||
* │ A E
|
|
||||||
* │ │
|
|
||||||
* └──────┘
|
|
||||||
* </pre>
|
|
||||||
* <p>
|
|
||||||
* bringt sie der Aufruf {@code B.nextTo(A, EAST, 0)} in diese Ausrichtung:
|
|
||||||
* <pre>
|
|
||||||
* B.nextTo(A, EAST, 0) │ B.nextTo(A, EAST, 10)
|
|
||||||
* │
|
|
||||||
* ┌─────┬─────┐ │ ┌─────┐ ┌─────┐
|
|
||||||
* │ │ │ │ │ │ │ │
|
|
||||||
* │ A │ B │ │ │ A │ │ B │
|
|
||||||
* │ │ │ │ │ │ │ │
|
|
||||||
* └─────┴─────┘ │ └─────┘ └─────┘
|
|
||||||
* │
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param shape
|
|
||||||
* @param dir
|
|
||||||
* @param buff
|
|
||||||
*/
|
|
||||||
public void nextTo( Shape shape, Options.Direction dir, double buff ) {
|
|
||||||
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
|
||||||
Point2D anchorThis = this.getAbsAnchorPoint(dir.inverse());
|
|
||||||
|
|
||||||
this.x += (anchorShape.getX() - anchorThis.getX()) + dir.x * buff;
|
|
||||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
|
||||||
Point2D apDir = getAbsAnchorPoint(dir);
|
|
||||||
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
|
||||||
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
|
||||||
Point2D ap = getAbsAnchorPoint(CENTER);
|
|
||||||
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*public void shear( double dx, double dy ) {
|
|
||||||
verzerrung.shear(dx, dy);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public AffineTransform getTransform() {
|
|
||||||
// Point2D.Double anchorPoint = getAnchorPoint();
|
|
||||||
Point2D.Double basePoint = getAnchorPoint(Options.Direction.NORTHWEST);
|
|
||||||
|
|
||||||
AffineTransform transform = new AffineTransform();
|
|
||||||
transform.translate(x, y);
|
|
||||||
transform.rotate(Math.toRadians(rotation));
|
|
||||||
//transform.scale(scale, scale);
|
|
||||||
//transform.translate(-anchorPoint.x, -anchorPoint.y);
|
|
||||||
transform.translate(basePoint.x, basePoint.y);
|
|
||||||
return transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeichnet die Form.
|
|
||||||
*
|
|
||||||
* @param graphics
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final void draw( Graphics2D graphics ) {
|
|
||||||
draw(graphics, getTransform());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeichnet die Form, aber wendet zuvor noch eine zusätzliche
|
|
||||||
* Transformations- matrix an. Wird u.A. von der {@link ShapeGroup}
|
|
||||||
* verwendet.
|
|
||||||
*
|
|
||||||
* @param graphics
|
|
||||||
* @param transform
|
|
||||||
*/
|
|
||||||
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
|
||||||
if( !visible ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
java.awt.Shape shape = getShape();
|
|
||||||
if( shape != null ) {
|
|
||||||
if( transform != null ) {
|
|
||||||
shape = transform.createTransformedShape(shape);
|
|
||||||
}
|
|
||||||
|
|
||||||
java.awt.Color currentColor = graphics.getColor();
|
|
||||||
fillShape(shape, graphics);
|
|
||||||
strokeShape(shape, graphics);
|
|
||||||
graphics.setColor(currentColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vergleicht die Form mit einem anderen Objekt. Handelt es sich bei dem
|
|
||||||
* Objekt um eine andere Form, werden Position, Drehwinkel und Skalierung
|
|
||||||
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
|
||||||
* Eigenschaften zu berücksichtigen.
|
|
||||||
* <p>
|
|
||||||
* Die Eigenschaften, die durch {@link Fillable} und {@link Strokeable}
|
|
||||||
* impliziert werden, werden nicht verglichen.
|
|
||||||
*
|
|
||||||
* @param o Ein anderes Objekt.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean equals( Object o ) {
|
|
||||||
if( this == o ) return true;
|
|
||||||
if( o == null || getClass() != o.getClass() ) return false;
|
|
||||||
Shape pShape = (Shape) o;
|
|
||||||
return Double.compare(pShape.x, x) == 0 &&
|
|
||||||
Double.compare(pShape.y, y) == 0 &&
|
|
||||||
Double.compare(pShape.rotation, rotation) == 0 &&
|
|
||||||
Double.compare(pShape.scale, scale) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit den aktuellen
|
|
||||||
* Kontureigenschaften auf den Grafik-Kontext zu zeichnen. Die Methode
|
|
||||||
* verändert gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt
|
|
||||||
* sie nicht auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
|
||||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
|
||||||
*
|
|
||||||
* @param shape Die zu zeichnende Java-AWT Form
|
|
||||||
* @param graphics Das Grafikobjekt.
|
|
||||||
*/
|
|
||||||
protected void strokeShape( java.awt.Shape shape, Graphics2D graphics ) {
|
|
||||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
|
||||||
&& strokeWeight > 0.0 ) {
|
|
||||||
graphics.setColor(strokeColor.getJavaColor());
|
|
||||||
graphics.setStroke(getStroke());
|
|
||||||
graphics.draw(shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsmethode für Unterklassen, um die angegebene Form mit der aktuellen
|
|
||||||
* Füllung auf den Grafik-Kontext zu zeichnen. Die Methode verändert
|
|
||||||
* gegebenenfalls die aktuelle Farbe des Grafikobjekts und setzt sie nicht
|
|
||||||
* auf den Ursprungswert zurück, wie von {@link #draw(Graphics2D)}
|
|
||||||
* gefordert. Dies sollte die aufrufende Unterklasse übernehmen.
|
|
||||||
*
|
|
||||||
* @param shape Die zu zeichnende Java-AWT Form
|
|
||||||
* @param graphics Das Grafikobjekt.
|
|
||||||
*/
|
|
||||||
protected void fillShape( java.awt.Shape shape, Graphics2D graphics ) {
|
|
||||||
if( fill != null ) {
|
|
||||||
graphics.setPaint(fill);
|
|
||||||
graphics.fill(shape);
|
|
||||||
} else if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
|
||||||
graphics.setColor(fillColor.getJavaColor());
|
|
||||||
graphics.fill(shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
/**
|
|
||||||
* Diese Paket enthält Formen, die Diagramme darstellen.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm.shapes.charts;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dieses Paket enthält Implementationen der abstrakten
|
|
||||||
* {@link schule.ngb.zm.shapes.Shape} Klasse.
|
|
||||||
*
|
|
||||||
* Jede Unterklasse von {@code Shape} stellt eine konkrete Form wie ein
|
|
||||||
* {@link schule.ngb.zm.shapes.Rectangle Rechteck}, ein
|
|
||||||
* {@link schule.ngb.zm.shapes.Circle Kreis} oder ein
|
|
||||||
* {@link schule.ngb.zm.shapes.Picture Bild} dar.
|
|
||||||
*
|
|
||||||
* Mit {@link schule.ngb.zm.shapes.ShapeGroup} können Formen gruppiert
|
|
||||||
* und gemeinsam transformiert werden.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm.shapes;
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
package schule.ngb.zm.util;
|
|
||||||
|
|
||||||
import java.lang.ref.Reference;
|
|
||||||
import java.lang.ref.SoftReference;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ein Cache ist ein {@link Map} Implementation, die Inhaltsobjekte in einer
|
|
||||||
* {@link Reference} speichert und als Zwischenspeicher für Objekte dienen kann,
|
|
||||||
* deren Erstellung aufwendig ist.
|
|
||||||
* <p>
|
|
||||||
* Für einen Cache ist nicht garantiert, dass ein eingefügtes Objekt beim
|
|
||||||
* nächsten Aufruf noch vorhanden ist, da die Referenz inzwischen vom Garbage
|
|
||||||
* Collector gelöscht worden sein kann.
|
|
||||||
* <p>
|
|
||||||
* Als interne Map wird einen {@link ConcurrentHashMap} verwendet.
|
|
||||||
* <p>
|
|
||||||
* Ein passender Cache wird mittels der Fabrikmethoden {@link #newSoftCache()}
|
|
||||||
* und {@link #newWeakCache()} erstellt.
|
|
||||||
* <pre><code>
|
|
||||||
* Cache<String, Image> imageCache = Cache.newSoftCache();
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @param <K> Der Typ der Schlüssel.
|
|
||||||
* @param <V> Der Typ der Objekte.
|
|
||||||
*/
|
|
||||||
public final class Cache<K, V> implements Map<K, V> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen Cache mit {@link SoftReference} Referenzen.
|
|
||||||
*
|
|
||||||
* @param <K> Der Typ der Schlüssel.
|
|
||||||
* @param <V> Der Typ der Objekte.
|
|
||||||
* @return Ein Cache.
|
|
||||||
*/
|
|
||||||
public static <K, V> Cache<K, V> newSoftCache() {
|
|
||||||
return new Cache<>(SoftReference::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen Cache mit {@link WeakReference} Referenzen.
|
|
||||||
*
|
|
||||||
* @param <K> Der Typ der Schlüssel.
|
|
||||||
* @param <V> Der Typ der Objekte.
|
|
||||||
* @return Ein Cache.
|
|
||||||
*/
|
|
||||||
public static <K, V> Cache<K, V> newWeakCache() {
|
|
||||||
return new Cache<>(WeakReference::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Map<K, Reference<V>> cache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final Reference<V> NOCACHE;
|
|
||||||
|
|
||||||
private final Function<V, Reference<V>> refSupplier;
|
|
||||||
|
|
||||||
private Cache( Function<V, Reference<V>> refSupplier ) {
|
|
||||||
this.refSupplier = refSupplier;
|
|
||||||
NOCACHE = refSupplier.apply(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return cache.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return cache.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsKey( Object key ) {
|
|
||||||
return cache.containsKey(key) && cache.get(key).get() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsValue( Object value ) {
|
|
||||||
return cache.values().stream()
|
|
||||||
.anyMatch(( ref ) -> ref.get() != null && ref.get() == value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V get( Object key ) {
|
|
||||||
if( cache.containsKey(key) ) {
|
|
||||||
return cache.get(key).get();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deaktiviert das Caching für den angegebenen Schlüssel.
|
|
||||||
* <p>
|
|
||||||
* Folgende Aufrufe von {@link #put(Object, Object)} mit demselben Schlüssel
|
|
||||||
* haben keinen Effekt. Um das Caching wieder zu aktivieren, muss
|
|
||||||
* {@link #remove(Object)} mit dem Schlüssel aufgerufen werden,
|
|
||||||
*
|
|
||||||
* @param key Der Schlüssel.
|
|
||||||
*/
|
|
||||||
public void disableCache( K key ) {
|
|
||||||
cache.put(key, NOCACHE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob der für den angegebenen Schlüssel zuvor
|
|
||||||
* {@link #disableCache(Object)} aufgerufen wurde.
|
|
||||||
*
|
|
||||||
* @param key Der Schlüssel.
|
|
||||||
* @return {@code true}, wenn der Schlüssel nicht gespeichert wird.
|
|
||||||
*/
|
|
||||||
public boolean isCachingDisabled( K key ) {
|
|
||||||
return cache.get(key) == NOCACHE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V put( K key, V value ) {
|
|
||||||
if( !isCachingDisabled(key) ) {
|
|
||||||
V prev = remove(key);
|
|
||||||
cache.put(key, refSupplier.apply(value));
|
|
||||||
return prev;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V remove( Object key ) {
|
|
||||||
Reference<V> ref = cache.get(key);
|
|
||||||
cache.remove(key);
|
|
||||||
|
|
||||||
V prev = null;
|
|
||||||
if( ref != null ) {
|
|
||||||
prev = ref.get();
|
|
||||||
ref.clear();
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putAll( Map<? extends K, ? extends V> m ) {
|
|
||||||
for( Entry<? extends K, ? extends V> e : m.entrySet() ) {
|
|
||||||
put(e.getKey(), e.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
cache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<K> keySet() {
|
|
||||||
return cache.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<V> values() {
|
|
||||||
return cache.values().stream()
|
|
||||||
.filter(( ref ) -> ref.get() != null)
|
|
||||||
.map(( ref ) -> ref.get())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Entry<K, V>> entrySet() {
|
|
||||||
return cache.entrySet().stream()
|
|
||||||
.filter(( e ) -> e.getValue() != null && e.getValue().get() != null)
|
|
||||||
.map(( e ) -> new SoftCacheEntryView(e.getKey()))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class SoftCacheEntryView implements Map.Entry<K, V> {
|
|
||||||
|
|
||||||
private K key;
|
|
||||||
|
|
||||||
public SoftCacheEntryView( K key ) {
|
|
||||||
this.key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public K getKey() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V getValue() {
|
|
||||||
return Cache.this.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V setValue( V value ) {
|
|
||||||
return Cache.this.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
package schule.ngb.zm.util;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Hilfsklasse, um Dinge zu zählen.
|
|
||||||
* <p>
|
|
||||||
* Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden,
|
|
||||||
* die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder
|
|
||||||
* Methoden verändert werden kann.
|
|
||||||
* <p>
|
|
||||||
* Der Zähler kann aber auch Objekte zählen, indem die Instanzen an
|
|
||||||
* {@link #count(Object)} übergeben werden. Am Ende kann mit {@link #getCount()}
|
|
||||||
* die Anzahl der Objekte abgerufen werden.
|
|
||||||
* <p>
|
|
||||||
* Handelt es sich bei den Objekten um Zahlen, dann merkt sich ein
|
|
||||||
* {@code Counter} auch das Maximum, das Minimum, die Summe und den Durchschnitt
|
|
||||||
* der gezählten Werte.
|
|
||||||
* <p>
|
|
||||||
* Ein Zähler kann auch komplette Arrays oder Listen von Zahlen zählen und die
|
|
||||||
* obigen Statistiken auswerten.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public final class Counter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen {@code Counter}, der alle Integer im angegebenen
|
|
||||||
* Array gezählt hat.
|
|
||||||
*
|
|
||||||
* @param values Die zu zählenden Werte.
|
|
||||||
* @return Ein neuer {@code Counter}.
|
|
||||||
*/
|
|
||||||
public static Counter fromArray( int[] values ) {
|
|
||||||
return new Counter().countAll(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen {@code Counter}, der alle Doubles im angegebenen
|
|
||||||
* Array gezählt hat.
|
|
||||||
*
|
|
||||||
* @param values Die zu zählenden Werte.
|
|
||||||
* @return Ein neuer {@code Counter}.
|
|
||||||
*/
|
|
||||||
public static Counter fromArray( double[] values ) {
|
|
||||||
return new Counter().countAll(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen {@code Counter}, der alle Zahlen im angegebenen
|
|
||||||
* Array gezählt hat.
|
|
||||||
*
|
|
||||||
* @param values Die zu zählenden Werte.
|
|
||||||
* @return Ein neuer {@code Counter}.
|
|
||||||
*/
|
|
||||||
public static Counter fromArray( Number[] values ) {
|
|
||||||
return new Counter().countAll(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen {@code Counter}, der alle Zahlen in der angegebenen
|
|
||||||
* Liste gezählt hat.
|
|
||||||
*
|
|
||||||
* @param values Die zu zählenden Werte.
|
|
||||||
* @return Ein neuer {@code Counter}.
|
|
||||||
*/
|
|
||||||
public static Counter fromList( List<Number> values ) {
|
|
||||||
return new Counter().countAll(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aktuelle Anzahl gezählter Werte.
|
|
||||||
*/
|
|
||||||
private final AtomicInteger count = new AtomicInteger(0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Statistiken zu den gezählten Werten.
|
|
||||||
*/
|
|
||||||
private double min = Double.NaN, max = Double.NaN, sum = Double.NaN;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen, leeren {@code Counter}.
|
|
||||||
*/
|
|
||||||
public Counter() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ertstellt einen neuen {@code Counter}, der mit dem angegebenen Wert
|
|
||||||
* initialisiert ist.
|
|
||||||
*
|
|
||||||
* @param initial Wert des Zählers zu Beginn.
|
|
||||||
*/
|
|
||||||
public Counter( int initial ) {
|
|
||||||
count.set(initial);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die aktuelle Anzahl.
|
|
||||||
*/
|
|
||||||
public int getCount() {
|
|
||||||
return count.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Das Maxium der bisher gezählten Werte.
|
|
||||||
*/
|
|
||||||
public double getMax() {
|
|
||||||
synchronized( count ) {
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Das Minimum der bisher gezählten Werte.
|
|
||||||
*/
|
|
||||||
public double getMin() {
|
|
||||||
synchronized( count ) {
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Die Summe der bisher gezählten Werte.
|
|
||||||
*/
|
|
||||||
public double getSum() {
|
|
||||||
synchronized( count ) {
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Der Mittelwert der bisher gezählten Werte.
|
|
||||||
*/
|
|
||||||
public double getAvg() {
|
|
||||||
if( Double.isNaN(sum) ) {
|
|
||||||
return Double.NaN;
|
|
||||||
} else {
|
|
||||||
return sum / count.doubleValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Zähler auf den angegebenen Wert.
|
|
||||||
* <p>
|
|
||||||
* Die anderen Statistiken werden nicht verändert.
|
|
||||||
*
|
|
||||||
* @param count Der neue Wert des Zählers.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Counter setCount( int count ) {
|
|
||||||
this.count.set(count);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den Zähler auf Null.
|
|
||||||
* <p>
|
|
||||||
* Die Statistiken werden auf {@link Double#NaN} gesetzt.
|
|
||||||
*
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Counter reset() {
|
|
||||||
count.set(0);
|
|
||||||
synchronized( count ) {
|
|
||||||
min = Double.NaN;
|
|
||||||
max = Double.NaN;
|
|
||||||
sum = Double.NaN;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erhöht den Zähler um Eins.
|
|
||||||
* <p>
|
|
||||||
* Die anderen Statistiken werden nicht verändert.
|
|
||||||
*
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Counter inc() {
|
|
||||||
this.count.incrementAndGet();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verringert den Zähler um Eins.
|
|
||||||
* <p>
|
|
||||||
* Die anderen Statistiken werden nicht verändert.
|
|
||||||
*
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Counter dec() {
|
|
||||||
this.count.decrementAndGet();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zählt den angegebenen Wert.
|
|
||||||
* <p>
|
|
||||||
* Erhöht den Zähler um Eins und aktualisiert die Statistiken.
|
|
||||||
*
|
|
||||||
* @param value Der neue Wert.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Counter count( double value ) {
|
|
||||||
inc();
|
|
||||||
// Update stats
|
|
||||||
synchronized( count ) {
|
|
||||||
sum = Double.isNaN(sum) ? value : sum + value;
|
|
||||||
if( Double.isNaN(max) || max < value )
|
|
||||||
max = value;
|
|
||||||
if( Double.isNaN(min) || min > value )
|
|
||||||
min = value;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zählt die angegebene Zahl.
|
|
||||||
* <p>
|
|
||||||
* Erhöht den Zähler um Eins und aktualisiert die Statistiken.
|
|
||||||
*
|
|
||||||
* @param num Die neue Zahl.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Counter count( Number num ) {
|
|
||||||
return count(num.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zählt das angegebenen Objekt.
|
|
||||||
* <p>
|
|
||||||
* Erhöht den Zähler um Eins.
|
|
||||||
*
|
|
||||||
* @param obj Ein beliebiges Objekt.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Counter count( Object obj ) {
|
|
||||||
if( obj instanceof Number ) {
|
|
||||||
return count((Number) obj);
|
|
||||||
} else {
|
|
||||||
inc();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zöhlt alle Werte im angegebenen Array.
|
|
||||||
*
|
|
||||||
* @param values Das Array der neuen Werte.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
* @see #count(double)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public synchronized Counter countAll( double[] values ) {
|
|
||||||
for( double value : values ) {
|
|
||||||
count(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zöhlt alle Werte im angegebenen Array.
|
|
||||||
*
|
|
||||||
* @param values Das Array der neuen Werte.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
* @see #count(double)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public synchronized Counter countAll( int[] values ) {
|
|
||||||
for( double value : values ) {
|
|
||||||
count(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zöhlt alle Zahlen im angegebenen Array.
|
|
||||||
*
|
|
||||||
* @param values Das Array der neuen Zahlen.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
* @see #count(Number)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public synchronized Counter countAll( Number[] values ) {
|
|
||||||
for( Number value : values ) {
|
|
||||||
count(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zöhlt alle Zahlen in der angegebenen Sammlung.
|
|
||||||
*
|
|
||||||
* @param values Die Sammlung der neuen Zahlen.
|
|
||||||
* @return Dieser Zähler selbst (method chaining).
|
|
||||||
* @see #count(Number)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public synchronized Counter countAll( Collection<Number> values ) {
|
|
||||||
for( Number value : values ) {
|
|
||||||
count(value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
package schule.ngb.zm.util;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
|
||||||
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.FontMetrics;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.font.LineMetrics;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.*;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static java.lang.Math.exp;
|
|
||||||
import static java.lang.Math.log;
|
|
||||||
import static schule.ngb.zm.Constants.random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
|
|
||||||
* <p>
|
|
||||||
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen.
|
|
||||||
* Unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public final class Faker {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* URL, von der extern generierte Fake-Bilder geladen werden können.
|
|
||||||
* <p>
|
|
||||||
* Die URL wird als Format-String definiert mit zwei {@code %d}
|
|
||||||
* Platzhaltern, die durch die Breite und Höhe des gewünschten Bildes
|
|
||||||
* ersetzt werden.
|
|
||||||
*/
|
|
||||||
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
|
|
||||||
* <p>
|
|
||||||
* Jeder Datensatz besteht aus einem String-Array mit den folgenden Daten
|
|
||||||
* <ul>
|
|
||||||
* <li><code>fakeUser[i][0]</code>: Vorname</li>
|
|
||||||
* <li><code>fakeUser[i][1]</code>: Nachname</li>
|
|
||||||
* <li><code>fakeUser[i][2]</code>: Geschlecht</li>
|
|
||||||
* <li><code>fakeUser[i][3]</code>: Nutzername</li>
|
|
||||||
* <li><code>fakeUser[i][4]</code>: Passwort</li>
|
|
||||||
* <li><code>fakeUser[i][5]</code>: E-Mail</li>
|
|
||||||
* <li><code>fakeUser[i][6]</code>: Geburtsdatum</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static String[][] fakeUsers( int count ) {
|
|
||||||
return randomSample("users", count, ( line ) -> line.split(","), String[].class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static String[] fakeNames( int count ) {
|
|
||||||
return randomSample("users", count, ( line ) -> line.split(",")[0], String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen im Format
|
|
||||||
* "Vorname Nachname".
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static String[] fakeFullnames( int count ) {
|
|
||||||
return randomSample("users", count, ( line ) -> {
|
|
||||||
String[] parts = line.split(",");
|
|
||||||
return parts[0] + " " + parts[1];
|
|
||||||
}, String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static String[] fakeUsernames( int count ) {
|
|
||||||
return randomSample("users", count, ( line ) -> line.split(",")[3], String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static String[] fakePasswords( int count ) {
|
|
||||||
return randomSample("users", count, ( line ) -> line.split(",")[4], String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static String[] fakeEmails( int count ) {
|
|
||||||
return randomSample("users", count, ( line ) -> line.split(",")[5], String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger deutscher
|
|
||||||
* Wörter.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static String[] fakeStrings( int count ) {
|
|
||||||
return randomSample("words", count, ( line ) -> line, String.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
|
||||||
* {@code LocalDate}-Objekte, die ein Datum ohne Uhrzeit beschreiben.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static LocalDate[] fakeDates( int count ) {
|
|
||||||
long nowEpoch = LocalDate.now().toEpochDay();
|
|
||||||
long from = LocalDate.ofEpochDay(nowEpoch - 18 * 365).toEpochDay();
|
|
||||||
long to = LocalDate.ofEpochDay(nowEpoch - 14 * 365).toEpochDay();
|
|
||||||
|
|
||||||
LocalDate[] result = new LocalDate[count];
|
|
||||||
for( int i = 0; i < count; i++ ) {
|
|
||||||
result[i] = LocalDate.ofEpochDay((int) Constants.interpolate(from, to, random()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
|
||||||
* {@code LocalDateTime}-Objekte, die einen Zeitpunkt mit Dateum und Uhrzeit
|
|
||||||
* beschreiben,
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Beispieldaten.
|
|
||||||
* @return Ein Array mit den Beispieldaten.
|
|
||||||
*/
|
|
||||||
public static LocalDateTime[] fakeDatetimes( int count ) {
|
|
||||||
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
|
||||||
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
|
||||||
long to = LocalDateTime.ofEpochSecond(nowEpoch - 14 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
|
||||||
|
|
||||||
LocalDateTime[] result = new LocalDateTime[count];
|
|
||||||
for( int i = 0; i < count; i++ ) {
|
|
||||||
result[i] = LocalDateTime.ofEpochSecond((int) Constants.interpolate(from, to, random()), 0, ZoneOffset.UTC);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt einen Blindtext mit der angegebenen Anzahl Worten, aufgeteilt in
|
|
||||||
* die angegebene Anzahl Absätze.
|
|
||||||
* <p>
|
|
||||||
* Abssätze werden duch einen doppelten Zeilenumbruch {@code \n\n}
|
|
||||||
* getrennt.
|
|
||||||
*
|
|
||||||
* @param words Anzahl Wörter im Text insgesamt.
|
|
||||||
* @param paragraphs Anzahl Absätze.
|
|
||||||
* @return Ein zufälliger Blindtext.
|
|
||||||
*/
|
|
||||||
public static String fakeText( int words, int paragraphs ) {
|
|
||||||
String basetext = "";
|
|
||||||
try(
|
|
||||||
InputStream in = Faker.class.getResourceAsStream("mock-text.txt");
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in))
|
|
||||||
) {
|
|
||||||
basetext = reader.readLine();
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.error(ex, "Error generating fake blindtext: " + ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
String result = basetext.repeat(words / 283);
|
|
||||||
|
|
||||||
int w = (words % 283);
|
|
||||||
if( w > 0 ) {
|
|
||||||
Matcher m = Pattern.compile("([a-zA-Z_0-9\\u00C0-\\u017F]+\\W+){" + w + "}").matcher(basetext);
|
|
||||||
if( m.find() ) {
|
|
||||||
result += m.group().stripTrailing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( paragraphs > 1 ) {
|
|
||||||
int half = words / paragraphs;
|
|
||||||
final int maxLength = result.length();
|
|
||||||
Matcher m = Pattern.compile("([a-zA-Z_0-9\\u00C0-\\u017F]+\\W+){" + (half - 10) + "," + (half + 10) + "}([a-zA-Z_0-9\\u00C0-\\u017F]+)\\.").matcher(result);
|
|
||||||
if( m.find() ) {
|
|
||||||
result = m.replaceAll(( mr ) -> mr.end() == maxLength ? mr.group().trim() : mr.group().trim() + "\n\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Array mit der angegebenen Anzahl zufällig erzeugter Integer
|
|
||||||
* im angegebenen Bereich.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der Zahlen im Array.
|
|
||||||
* @param min Untere Grenze der Zufallszahlen.
|
|
||||||
* @param max Obere Grenze der Zufallszahlen.
|
|
||||||
* @return Ein Array mit Zufallszahlen.
|
|
||||||
* @see Constants#random(int, int)
|
|
||||||
*/
|
|
||||||
public static int[] fakeIntArray( int count, int min, int max ) {
|
|
||||||
int[] arr = new int[count];
|
|
||||||
for( int i = 0; i < count; i++ ) {
|
|
||||||
arr[i] = random(min, max);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt eine Liste mit der angegebenen Anzahl zufällig erzeugter Integer
|
|
||||||
* im angegebenen Bereich.
|
|
||||||
* <p>
|
|
||||||
* Ist {@code list} ein Listenobjekt, werden dei Zahlen an diese angehängt.
|
|
||||||
* Wird {@code null} übergeben, wird eine neue {@link ArrayList} erzeugt.
|
|
||||||
*
|
|
||||||
* @param count Anzahl der erzeugten Zahlen.
|
|
||||||
* @param min Untere Grenze der Zufallszahlen.
|
|
||||||
* @param max Obere Grenze der Zufallszahlen.
|
|
||||||
* @param list Eine Liste, die befüllt werden soll, oder {@code null}.
|
|
||||||
* @return Ein Array mit Zufallszahlen.
|
|
||||||
* @see Constants#random(int, int)
|
|
||||||
*/
|
|
||||||
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
|
|
||||||
List<Integer> result = (list == null) ? new ArrayList<>(count) : list;
|
|
||||||
fakeIntegers(count, min, max, result::add);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt die angegebene Anzahl Zufallszahlen im angegebenen Bereich und
|
|
||||||
* übergibt sie an den angegebenen {@code Consumer}.
|
|
||||||
*
|
|
||||||
* Ein typischer Aufruf, um eine {@code #LinkedList} mit 100 Zufallszahlen
|
|
||||||
* zu erzeugen könnte so aussehen:
|
|
||||||
* <pre><code>
|
|
||||||
* List<Integer> l = new LinkedList<>();
|
|
||||||
* Faker.fakeIntegers(100, 0, 100, l::add);
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @param count Anzahl der erzeugten Zahlen.
|
|
||||||
* @param min Untere Grenze der Zufallszahlen.
|
|
||||||
* @param max Obere Grenze der Zufallszahlen.
|
|
||||||
* @param con {@code Consumer} für die Zahlen.
|
|
||||||
*/
|
|
||||||
public static void fakeIntegers( int count, int min, int max, Consumer<Integer> con ) {
|
|
||||||
for( int i = 0; i < count; i++ ) {
|
|
||||||
con.accept(random(min, max));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <L> L fakeIntegers( int count, int min, int max, Supplier<L> sup, BiConsumer<L, Integer> con ) {
|
|
||||||
L result = sup.get();
|
|
||||||
|
|
||||||
for( int i = 0; i < count; i++ ) {
|
|
||||||
con.accept(result, random(min, max));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings( "unchecked" )
|
|
||||||
private static <T> T[] randomSample( String filename, int count, Function<String, T> transformer, Class<T> type ) {
|
|
||||||
T[] result = (T[]) Array.newInstance(type, count);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
double k = count; // cast to double
|
|
||||||
|
|
||||||
double W = exp(log(random()) / k);
|
|
||||||
|
|
||||||
try(
|
|
||||||
InputStream in = Faker.class.getResourceAsStream("mock-" + filename + ".csv");
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in))
|
|
||||||
) {
|
|
||||||
String line;
|
|
||||||
while( (line = reader.readLine()) != null ) {
|
|
||||||
if( i < count ) {
|
|
||||||
result[i] = transformer.apply(line);
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
int j = (int) (log(random()) / log(1 - W)) + 1;
|
|
||||||
while( j > 0 ) {
|
|
||||||
line = reader.readLine();
|
|
||||||
j -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( line != null ) {
|
|
||||||
result[random(0, count - 1)] = transformer.apply(line);
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
W *= exp(log(random()) / k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill remaining array
|
|
||||||
while( i < count ) {
|
|
||||||
result[i] = result[random(0, i - 1)];
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.error(ex, "Error loading mock data file: " + ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
|
||||||
* <p>
|
|
||||||
* Das Bild ist ein aus dem Internet geladenes, zufälliges Motiv, dass unter
|
|
||||||
* einer freien Lizenz (Creative Commons) steht.
|
|
||||||
*
|
|
||||||
* @param width Breite des Bildes.
|
|
||||||
* @param height Höhe des Bildes.
|
|
||||||
* @return Ein zufälliges Bild in der angegebenen Größe.
|
|
||||||
*/
|
|
||||||
public static BufferedImage fakeImage( int width, int height ) {
|
|
||||||
return fakeImage(width, height, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erzeugt ein Platzhalter-Bild in der angegebenen Größe.
|
|
||||||
* <p>
|
|
||||||
* Wenn {@code fromWeb} auf {@code true} gesetzt ist, wird ein zufälliges
|
|
||||||
* Motiv, das unter einer freien Lizenz (Creative Commons) steht, geladen.
|
|
||||||
* Bei {@code false} wird das Bild lokal generiert.
|
|
||||||
*
|
|
||||||
* @param width Breite des Bildes.
|
|
||||||
* @param height Höhe des Bildes.
|
|
||||||
* @param fromWeb Bei {@code true} wird das Bild aus dem Internet geladen,
|
|
||||||
* bei {@code false} wird das Bild lokal erzeugt.
|
|
||||||
* @return Ein zufälliges Bild in der angegebenen Größe.
|
|
||||||
*/
|
|
||||||
public static BufferedImage fakeImage( int width, int height, boolean fromWeb ) {
|
|
||||||
if( !fromWeb ) {
|
|
||||||
BufferedImage img = ImageLoader.createImage(width, height);
|
|
||||||
Graphics2D graphics = (Graphics2D) img.getGraphics().create();
|
|
||||||
|
|
||||||
String text = width + " x " + height;
|
|
||||||
|
|
||||||
Color clr = Constants.randomNiceColor();
|
|
||||||
|
|
||||||
graphics.setBackground(clr.getJavaColor());
|
|
||||||
graphics.clearRect(0, 0, width, height);
|
|
||||||
|
|
||||||
graphics.setColor(clr.textcolor().getJavaColor());
|
|
||||||
graphics.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, (int) ((width + height) * 0.05)));
|
|
||||||
FontMetrics fontMerics = graphics.getFontMetrics();
|
|
||||||
LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics);
|
|
||||||
graphics.drawString(text,
|
|
||||||
(width - fontMerics.stringWidth(text)) / 2,
|
|
||||||
(int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2)
|
|
||||||
);
|
|
||||||
graphics.dispose();
|
|
||||||
|
|
||||||
return img;
|
|
||||||
} else {
|
|
||||||
return ImageLoader.loadImage(String.format(FAKE_IMG_URL, width, height), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Faker() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(Faker.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,367 +0,0 @@
|
|||||||
package schule.ngb.zm.util;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generator für Perlin-Noise.
|
|
||||||
* <p>
|
|
||||||
* Die Implementierung basiert auf dem von Ken Perlin entwickelten Algorithmus
|
|
||||||
* und wurde anhand der <a
|
|
||||||
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
|
||||||
* FLAFLA2</a> implementiert.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public class Noise {
|
|
||||||
|
|
||||||
private static final int N = 256;
|
|
||||||
|
|
||||||
private static final int M = N - 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interne Permutationstabelle für diesen Generator.
|
|
||||||
*/
|
|
||||||
private int[] p;
|
|
||||||
|
|
||||||
private double octaves = 1, persistence = .5;
|
|
||||||
|
|
||||||
private double frequency = 1;
|
|
||||||
|
|
||||||
private double amplitude = 1;
|
|
||||||
|
|
||||||
private int repeat = -1;
|
|
||||||
|
|
||||||
private double rangeMin = 0.0, rangeMax = 1.0;
|
|
||||||
|
|
||||||
public Noise() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Noise( long seed ) {
|
|
||||||
init(new Random(seed));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
|
||||||
*
|
|
||||||
* @param rand Ein Zufallsgenerator-Objekt.
|
|
||||||
*/
|
|
||||||
public Noise( Random rand ) {
|
|
||||||
init(rand);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getOctaves() {
|
|
||||||
return octaves;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOctaves( double pOctaves ) {
|
|
||||||
this.octaves = pOctaves;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getPersistence() {
|
|
||||||
return persistence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPersistence( double pPersistence ) {
|
|
||||||
this.persistence = pPersistence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getFrequency() {
|
|
||||||
return frequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFrequency( double pFrequency ) {
|
|
||||||
this.frequency = pFrequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getAmplitude() {
|
|
||||||
return amplitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAmplitude( double pAmplitude ) {
|
|
||||||
this.amplitude = pAmplitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRange( double pRangeMin, double pRangeMax ) {
|
|
||||||
this.rangeMin = pRangeMin;
|
|
||||||
this.rangeMax = pRangeMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getRangeMin() {
|
|
||||||
return rangeMin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getRangeMax() {
|
|
||||||
return rangeMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRepeat() {
|
|
||||||
return repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRepeat( int pRepeat ) {
|
|
||||||
this.repeat = pRepeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double noise( double x ) {
|
|
||||||
double total = 0;
|
|
||||||
double freq = this.frequency;
|
|
||||||
double amp = this.amplitude;
|
|
||||||
double maxValue = 0;
|
|
||||||
|
|
||||||
for( int i = 0; i < octaves; i++ ) {
|
|
||||||
total += perlin(x * freq) * amp;
|
|
||||||
|
|
||||||
maxValue += amp;
|
|
||||||
|
|
||||||
amp *= persistence;
|
|
||||||
freq *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lerp(rangeMin, rangeMax, (total / maxValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
public double noise( double x, double y ) {
|
|
||||||
double total = 0;
|
|
||||||
double freq = this.frequency;
|
|
||||||
double amp = this.amplitude;
|
|
||||||
double maxValue = 0;
|
|
||||||
|
|
||||||
for( int i = 0; i < octaves; i++ ) {
|
|
||||||
total += perlin(x * freq, y * freq) * amp;
|
|
||||||
|
|
||||||
maxValue += amp;
|
|
||||||
|
|
||||||
amp *= persistence;
|
|
||||||
freq *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lerp(rangeMin, rangeMax, (total / maxValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
public double noise( double x, double y, double z ) {
|
|
||||||
double total = 0;
|
|
||||||
double freq = this.frequency;
|
|
||||||
double amp = this.amplitude;
|
|
||||||
double maxValue = 0;
|
|
||||||
|
|
||||||
for( int i = 0; i < octaves; i++ ) {
|
|
||||||
total += perlin(x * freq, y * freq, z * freq) * amp;
|
|
||||||
|
|
||||||
maxValue += amp;
|
|
||||||
|
|
||||||
amp *= persistence;
|
|
||||||
freq *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lerp(rangeMin, rangeMax, (total / maxValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
private double perlin( double x ) {
|
|
||||||
// @formatter:off
|
|
||||||
if( repeat > 0 ) {
|
|
||||||
x %= repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
int xi = (int)x & M;
|
|
||||||
|
|
||||||
double xf = x - (int)x;
|
|
||||||
|
|
||||||
double u = fade(xf);
|
|
||||||
|
|
||||||
int a, b;
|
|
||||||
a = p[ xi ];
|
|
||||||
b = p[inc(xi)];
|
|
||||||
|
|
||||||
return (lerp(grad(a,xf), grad(b,xf-1), u) + 1) / 2;
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
private double perlin( double x, double y ) {
|
|
||||||
// @formatter:off
|
|
||||||
if( repeat > 0 ) {
|
|
||||||
x %= repeat;
|
|
||||||
y %= repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
int xi = (int) x & M;
|
|
||||||
int yi = (int) y & M;
|
|
||||||
|
|
||||||
double xf = x - (int) x;
|
|
||||||
double yf = y - (int) y;
|
|
||||||
|
|
||||||
double u = fade(xf);
|
|
||||||
double v = fade(yf);
|
|
||||||
|
|
||||||
int aa, ab, ba, bb;
|
|
||||||
aa = p[p[ xi ] + yi ];
|
|
||||||
ab = p[p[ xi ] + inc(yi)];
|
|
||||||
ba = p[p[inc(xi)] + yi ];
|
|
||||||
bb = p[p[inc(xi)] + inc(yi)];
|
|
||||||
|
|
||||||
double x1, x2;
|
|
||||||
x1 = lerp(
|
|
||||||
grad(aa, xf , yf),
|
|
||||||
grad(ba, xf-1, yf),
|
|
||||||
u);
|
|
||||||
x2 = lerp(
|
|
||||||
grad(ab, xf , yf-1),
|
|
||||||
grad(bb, xf-1, yf-1),
|
|
||||||
u);
|
|
||||||
|
|
||||||
return (lerp(x1, x2, v) + 1) / 2;
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
private double perlin( double x, double y, double z ) {
|
|
||||||
// @formatter:off
|
|
||||||
if( repeat > 0 ) {
|
|
||||||
x %= repeat;
|
|
||||||
y %= repeat;
|
|
||||||
z %= repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
int xi = (int)x & M;
|
|
||||||
int yi = (int)y & M;
|
|
||||||
int zi = (int)z & M;
|
|
||||||
double xf = x - (int)x;
|
|
||||||
double yf = y - (int)y;
|
|
||||||
double zf = z - (int)z;
|
|
||||||
|
|
||||||
double u = fade(xf);
|
|
||||||
double v = fade(yf);
|
|
||||||
double w = fade(zf);
|
|
||||||
|
|
||||||
int aaa, aba, aab, abb, baa, bba, bab, bbb;
|
|
||||||
aaa = p[p[p[ xi ] + yi ] + zi ];
|
|
||||||
aba = p[p[p[ xi ] + inc(yi)] + zi ];
|
|
||||||
aab = p[p[p[ xi ] + yi ] + inc(zi)];
|
|
||||||
abb = p[p[p[ xi ] + inc(yi)] + inc(zi)];
|
|
||||||
baa = p[p[p[inc(xi)] + yi ] + zi ];
|
|
||||||
bba = p[p[p[inc(xi)] + inc(yi)] + zi ];
|
|
||||||
bab = p[p[p[inc(xi)] + yi ] + inc(zi)];
|
|
||||||
bbb = p[p[p[inc(xi)] + inc(yi)] + inc(zi)];
|
|
||||||
|
|
||||||
double x1, x2, y1, y2;
|
|
||||||
x1 = lerp(
|
|
||||||
grad(aaa, xf , yf, zf),
|
|
||||||
grad(baa, xf-1, yf, zf),
|
|
||||||
u);
|
|
||||||
x2 = lerp(
|
|
||||||
grad(aba, xf , yf-1, zf),
|
|
||||||
grad(bba, xf-1, yf-1, zf),
|
|
||||||
u);
|
|
||||||
y1 = lerp(x1, x2, v);
|
|
||||||
|
|
||||||
x1 = lerp(
|
|
||||||
grad(aab, xf , yf, zf-1),
|
|
||||||
grad(bab, xf-1, yf, zf-1),
|
|
||||||
u);
|
|
||||||
x2 = lerp(
|
|
||||||
grad(abb, xf , yf-1, zf-1),
|
|
||||||
grad(bbb, xf-1, yf-1, zf-1),
|
|
||||||
u);
|
|
||||||
y2 = lerp(x1, x2, v);
|
|
||||||
|
|
||||||
return (lerp(y1, y2, w) + 1) / 2;
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init( Random rand ) {
|
|
||||||
p = new int[N * 2];
|
|
||||||
|
|
||||||
if( rand == null ) {
|
|
||||||
System.arraycopy(PERLIN_PERMUTATION, 0, p, 0, N);
|
|
||||||
} else {
|
|
||||||
// Generate random permutation
|
|
||||||
for( int i = 0; i < N; i++ ) {
|
|
||||||
int n = rand.nextInt(N);
|
|
||||||
if( p[n] == 0 )
|
|
||||||
p[n] = i;
|
|
||||||
else
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duplicate permutation array to prevent overflow errors
|
|
||||||
System.arraycopy(p, 0, p, N, N);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double fade( double t ) {
|
|
||||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double lerp( double a, double b, double t ) {
|
|
||||||
return a + t * (b - a);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int inc( int i ) {
|
|
||||||
++i;
|
|
||||||
if( repeat > 0 )
|
|
||||||
i = i % repeat;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double grad( int hash, double x ) {
|
|
||||||
switch( hash & 0x1 ) {
|
|
||||||
// @formatter:off
|
|
||||||
case 0x0: return x;
|
|
||||||
case 0x1: return -x;
|
|
||||||
default: return 0;
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private double grad( int hash, double x, double y ) {
|
|
||||||
switch( hash & 0x3 ) {
|
|
||||||
// @formatter:off
|
|
||||||
case 0x0: return x;
|
|
||||||
case 0x1: return -x;
|
|
||||||
case 0x2: return y;
|
|
||||||
case 0x3: return -y;
|
|
||||||
default: return 0;
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private double grad( int hash, double x, double y, double z ) {
|
|
||||||
switch( hash & 0xF ) {
|
|
||||||
// @formatter:off
|
|
||||||
case 0x0: return x + y;
|
|
||||||
case 0x1: return -x + y;
|
|
||||||
case 0x2: return x - y;
|
|
||||||
case 0x3: return -x - y;
|
|
||||||
case 0x4: return x + z;
|
|
||||||
case 0x5: return -x + z;
|
|
||||||
case 0x6: return x - z;
|
|
||||||
case 0x7: return -x - z;
|
|
||||||
case 0x8: return y + z;
|
|
||||||
case 0x9: return -y + z;
|
|
||||||
case 0xA: return y - z;
|
|
||||||
case 0xB: return -y - z;
|
|
||||||
case 0xC: return y + x;
|
|
||||||
case 0xD: return -y + z;
|
|
||||||
case 0xE: return y - x;
|
|
||||||
case 0xF: return -y - z;
|
|
||||||
default: return 0; // never happens
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int[] PERLIN_PERMUTATION = new int[]{
|
|
||||||
151, 160, 137, 91, 90, 15,
|
|
||||||
131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
|
|
||||||
190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
|
|
||||||
88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166,
|
|
||||||
77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244,
|
|
||||||
102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
|
|
||||||
135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
|
|
||||||
5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
|
|
||||||
223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
|
|
||||||
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228,
|
|
||||||
251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107,
|
|
||||||
49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254,
|
|
||||||
138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
package schule.ngb.zm.util;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsklasse zur Zeitmessung im Nanosekundenbereich.
|
|
||||||
* <p>
|
|
||||||
* Mit einem {@code Timer} kann zum Beispiel die Laufzeit eines Algorithmus
|
|
||||||
* gemessen werden. Wie eine echte Stoppuhr läuft der {@code Timer} weiter, wenn
|
|
||||||
* nach einem {@link #stop()} wieder {@link #start()} aufgerufen wird. Soll die
|
|
||||||
* Zeitmessung wieder bei null beginnen, muss vorher {@link #reset()} genutzt
|
|
||||||
* werden.
|
|
||||||
* <p>
|
|
||||||
* Die gemessene Zeit kann in {@link #getMillis() Millisekunden} oder
|
|
||||||
* {@link #getSeconds() Sekunden} abgerufen werden. Wird eine noch größere
|
|
||||||
* Genauigkeit benötigt, können mit {@link #getTime(TimeUnit)} beliebige
|
|
||||||
* Zeiteinheiten (zum Beispiel {@link TimeUnit#NANOSECONDS Nanosekunden})
|
|
||||||
* abgerufen werden.
|
|
||||||
* <p>
|
|
||||||
* Die Zeit kann auch bei laufender Uhr abgefragt werden. In dem Fall wird die
|
|
||||||
* bis zu diesem Zeitpunkt gemessene Zeit zurückgegeben.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public final class Timer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die Basiseinheit für die Zeitmessung.
|
|
||||||
*/
|
|
||||||
private final TimeUnit baseUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Zeitmessung gerade läuft.
|
|
||||||
*/
|
|
||||||
private boolean running = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Startzeit der Zeitmessung. -1, wenn noch keine Messung gestartet wurde.
|
|
||||||
*/
|
|
||||||
private long start = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeit, die bisher bei allen Zeitmessungen, die gestoppt wurden, insgesamt
|
|
||||||
* vergangen ist.
|
|
||||||
*/
|
|
||||||
private long elapsed = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen neuen {@code Timer} mit Millisekunden als Basiseinheit.
|
|
||||||
*/
|
|
||||||
public Timer() {
|
|
||||||
this(TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt einen {@code Timer}, der die angegebene Einheit als Basiseinheit
|
|
||||||
* für {@link #getTime()} benutzt.
|
|
||||||
* <p>
|
|
||||||
* Um eine Zeitmessung in Nanosekunden durchzuführen, kann der {@code Timer}
|
|
||||||
* beispielsweise so instanziiert werden:
|
|
||||||
* <pre><code>
|
|
||||||
* Timer clock = new Timer(TimeUnit.NANOSECONDS);
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @param baseUnit Die Basiseinheit für die Zeitmessung.
|
|
||||||
*/
|
|
||||||
public Timer( TimeUnit baseUnit ) {
|
|
||||||
this.baseUnit = baseUnit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ob die Zeitmessung gerade läuft.
|
|
||||||
*
|
|
||||||
* @return {@code true}, wenn die Zeitmessung mit {@link #start()} gestartet
|
|
||||||
* wurde.
|
|
||||||
*/
|
|
||||||
public boolean isRunning() {
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Startet die Zeitmessung.
|
|
||||||
* <p>
|
|
||||||
* Wenn zuvor schon eine Zeitmessung gestartet wurde, wird die neue Messung
|
|
||||||
* zur Summe aller Messungen hinzuaddiert. Soll die Messung bei null
|
|
||||||
* starten, muss vorher {@link #reset()} verwendet werden:
|
|
||||||
*
|
|
||||||
* <pre><code>
|
|
||||||
* // Timer auf null stellen und sofort starten
|
|
||||||
* timer.reset().start();
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @return Dieser {@code Timer} selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Timer start() {
|
|
||||||
start = System.nanoTime();
|
|
||||||
running = true;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stoppt den {@code Timer}, wenn er derzeit läuft. Die gemessene Dauer wird
|
|
||||||
* zur Summe aller gemessenen Zeiten hinzuaddiert.
|
|
||||||
*
|
|
||||||
* @return Dieser {@code Timer} selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Timer stop() {
|
|
||||||
if( running ) {
|
|
||||||
running = false;
|
|
||||||
elapsed += System.nanoTime() - start;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den {@code Timer} auf den Startzustand und löscht alle bisher
|
|
||||||
* gemessenen Zeiten. Falls die Zeitmessung gerade läuft, stoppt sie nicht,
|
|
||||||
* sondern startet vom Zeitpunkt des Aufrufs neu.
|
|
||||||
*
|
|
||||||
* @return Dieser {@code Timer} selbst (method chaining).
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
|
||||||
public Timer reset() {
|
|
||||||
elapsed = 0;
|
|
||||||
start = -1;
|
|
||||||
|
|
||||||
if( running ) {
|
|
||||||
start = System.nanoTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Zeit in der Basiseinheit zurück.
|
|
||||||
*
|
|
||||||
* @return Die bisher insgesamt gemessene Zeit.
|
|
||||||
*/
|
|
||||||
public long getTime() {
|
|
||||||
return getTime(baseUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die Zeit in der angegebenen Einheit zurück.
|
|
||||||
* <p>
|
|
||||||
* Größere Zeiteinheiten werden gerundet und verlieren daher an Genauigkeit.
|
|
||||||
* Eine Zeitmessung von 999 Millisekunden wird als 0 Sekunden
|
|
||||||
* zurückgegeben.
|
|
||||||
* <p>
|
|
||||||
* Um genauere Ergebnisse zu erhalten, kann mit {@link #getMillis()} und
|
|
||||||
* {@link #getSeconds()} die gemessene Zeit in Minuten beziehungsweise
|
|
||||||
* Sekunden als Kommazahl abgefragt werden.
|
|
||||||
*
|
|
||||||
* @param unit Zeiteinheit
|
|
||||||
* @return Die bisher insgesamt gemessene Zeit in der gewählten Zeiteinheit.
|
|
||||||
*/
|
|
||||||
public long getTime( TimeUnit unit ) {
|
|
||||||
if( running ) {
|
|
||||||
return unit.convert(System.nanoTime() - start + elapsed, TimeUnit.NANOSECONDS);
|
|
||||||
} else {
|
|
||||||
return unit.convert(elapsed, TimeUnit.NANOSECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die gemessene Zeit in Millisekunden (gerundet) zurück.
|
|
||||||
*
|
|
||||||
* @return Die gemessene Zeit in ms.
|
|
||||||
*/
|
|
||||||
public int getMillis() {
|
|
||||||
if( running ) {
|
|
||||||
return (int) ((System.nanoTime() - start + elapsed) / 1000000);
|
|
||||||
} else {
|
|
||||||
return (int) (elapsed / 1000000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt die gemessene Zeit in Sekunden zurück.
|
|
||||||
*
|
|
||||||
* @return Die gemessene Zeit in s.
|
|
||||||
*/
|
|
||||||
public double getSeconds() {
|
|
||||||
if( running ) {
|
|
||||||
return (System.nanoTime() - start + elapsed) / 1000000000.0;
|
|
||||||
} else {
|
|
||||||
return elapsed / 1000000000.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package schule.ngb.zm.util.events;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementierung einer EventListener-API basierend auf dem Artikel
|
|
||||||
* <a href="https://org.coloradomesa.edu/~wmacevoy/listen/paper.html">Skilled
|
|
||||||
* Listening in Java</a> von Dr. Warren D. MacEvoy.
|
|
||||||
* <p>
|
|
||||||
* Der {@code EventDispatcher} verwaltet eine Liste von angemeldeten Listenern
|
|
||||||
* in einem {@link CopyOnWriteArraySet}, dass besonders für Situationen geeignet
|
|
||||||
* ist, in denen in der Regel wenige Objekte verwaltet werden müssen, auf die
|
|
||||||
* schnell zugegriffen werden soll und die sich selten ändern. Das trifft in den
|
|
||||||
* meisten Fällen auf die Listener eines Objekts zu.
|
|
||||||
* <p>
|
|
||||||
* Um einen {@code EventDispatcher} zu nutzen, muss eine Schnittstelle als
|
|
||||||
* Unterinterface von {@link Listener} erstellt werden, die Methoden für die
|
|
||||||
* einzelnen Events definiert. In der Regel wird eine Methode pro Event
|
|
||||||
* angelegt, aber dies ist nicht unbedingt notwendig.
|
|
||||||
* <p>
|
|
||||||
* Ein Objekt, dass Events für diese Art von Listener erzeugt, erstellt einen
|
|
||||||
* {@code EventDispatcher} und
|
|
||||||
* {@link #registerEventType(String, BiConsumer) registriert} die notwendigen
|
|
||||||
* Events.
|
|
||||||
*
|
|
||||||
* <pre><code>
|
|
||||||
* EventDispatcher<MyEvent, MyEventListener> dispatcher = new EventDispatcher<>();
|
|
||||||
* dispatcher.registerEventType("start", (evt, listener) -> listener.started(evt));
|
|
||||||
* dispatcher.registerEventType("stop", (evt, listener) -> listener.stopped(evt));
|
|
||||||
* </code></pre>
|
|
||||||
* <p>
|
|
||||||
* Hier werden zwei Events registriert "start" und "stop". Die Bezeichnung der
|
|
||||||
* Events wird nut intern verwendet. Jedes Event registriert eine Funktion, die
|
|
||||||
* den Aufruf der Listener-Schnittstelle umsetzt. In der Regel ist dies ein
|
|
||||||
* Lambda-Ausdruck von zwei Parametern: dem Event-Objekt vom Typ {@code E} und
|
|
||||||
* der Listener, der aufgerufen werden soll, vom Typ {@code L}.
|
|
||||||
* <p>
|
|
||||||
* Nun können {@code MyEventListener} angemeldet und Events ausgelöst werden.
|
|
||||||
*
|
|
||||||
* <pre><code>
|
|
||||||
* public void addEventListener( MyEventListener listener ) {
|
|
||||||
* dispatcher.addListener(listener);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public void removeEventListener( MyEventListener listener ) {
|
|
||||||
* dispatcher.removeListener(listener);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public void methodThatCreatesEvents() {
|
|
||||||
* dispatcher.dispatchEvent("start", new MyEvent());
|
|
||||||
* // Do something
|
|
||||||
* dispatcher.dispatchEvent("stop", new MyEvent());
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
* <p>
|
|
||||||
* Siehe {@link schule.ngb.zm.media.AudioListener} und
|
|
||||||
* {@link schule.ngb.zm.media.Music} für ein Beispiel der Verwendung.
|
|
||||||
*
|
|
||||||
* @param <E> Typ der Event-Objekte.
|
|
||||||
* @param <L> Typ der verwendeten Listener-Schnittstelle.
|
|
||||||
*/
|
|
||||||
public class EventDispatcher<E, L extends Listener<E>> {
|
|
||||||
|
|
||||||
private final CopyOnWriteArraySet<L> listeners;
|
|
||||||
|
|
||||||
private final ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
|
||||||
|
|
||||||
public EventDispatcher() {
|
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
|
||||||
eventRegistry = new ConcurrentHashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
|
||||||
Validator.requireNotNull(eventKey, "eventKey");
|
|
||||||
Validator.requireNotNull(dispatcher, "dispatcher");
|
|
||||||
|
|
||||||
if( !eventRegistered(eventKey) ) {
|
|
||||||
eventRegistry.put(eventKey, dispatcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addListener( L listener ) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeListener( L listener ) {
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public boolean hasListeners() {
|
|
||||||
return !listeners.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean eventRegistered( String eventKey ) {
|
|
||||||
return eventRegistry.containsKey(eventKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispatchEvent( String eventKey, final E event ) {
|
|
||||||
Validator.requireNotNull(eventKey, "eventKey");
|
|
||||||
Validator.requireNotNull(event, "event");
|
|
||||||
|
|
||||||
if( eventRegistered(eventKey) ) {
|
|
||||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
|
||||||
listeners.forEach(( listener ) -> dispatcher.accept(event, listener));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package schule.ngb.zm.util.events;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generische Basisschnittstelle für die Implementierung von EventListenern.
|
|
||||||
*
|
|
||||||
* @param <E> Typ der Event-Objekte, die von diesem Listener empfangen werden.
|
|
||||||
* @see EventDispatcher
|
|
||||||
*/
|
|
||||||
public interface Listener<E> {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
/**
|
|
||||||
* Dieses Paket enthält Hilfsklassen, die das Listener-Entwurfsmuster
|
|
||||||
* umsetzen.
|
|
||||||
*/
|
|
||||||
package schule.ngb.zm.util.events;
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
package schule.ngb.zm.util.io;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsklasse, um Textdateien in verschiedenen Formaten einzulesen.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( "unused" )
|
|
||||||
public final class FileLoader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Charset: ASCII
|
|
||||||
*/
|
|
||||||
public static final Charset ASCII = StandardCharsets.US_ASCII;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Charset: UTF-8
|
|
||||||
*/
|
|
||||||
public static final Charset UTF8 = StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Charset: UTF-16
|
|
||||||
*/
|
|
||||||
public static final Charset UTF16 = StandardCharsets.UTF_16;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Charset: ISO-8859-1
|
|
||||||
*/
|
|
||||||
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt die angegebene Textdatei zeilenweise in eine Liste.
|
|
||||||
* <p>
|
|
||||||
* Als {@link Charset} wird {@link #UTF8} verwendet.
|
|
||||||
*
|
|
||||||
* @param source Die Quelle der Textdatei.
|
|
||||||
* @return Eine Liste mit den Zeilen der Textdatei.
|
|
||||||
* @see #loadLines(String, Charset)
|
|
||||||
*/
|
|
||||||
public static List<String> loadLines( String source ) {
|
|
||||||
return loadLines(source, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt die angegebene Textdatei mit dem angegebenen Charset zeilenweise in
|
|
||||||
* eine Liste.
|
|
||||||
* <p>
|
|
||||||
* Am Ende jeder Zeile wird das Symbol für einen Zeilenumbruch ({@code \n})
|
|
||||||
* entfernt.
|
|
||||||
*
|
|
||||||
* @param source Die Quelle der Textdatei.
|
|
||||||
* @param charset Das zu verwendene {@code Charset}.
|
|
||||||
* @return Eine Liste mit den Zeilen der Textdatei.
|
|
||||||
*/
|
|
||||||
public static List<String> loadLines( String source, Charset charset ) {
|
|
||||||
Validator.requireNotNull(source, "source");
|
|
||||||
Validator.requireNotNull(charset, "charset");
|
|
||||||
|
|
||||||
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
|
||||||
List<String> result = new ArrayList<>();
|
|
||||||
|
|
||||||
String line;
|
|
||||||
while( (line = reader.readLine()) != null ) {
|
|
||||||
result.add(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch( MalformedURLException muex ) {
|
|
||||||
LOG.warn("Could not find resource for <%s>", source);
|
|
||||||
return Collections.emptyList();
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.warn(ex, "Error while loading lines from source <%s>", source);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt den Inhalt der angegebenen Textdatei vollständig in einen String.
|
|
||||||
* <p>
|
|
||||||
* Als {@link Charset} wird {@link #UTF8} verwendet.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Textdatei (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkressource)
|
|
||||||
* @return Der Inhalt der Textdatei.
|
|
||||||
*/
|
|
||||||
public static String loadText( String source ) {
|
|
||||||
return loadText(source, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt den Inhalt der angegebenen Textdatei mit dem angegebenen
|
|
||||||
* {@code Charset} vollständig in einen String.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Textdatei (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkressource)
|
|
||||||
* @param charset Der {@code Charset} der Textdatei.
|
|
||||||
* @return Der Inhalt der Textdatei.
|
|
||||||
*/
|
|
||||||
public static String loadText( String source, Charset charset ) {
|
|
||||||
Validator.requireNotNull(source, "source");
|
|
||||||
Validator.requireNotNull(charset, "charset");
|
|
||||||
|
|
||||||
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
|
|
||||||
String line;
|
|
||||||
while( (line = reader.readLine()) != null ) {
|
|
||||||
result.append(line).append('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
} catch( MalformedURLException muex ) {
|
|
||||||
LOG.warn("Could not find resource for <%s>", source);
|
|
||||||
return "";
|
|
||||||
} catch( IOException ex ) {
|
|
||||||
LOG.warn(ex, "Error while loading string from source <%s>", source);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt die Daten aus einer CSV-Datei in ein zweidimensionales
|
|
||||||
* String-Array.
|
|
||||||
* <p>
|
|
||||||
* Der Aufruf entspricht
|
|
||||||
* <pre><code>
|
|
||||||
* FileLoader.loadCsv(source, ',', skipFirst, UTF8);
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die CSV-Datei (Absoluter Dateipfad,
|
|
||||||
* * Dateipfad im Classpath oder Netzwerkressource)
|
|
||||||
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
|
||||||
* @return Ein Array mit den Daten als {@code String}s.
|
|
||||||
* @see #loadCsv(String, char, boolean, Charset)
|
|
||||||
*/
|
|
||||||
public static String[][] loadCsv( String source, boolean skipFirst ) {
|
|
||||||
return loadCsv(source, ',', skipFirst, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt die Daten aus einer CSV-Datei in ein zweidimensionales
|
|
||||||
* String-Array.
|
|
||||||
* <p>
|
|
||||||
* Die Methode ist nicht in der Lage, komplexe CSV-Dateien zu verarbeiten.
|
|
||||||
* Insbesondere werden Inhalte, die das Trennzeichen {@code separator}
|
|
||||||
* enthalten, nicht korrekt erkannt. Das Trennzeichen wird unabhängig
|
|
||||||
* vom Kontext immer als Zelltrenner erkannt. (Im Normalfall kann das
|
|
||||||
* Trennzeichen durch die Verwendung doppelter Anführungszeichen in der Art
|
|
||||||
* {@code Inhalt,"Inhalt, der Komma enthält",Inhalt} maskiert werden.)
|
|
||||||
* <p>
|
|
||||||
* Es wird auch keine erweiterte Inhaltserkennung ausgeführt, sondern alle
|
|
||||||
* Inhalte als {@code String} gelesen. Die weitere Verarbeitung mit den
|
|
||||||
* passenden Parser-Methoden (beispielsweise
|
|
||||||
* {@link Double#parseDouble(String)}) obligt dem Nutzer.
|
|
||||||
*
|
|
||||||
* @param source Die Quelle der CSV-Daten.
|
|
||||||
* @param separator Das verwendete Trennzeichen.
|
|
||||||
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
|
||||||
* @param charset Die zu verwendende Zeichenkodierung.
|
|
||||||
* @return Ein Array mit den Daten als {@code String}s.
|
|
||||||
*/
|
|
||||||
public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) {
|
|
||||||
int n = skipFirst ? 1 : 0;
|
|
||||||
List<String> lines = loadLines(source, charset);
|
|
||||||
return lines.stream().skip(n).map(
|
|
||||||
//( line ) -> line.split(Character.toString(separator))
|
|
||||||
( line ) -> line.split("\\s*" + separator + "\\s*")
|
|
||||||
).toArray(String[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double[][] loadValues( String source, String separator, boolean skipFirst ) {
|
|
||||||
return loadValues(source, separator, skipFirst, UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt Double-Werte aus einer Text-Datei in ein zweidimensionales Array.
|
|
||||||
* <p>
|
|
||||||
* Die Zeilen der Eingabedatei werden anhand der Zeichenkette {@code separator}
|
|
||||||
* in einzelne Teile aufgetrennt. {@code separator} wird als regulärer Ausdruck
|
|
||||||
* interpretiert (siehe {@link String#split(String)}).
|
|
||||||
* <p>
|
|
||||||
* Jeder Teilstring wird mit {@link Double#parseDouble(String)} in
|
|
||||||
* {@code double} umgeformt. Es liegt in der Verantwortung des Nutzers,
|
|
||||||
* sicherzustellen, dass die Eingabedatei nur Zahlen enthält, die korrekt
|
|
||||||
* in {@code double} umgewandelt werden können. Zellen, für die die
|
|
||||||
* Umwandlung fehlschlägt, werden mit 0.0 befüllt.
|
|
||||||
* <p>
|
|
||||||
* Die Methode unterliegt denselben Einschränkungen wie
|
|
||||||
* {@link #loadCsv(String, char, boolean, Charset)}.
|
|
||||||
*
|
|
||||||
* @param source Die Quelle der CSV-Daten.
|
|
||||||
* @param separator Ein Trennzeichen oder ein regulärer Ausdruck.
|
|
||||||
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
|
||||||
* @param charset Die zu verwendende Zeichenkodierung.
|
|
||||||
* @return Ein Array mit den Daten als {@code String}s.
|
|
||||||
*/
|
|
||||||
public static double[][] loadValues( String source, String separator, boolean skipFirst, Charset charset ) {
|
|
||||||
int n = skipFirst ? 1 : 0;
|
|
||||||
List<String> lines = loadLines(source, charset);
|
|
||||||
return lines.stream().skip(n).map(
|
|
||||||
( line ) -> Arrays
|
|
||||||
//.stream(line.split(Character.toString(separator)))
|
|
||||||
.stream(line.split(separator))
|
|
||||||
.mapToDouble(
|
|
||||||
( value ) -> {
|
|
||||||
try {
|
|
||||||
return Double.parseDouble(value);
|
|
||||||
} catch( NumberFormatException nfe ) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).toArray()
|
|
||||||
).toArray(double[][]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FileLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(FileLoader.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package schule.ngb.zm.util.io;
|
|
||||||
|
|
||||||
import schule.ngb.zm.util.Cache;
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.FontFormatException;
|
|
||||||
import java.awt.Image;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Eine Hilfsklasse mit Klassenmethoden, um Schriftarten zu laden.
|
|
||||||
* <p>
|
|
||||||
* Schriftarten können von verschiedenen Quellen geladen werden. Schriftarten,
|
|
||||||
* die aus Dateien geladen wurden, werden in einem internen Cache gespeichert
|
|
||||||
* und nachfolgende Versuche, dieselbe Schriftart zu laden, werden aus dem Cache
|
|
||||||
* bedient.
|
|
||||||
*/
|
|
||||||
public class FontLoader {
|
|
||||||
|
|
||||||
private static final Cache<String, Font> fontCache = Cache.newSoftCache();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt eine Schrift aus einer Datei.
|
|
||||||
* <p>
|
|
||||||
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
|
||||||
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
|
||||||
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
|
|
||||||
* das Ergebnis {@code null}.
|
|
||||||
* <p>
|
|
||||||
* Die gefundene Schrift wird unter ihrem Dateinamen in den
|
|
||||||
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
|
|
||||||
* werden.
|
|
||||||
* <p>
|
|
||||||
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
|
||||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
|
||||||
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
|
||||||
* verwendet werden.
|
|
||||||
*
|
|
||||||
* @param source
|
|
||||||
* @return Die erste geladene Schrift oder {@code null}.
|
|
||||||
* @see #loadFont(String, String)
|
|
||||||
*/
|
|
||||||
public static Font loadFont( String source ) {
|
|
||||||
String name = source;
|
|
||||||
// Dateipfad entfernen
|
|
||||||
int lastIndex = source.lastIndexOf(File.separatorChar);
|
|
||||||
if( lastIndex > -1 ) {
|
|
||||||
source = source.substring(lastIndex + 1);
|
|
||||||
}
|
|
||||||
// Dateiendung entfernen
|
|
||||||
lastIndex = name.lastIndexOf('.');
|
|
||||||
if( lastIndex > -1 ) {
|
|
||||||
name = name.substring(0, lastIndex);
|
|
||||||
}
|
|
||||||
return loadFont(name, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Font loadFont( String name, String source ) {
|
|
||||||
Validator.requireNotNull(source, "source");
|
|
||||||
Validator.requireNotEmpty(source, "source");
|
|
||||||
|
|
||||||
if( fontCache.containsKey(name) ) {
|
|
||||||
LOG.trace("Retrieved font <%s> from font cache.", name);
|
|
||||||
return fontCache.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for System fonts
|
|
||||||
Font font = Font.decode(source);
|
|
||||||
if( source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
|
||||||
fontCache.put(name, font);
|
|
||||||
fontCache.put(source, font);
|
|
||||||
LOG.debug("Loaded system font for <%s>.", source);
|
|
||||||
return font;
|
|
||||||
} else {
|
|
||||||
font = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load userfonts
|
|
||||||
try( InputStream in = ResourceStreamProvider.getInputStream(source) ) {
|
|
||||||
font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN);
|
|
||||||
|
|
||||||
if( font != null ) {
|
|
||||||
fontCache.put(name, font);
|
|
||||||
fontCache.put(source, font);
|
|
||||||
//ge.registerFont(font);
|
|
||||||
}
|
|
||||||
LOG.debug("Loaded custom font from source <%s>.", source);
|
|
||||||
} catch( MalformedURLException muex ) {
|
|
||||||
LOG.warn("Could not find font resource for <%s>", source);
|
|
||||||
} catch( IOException ioex ) {
|
|
||||||
LOG.warn(ioex, "Error loading custom font file from source <%s>.", source);
|
|
||||||
} catch( FontFormatException ffex ) {
|
|
||||||
LOG.warn(ffex, "Error creating custom font from source <%s>.", source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return font;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt eine Schrift aus einer Datei.
|
|
||||||
* <p>
|
|
||||||
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
|
||||||
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
|
||||||
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
|
|
||||||
* das Ergebnis {@code null}.
|
|
||||||
* <p>
|
|
||||||
* Die gefundene Schrift wird unter ihrem Dateinamen in den
|
|
||||||
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
|
|
||||||
* werden.
|
|
||||||
* <p>
|
|
||||||
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
|
||||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
|
||||||
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
|
||||||
* verwendet werden.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param sources
|
|
||||||
* @return Die erste geladene Schrift oder {@code null}.
|
|
||||||
* @see #loadFont(String, String)
|
|
||||||
*/
|
|
||||||
public static Font loadFonts( String name, String... sources ) {
|
|
||||||
for( String fontSource : sources ) {
|
|
||||||
// TODO: Ignore exceptions in this case and throw own at end?
|
|
||||||
Font font = loadFont(name, fontSource);
|
|
||||||
if( font != null ) {
|
|
||||||
return font;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FontLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(FontLoader.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
package schule.ngb.zm.util.io;
|
|
||||||
|
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
|
||||||
import schule.ngb.zm.util.Log;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hilfsklasse, um {@link InputStream}s für Ressourcen zu erhalten.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class ResourceStreamProvider {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ermittelt zur angegebenen Quelle einen passenden {@link URL} (<em>Unified
|
|
||||||
* Resource Locator</em>). Eine passende Datei-Ressource wird wie folgt
|
|
||||||
* ermittelt:
|
|
||||||
* <ol>
|
|
||||||
* <li>Ist {@code source} eine existierende Datei
|
|
||||||
* ({@code new File(source}.isFile() == true})?</li>
|
|
||||||
* <li>Ist {@code source} ein relativer Pfad im Projekt ({@code getResource(source) != null})?</li>
|
|
||||||
* <li>Ist {@code source} im Classpath enthalten ({@code getClassLoader().getResource(source) != null})?</li>
|
|
||||||
* <li>Ansonsten erstelle ein {@link URL}-Objekt.</li>
|
|
||||||
* </ol>
|
|
||||||
* <p>
|
|
||||||
* Ein {@code URL} für die erste gefundene Resource wird zurückgegeben.
|
|
||||||
* <p>
|
|
||||||
* Auftretende Exceptions werden als {@link IOException} geworfen.
|
|
||||||
* Bei einer Exception werden die nachfolgenden Quellen nicht mehr abgefragt.
|
|
||||||
* Eine {@link java.net.MalformedURLException} beim Konstruieren des {@code URL}
|
|
||||||
* zu einer Datei verhindert daher, dass noch im Classpath gesucht wird.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkressource)
|
|
||||||
* @return Ein {@code InputStream} für die Ressource
|
|
||||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
|
||||||
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
|
||||||
* ist.
|
|
||||||
* @throws IOException Geworfen beim Erzeugen einer URL zu
|
|
||||||
* einer bestehenden Ressource.
|
|
||||||
*/
|
|
||||||
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
|
||||||
Validator.requireNotNull(source, "source");
|
|
||||||
Validator.requireNotEmpty(source, "source");
|
|
||||||
|
|
||||||
// Ist source ein valider Dateipfad?
|
|
||||||
File file = new File(source);
|
|
||||||
if( file.isFile() ) {
|
|
||||||
return file.toURI().toURL();
|
|
||||||
}
|
|
||||||
// Ist source im Classpath vorhanden?
|
|
||||||
URL url = Zeichenmaschine.class.getClassLoader().getResource(source);
|
|
||||||
if( url != null ) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
// Dann versuchen aus source direkt eine URL zu machen.
|
|
||||||
return new URL(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
|
||||||
* passenden {@link InputStream}. Die konkrete Art des Streams hängt davon
|
|
||||||
* ab, welche Art an Ressource gefunden wird:
|
|
||||||
* <ol>
|
|
||||||
* <li>Ist {@code source} eine existierende Datei
|
|
||||||
* {@code new File(source).isFile() == true}, dann wird ein
|
|
||||||
* {@link FileInputStream} erstellt.</li>
|
|
||||||
* <li>Ist {@code source} ein relativer Pfad im Projekt, wird ein passender
|
|
||||||
* Stream mit {@link Class#getResourceAsStream(String)} geöffnet.</li>
|
|
||||||
* <li>Ist {@code source} im Classpath enthalten, wird ein passender Stream
|
|
||||||
* mit {@link ClassLoader#getResourceAsStream(String)} geöffnet.</li>
|
|
||||||
* <li>Ist {@code source} eine gültige {@link URL}, dann wird ein
|
|
||||||
* {@link URL#openStream() Netwerkstream} geöffnet.</li>
|
|
||||||
* </ol>
|
|
||||||
* <p>
|
|
||||||
* Die Möglichen Ressourcen werden in der gelisteten Reihenfolge durchprobiert
|
|
||||||
* und der erste gefundene Stream zurückgegeben. Auftretende Exceptions
|
|
||||||
* werden als {@link IOException} geworfen. Das bedeutet, falls für {@code source}
|
|
||||||
* keine gültige Ressource gefunden werden kann, wird am Ende die
|
|
||||||
* von {@link URL#openStream()} erzeugte {@code IOException} geworfen.
|
|
||||||
* <p>
|
|
||||||
* Bei einer Exception werden die nachfolgenden Quellen nicht mehr abgefragt.
|
|
||||||
* Eine {@link SecurityException} beim Öffnen des {@code FileInputStream}
|
|
||||||
* (zum Beispiel, weil auf eine existierende Datei keine Leserechte bestehen)
|
|
||||||
* verhindert daher, dass noch im Classpath gesucht wird.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkresource).
|
|
||||||
* @return Ein {@code InputStream} für die Ressource.
|
|
||||||
* @throws NullPointerException Falls {@code source} {@code null} ist.
|
|
||||||
* @throws IllegalArgumentException Falls {@code source} ein leerer String
|
|
||||||
* ist.
|
|
||||||
* @throws IOException Geworfen beim Öffnen des Streams zu
|
|
||||||
* einer bestehenden Ressource oder falls
|
|
||||||
* keine passende Ressource gefunden
|
|
||||||
* wurde.
|
|
||||||
*/
|
|
||||||
public static InputStream getInputStream( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
|
||||||
return getResourceURL(source).openStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
|
||||||
* passenden {@link OutputStream}.
|
|
||||||
* <p>
|
|
||||||
* Es wird davon ausgegangen, dass die Quelle auf eine Datei verweist, auf
|
|
||||||
* die Schreibrechte bestehen. Ist dies nicht der Fall, wird eine
|
|
||||||
* {@link IOException} geworfen.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath).
|
|
||||||
* @return {@code OutputStream} für die Ressource.
|
|
||||||
* @throws IOException Falls die Quelle kein gültiges Ziel beschreibt.
|
|
||||||
*/
|
|
||||||
public static OutputStream getOutputStream( String source ) throws IOException {
|
|
||||||
try {
|
|
||||||
return Files.newOutputStream(Path.of(getResourceURL(source).toURI()));
|
|
||||||
} catch( URISyntaxException ex ) {
|
|
||||||
throw new IOException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
|
||||||
* {@link BufferedReader}.
|
|
||||||
* <p>
|
|
||||||
* Die Suche erfolgt, wie bei {@link #getInputStream(String)} beschrieben.
|
|
||||||
* <p>
|
|
||||||
* Als {@link Charset} wird {@link StandardCharsets#UTF_8 UTF-8} verwendet.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkresource).
|
|
||||||
* @return Ein {@code BufferedReader} für die Quelle.
|
|
||||||
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
|
||||||
* werden kann.
|
|
||||||
* @see #getInputStream(String)
|
|
||||||
*/
|
|
||||||
public static BufferedReader getReader( String source ) throws IOException {
|
|
||||||
return getReader(source, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht eine zur angegebenen Quelle passende Ressource und öffnet einen
|
|
||||||
* {@link BufferedReader} mit dem angegebenen Charset.
|
|
||||||
* <p>
|
|
||||||
* Die Suche erfolgt wie bei {@link #getInputStream(String)} beschrieben.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkresource).
|
|
||||||
* @param charset Der zu verwendende Charset.
|
|
||||||
* @return Ein {@code BufferedReader} für die Quelle.
|
|
||||||
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
|
||||||
* werden kann.
|
|
||||||
* @see #getInputStream(String)
|
|
||||||
*/
|
|
||||||
public static BufferedReader getReader( String source, Charset charset ) throws IOException {
|
|
||||||
return new BufferedReader(new InputStreamReader(getInputStream(source), charset.newDecoder()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht eine zur angegebenen Quelle passende Datei und öffnet einen
|
|
||||||
* {@link BufferedWriter}.
|
|
||||||
* <p>
|
|
||||||
* Die Suche erfolgt wie bei {@link #getOutputStream(String)} beschrieben.
|
|
||||||
* <p>
|
|
||||||
* Als {@link Charset} wird {@link StandardCharsets#UTF_8 UTF-8} verwendet.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkresource).
|
|
||||||
* @return Ein {@code BufferedWriter} für die Quelle.
|
|
||||||
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
|
||||||
* werden kann.
|
|
||||||
* @see #getOutputStream(String)
|
|
||||||
*/
|
|
||||||
public static BufferedWriter getWriter( String source ) throws IOException {
|
|
||||||
return getWriter(source, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sucht eine zur angegebenen Quelle passende Datei und öffnet einen
|
|
||||||
* {@link BufferedWriter}.
|
|
||||||
* <p>
|
|
||||||
* Die Suche erfolgt wie bei {@link #getOutputStream(String)} beschrieben.
|
|
||||||
* <p>
|
|
||||||
* Als {@link Charset} wird {@link StandardCharsets#UTF_8 UTF-8} verwendet.
|
|
||||||
*
|
|
||||||
* @param source Eine Quelle für die Ressource (Absoluter Dateipfad,
|
|
||||||
* Dateipfad im Classpath oder Netzwerkresource).
|
|
||||||
* @return Ein {@code BufferedWriter} für die Quelle.
|
|
||||||
* @param charset Der zu verwendende Charset.
|
|
||||||
* @throws IOException Falls die Quelle nicht exisitert oder nicht geöffnet
|
|
||||||
* werden kann.
|
|
||||||
* @see #getOutputStream(String)
|
|
||||||
*/
|
|
||||||
public static BufferedWriter getWriter( String source, Charset charset ) throws IOException {
|
|
||||||
return new BufferedWriter(new OutputStreamWriter(getOutputStream(source), charset.newEncoder()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Privater C'tor
|
|
||||||
private ResourceStreamProvider() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static final Log LOG = Log.getLogger(ResourceStreamProvider.class);
|
|
||||||
|
|
||||||
}
|
|
||||||