Compare commits
108 Commits
v0.0.22-SN
...
v0.0.34-SN
| Author | SHA1 | Date | |
|---|---|---|---|
| d500c130ed | |||
| b4d390cd9b | |||
| 97ea610f34 | |||
| e2e1f24e3e | |||
| a09b956b48 | |||
| c92a4517b3 | |||
| 1260a38bb7 | |||
| 20772da813 | |||
| 8898d6e8ee | |||
| cb0ee9c842 | |||
| 3cf7871591 | |||
| c7e1eb11ed | |||
| 9fc58b05b6 | |||
| 8de3c41b9b | |||
| 15e47ceaa8 | |||
| 9f28786ab6 | |||
| 18b5c50016 | |||
| 03945e029a | |||
| 90e043e5f8 | |||
| 559459aef6 | |||
| 8d0bd2bc99 | |||
| b76d533739 | |||
| 807a13b725 | |||
| d4c5dbbb53 | |||
| 080db1f431 | |||
| 47827683e8 | |||
| ec30afd441 | |||
| d3bdbdbffb | |||
| 8cc7167d7e | |||
| 9e4271c304 | |||
| 4f13f5177d | |||
| 6321a7d421 | |||
| 135af10729 | |||
| 912f68c58f | |||
| 7f1d9012e9 | |||
| 60ed045986 | |||
| dc16608333 | |||
| 7b6398fe52 | |||
| 8f98ddc56d | |||
| 782ce33540 | |||
| 537527e525 | |||
| 8e93866b5e | |||
| fcb536ff96 | |||
| 70c607f2e8 | |||
| 6126ed3c15 | |||
| b0353c53a0 | |||
| c93a203ab9 | |||
| f1d32685b4 | |||
| 91842b511f | |||
| 52b480b46b | |||
| 4d2ade899d | |||
| dcdca893b7 | |||
| ebf0135486 | |||
| fea1083926 | |||
| 250d9d17d3 | |||
| 2a71243fc6 | |||
| 03d37222bf | |||
| 687d7d35b7 | |||
| e2e6f8c291 | |||
| 916a581768 | |||
| 5bb2f75193 | |||
| f0e4cd6c80 | |||
| a228b21c84 | |||
| 68c88ec9ca | |||
| 0d1dd771dd | |||
| e995bfc4fe | |||
| 97ff03990a | |||
| bd2364a8df | |||
| 617b915874 | |||
| 7772793e8d | |||
| bd8c0e37a7 | |||
| ecbe2b4f6b | |||
| 20fe700756 | |||
| 0100a3f574 | |||
| aceb79c44f | |||
| a4e29ccdba | |||
| 55014c8eec | |||
| 4f958cd57c | |||
| 04506f6e9c | |||
| 5a27e18634 | |||
| 8b23c658e8 | |||
| 1ca13c977a | |||
| 78c93666d0 | |||
| 917eb805c6 | |||
| fddd8d621b | |||
| 4bf0068051 | |||
| 371a962432 | |||
| 99848e47f8 | |||
| f75aaf4b7e | |||
| e5c6fa634a | |||
| ccc83414c7 | |||
| 16477463d4 | |||
| d3997561fc | |||
| b6b4ffe6a5 | |||
| bf261b5e9b | |||
| b79f26f51e | |||
| 538a8215e6 | |||
| cbda5c3077 | |||
| 2caa528a5e | |||
| bb50abb7bd | |||
| 38d5f22fb6 | |||
| d34c60505e | |||
| 4c8e5c8939 | |||
| 9a9a714050 | |||
| f0b064a3d5 | |||
| c922357ab7 | |||
| 17c31a1a03 | |||
| 6551bb75c9 |
8
.gitignore
vendored
@@ -48,3 +48,11 @@ gradle-app.setting
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# Python mkdocs
|
||||
.venv
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
25
CHANGELOG.md
@@ -6,13 +6,32 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## Version 0.0.34
|
||||
|
||||
### Added
|
||||
- System für EventListener erstellt
|
||||
- `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 hinzugefügt
|
||||
- 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
|
||||
|
||||
@@ -24,7 +43,7 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
### Changed
|
||||
- Neue Package-Struktur:
|
||||
- `schule.ngb.zm.media` für Audio-Klassen (und ggf. zukünftig Video).
|
||||
- `schule.ngb.zm.tasks` für alles Rund um Parallelität.
|
||||
- `schule.ngb.zm.util.tasks` für alles Rund um Parallelität.
|
||||
- `Zeichenthread` und `TaskRunner` setzen die Namen der Threads für besseres Debugging.
|
||||
|
||||
### Removed
|
||||
|
||||
24
build.gradle
@@ -3,17 +3,13 @@ plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
/*properties {
|
||||
zmVersion {
|
||||
major = 0;
|
||||
minor = 0;
|
||||
rev = 21;
|
||||
}
|
||||
}*/
|
||||
|
||||
group 'schule.ngb'
|
||||
version '0.0.22-SNAPSHOT'
|
||||
//version '{$zmVersion.major}.{$zmVersion.minor}.{$zmVersion.rev}-SNAPSHOT'
|
||||
version '0.0.34-SNAPSHOT'
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.release = 11
|
||||
@@ -28,8 +24,13 @@ dependencies {
|
||||
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'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
}
|
||||
|
||||
test {
|
||||
@@ -43,4 +44,5 @@ tasks.register('jarMP3SPI', Jar) {
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
with jar
|
||||
}
|
||||
|
||||
|
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 |
634
docs/assets/quickstart/AblaufMoleActive.excalidraw
Normal file
@@ -0,0 +1,634 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
BIN
docs/assets/quickstart/AblaufMoleActive.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
1030
docs/assets/quickstart/AblaufMoleStatic.excalidraw
Normal file
BIN
docs/assets/quickstart/AblaufMoleStatic.png
Normal file
|
After Width: | Height: | Size: 411 KiB |
375
docs/assets/quickstart/CircleMouseCollision.excalidraw
Normal file
@@ -0,0 +1,375 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
BIN
docs/assets/quickstart/CircleMouseCollision.png
Normal file
|
After Width: | Height: | Size: 821 KiB |
375
docs/assets/quickstart/Layers.excalidraw
Normal file
@@ -0,0 +1,375 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
BIN
docs/assets/quickstart/Layers.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
docs/assets/quickstart/shapes_2.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/assets/quickstart/shapes_3.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
docs/assets/quickstart/shapes_4.1.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
docs/assets/quickstart/shapes_4.2.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
docs/assets/quickstart/shapes_4.3.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
docs/assets/quickstart/shapes_5.1.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/assets/quickstart/shapes_5.3.gif
Normal file
|
After Width: | Height: | Size: 408 KiB |
BIN
docs/assets/quickstart/shapes_6.1.gif
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
docs/assets/quickstart/shapes_6.2.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
12
docs/assets/zmstyles.css
Normal file
@@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
||||
43
docs/einfuehrung.md
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
<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 für Schülerinnen und Schüler im Unterricht 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](quickstart.md)
|
||||
* [Installation](installation.md)
|
||||
* {{ javadoc_link() }}
|
||||
|
||||
## Über die 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`.
|
||||
7
docs/home_override/home.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block tabs %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
{% block footer %}{% endblock %}
|
||||
46
docs/installation.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 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/release/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
Normal file
@@ -0,0 +1,128 @@
|
||||
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...)
|
||||
606
docs/quickstart.md
Normal file
@@ -0,0 +1,606 @@
|
||||
# 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.
|
||||
Wor 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:
|
||||
|
||||
```
|
||||
@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 Skeunden 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).
|
||||
19
docs/tutorials/aquarium/aquarium1.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
83
mkdocs.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
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: einfuehrung.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
|
||||
25
requirements.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
194
src/main/java/schule/ngb/zm/BasicDrawable.java
Normal file
@@ -0,0 +1,194 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
// 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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine Farbe in der Zeichenmaschine.
|
||||
* <p>
|
||||
@@ -9,7 +18,8 @@ package schule.ngb.zm;
|
||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
||||
* (deckend).
|
||||
*/
|
||||
public class Color {
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Color implements Paint {
|
||||
|
||||
|
||||
//@formatter:off
|
||||
@@ -42,38 +52,67 @@ public class Color {
|
||||
* Die Farbe Zeichenmaschinen-Rot.
|
||||
*/
|
||||
public static final Color RED = new Color(240, 80, 37);
|
||||
|
||||
/**
|
||||
* Die Farbe Rot.
|
||||
*/
|
||||
public static final Color PURE_RED = new Color(255, 0, 0);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Grün.
|
||||
*/
|
||||
public static final Color GREEN = new Color(98, 199, 119);
|
||||
|
||||
/**
|
||||
* Die Farbe Grün.
|
||||
*/
|
||||
public static final Color PURE_GREEN = new Color(0, 255, 0);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Blau.
|
||||
*/
|
||||
public static final Color BLUE = new Color(49, 197, 244);
|
||||
public static final Color BLUE = new Color(43, 128, 243); // 49, 197, 244
|
||||
|
||||
/**
|
||||
* Die Farbe Blau.
|
||||
*/
|
||||
public static final Color PURE_BLUE = new Color(0, 0, 255);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Gelb.
|
||||
*/
|
||||
public static final Color YELLOW = new Color(248, 239, 34);
|
||||
|
||||
/**
|
||||
* Die Farbe Gelb.
|
||||
*/
|
||||
public static final Color PURE_YELLOW = new Color(255, 255, 0);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Orange.
|
||||
*/
|
||||
public static final Color ORANGE = new Color(248, 158, 80);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Türkis.
|
||||
*/
|
||||
public static final Color CYAN = new Color(java.awt.Color.CYAN);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Magenta.
|
||||
*/
|
||||
public static final Color MAGENTA = new Color(java.awt.Color.MAGENTA);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Rosa.
|
||||
*/
|
||||
public static final Color PINK = new Color(240, 99, 164);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Lila.
|
||||
*/
|
||||
public static final Color PURPLE = new Color(101, 0, 191);
|
||||
|
||||
/**
|
||||
* Die Farbe Zeichenmaschinen-Braun.
|
||||
*/
|
||||
@@ -83,6 +122,7 @@ public class Color {
|
||||
* Die Farbe Helmholtz-Grün.
|
||||
*/
|
||||
public static final Color HGGREEN = new Color(0, 165, 81);
|
||||
|
||||
/**
|
||||
* Die Farbe Helmholtz-Rot.
|
||||
*/
|
||||
@@ -102,7 +142,7 @@ public class Color {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var>.
|
||||
* Erstellt eine graue Farbe entsprechend dem Grauwert {@code gray}.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
*/
|
||||
@@ -111,8 +151,8 @@ public class Color {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var> und
|
||||
* des Transparentwertes <var>alpha</var>.
|
||||
* Erstellt eine graue Farbe entsprechend dem Grauwert {@code gray} und dem
|
||||
* Transparenzwert {@code alpha}.
|
||||
*
|
||||
* @param gray Ein Grauwert zwischen 0 und 255.
|
||||
*/
|
||||
@@ -121,9 +161,9 @@ public class Color {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
||||
* Werte liegen zwischen 0 und 255.
|
||||
* Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und
|
||||
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||
* liegen zwischen 0 und 255.
|
||||
*
|
||||
* @param red Rotwert zwischen 0 und 255.
|
||||
* @param green Grünwert zwischen 0 und 255.
|
||||
@@ -134,11 +174,11 @@ public class Color {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
|
||||
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
|
||||
* Werte liegen zwischen 0 und 255.
|
||||
* <var>alpha</var> gibt den den Transparentwert an (auch zwischen
|
||||
* 0 und 255), wobei 0 komplett durchsichtig ist und 255 komplett deckend.
|
||||
* Erstellt eine Farbe. Die Parameter {@code red}, {@code green} und
|
||||
* {@code blue} geben die Rot-, Grün- und Blauanteile der Farbe. Die Werte
|
||||
* liegen zwischen 0 und 255. {@code alpha} gibt den den Transparentwert an
|
||||
* (auch zwischen 0 und 255), wobei 0 komplett durchsichtig ist und 255
|
||||
* komplett deckend.
|
||||
*
|
||||
* @param red Rotwert zwischen 0 und 255.
|
||||
* @param green Grünwert zwischen 0 und 255.
|
||||
@@ -146,24 +186,24 @@ public class Color {
|
||||
* @param alpha Transparentwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int red, int green, int blue, int alpha ) {
|
||||
rgba = ((alpha&0xFF) << 24) | ((red&0xFF) << 16) | ((green&0xFF) << 8) | ((blue&0xFF) << 0);
|
||||
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe als Kopie von <var>color</var>.
|
||||
* Erstellt eine Farbe als Kopie von {@code color}.
|
||||
*
|
||||
* @param color
|
||||
* @param color Eine Farbe.
|
||||
*/
|
||||
public Color( Color color ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
this(color.getRGBA(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Farbe als Kopie von <var>color</var> und ersetzt den
|
||||
* Transparentwert durch <var>alpha</var>.
|
||||
* Erstellt eine Farbe als Kopie von {@code color} und ersetzt den
|
||||
* Transparentwert durch {@code alpha}.
|
||||
*
|
||||
* @param color
|
||||
* @param alpha
|
||||
* @param color Eine Farbe.
|
||||
* @param alpha Der neue Transparenzwert.
|
||||
*/
|
||||
public Color( Color color, int alpha ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), alpha);
|
||||
@@ -198,46 +238,97 @@ public class Color {
|
||||
|
||||
/**
|
||||
* Erzeugt eine Farbe aus einem kodierten RGBA Integer-Wert.
|
||||
* <p>
|
||||
* Der 32-bit Integer enthält (von rechts) in Bit 1 bis 8 den Rotwert, in
|
||||
* Bit 9 bis 16 Grünwert, in Bit 17 bis 24 den Blauwert und in Bit 25 bis 32
|
||||
* den Transparenzwert der Farbe.
|
||||
*
|
||||
* @param rgba
|
||||
* @return
|
||||
* @param rgba Eine RGBA-Farbe.
|
||||
* @return Ein Farbobjekt.
|
||||
*/
|
||||
public static Color getRGBColor( int rgba ) {
|
||||
Color c = new Color(rgba, true);
|
||||
return c;
|
||||
return new Color(rgba, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Farbe aus Werten im
|
||||
* <a href="https://de.wikipedia.org/wiki/HSV-Farbraum">HSB-Farbraum</a>.
|
||||
* <p>
|
||||
* {code h} beschreibt den Farbwert (engl. <em>hue</em>), {@code s} die
|
||||
* Sättigung (engl. <em>saturation</em>) und {@code b} die absolute
|
||||
* Helligkeit (engl. <em>brightness</em>) der Farbe. Alle Werte werden
|
||||
* zwischen 0.0 und 1.0 angegeben.
|
||||
*
|
||||
* @param h Der Farbwert.
|
||||
* @param s Die Sättigung.
|
||||
* @param b Die absolute Helligkeit.
|
||||
* @return Ein Farbobjekt.
|
||||
* @see java.awt.Color#getHSBColor(float, float, float)
|
||||
*/
|
||||
public static Color getHSBColor( double h, double s, double b ) {
|
||||
return new Color(java.awt.Color.getHSBColor((float) h, (float) s, (float) b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Farbe aus Werten im
|
||||
* <a href="https://de.wikipedia.org/wiki/HSV-Farbraum">HSL-Farbraum</a>.
|
||||
* <p>
|
||||
* {code h} beschreibt den Farbwert (engl. <em>hue</em>), {@code s} die
|
||||
* Sättigung (engl. <em>saturation</em>) und {@code l} die relative
|
||||
* Helligkeit (engl. <em>lightness</em>) der Farbe. Alle Werte werden
|
||||
* zwischen 0.0 und 1.0 angegeben.
|
||||
*
|
||||
* @param h Der Farbwert.
|
||||
* @param s Die Sättigung.
|
||||
* @param l Die relative Helligkeit.
|
||||
* @return Ein Farbobjekt.
|
||||
*/
|
||||
public static Color getHSLColor( double h, double s, double l ) {
|
||||
int rgb = Color.HSLtoRGB(new float[]{(float) h, (float) s, (float) l});
|
||||
return Color.getRGBColor(rgb);
|
||||
}
|
||||
|
||||
public static Color parseString( String pColor ) {
|
||||
pColor = pColor.toLowerCase().strip();
|
||||
if( pColor.contains("red") || pColor.contains("rot") ) {
|
||||
return Color.RED.copy();
|
||||
} else if( pColor.contains("blue") || pColor.contains("blau") ) {
|
||||
return Color.BLUE.copy();
|
||||
} else if( pColor.contains("green") || pColor.contains("grün") || pColor.contains("gruen") ) {
|
||||
return Color.GREEN.copy();
|
||||
} else if( pColor.contains("yellow") || pColor.contains("gelb") ) {
|
||||
return Color.YELLOW.copy();
|
||||
} else {
|
||||
return new Color();
|
||||
/**
|
||||
* Erstellt aus einem Farbnamen ein Farbobjekt.
|
||||
* <p>
|
||||
* Die gültigen Farbnamen können unter <a
|
||||
* href="https://htmlcolors.com/color-names">https://htmlcolors.com/color-names</a>
|
||||
* nachgeschlagen werden.
|
||||
*
|
||||
* @param color Der Name einer Farbe.
|
||||
* @return Ein Farbobjekt.
|
||||
*/
|
||||
public static Color parseString( String color ) {
|
||||
color = color.toLowerCase().strip();
|
||||
|
||||
// Parse colornames file and return first match
|
||||
try( InputStream in = Color.class.getResourceAsStream("colornames.csv"); BufferedReader reader = new BufferedReader(new InputStreamReader(in)) ) {
|
||||
String line;
|
||||
while( (line = reader.readLine()) != null ) {
|
||||
String[] parts = line.split(",");
|
||||
if( parts.length == 2 ) {
|
||||
if( parts[0].equals(color) ) {
|
||||
return Color.parseHexcode(parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch( IOException ex ) {
|
||||
// LOG?
|
||||
}
|
||||
return new Color();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann sechs-
|
||||
* oder achtstellig sein (wenn ein Transparentwert vorhanden ist). Dem Code
|
||||
* kann ein {@code #} Zeichen vorangestellt sein.
|
||||
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann drei-,
|
||||
* sechs- oder achtstellig sein (wenn ein Transparentwert vorhanden ist).
|
||||
* Dem Code kann ein {@code #} Zeichen vorangestellt sein, muss es aber
|
||||
* nicht.
|
||||
* <p>
|
||||
* Bei einem dreistelligen Code wird jedes zeichen doppelt interpretiert.
|
||||
* Das beduetet {@code #ABC} ist gleichbedeutend mit {@code #AABBCC}.
|
||||
*
|
||||
* @param hexcode
|
||||
* @return
|
||||
* @param hexcode Eine Farbe als Hexcode.
|
||||
* @return Ein Farbobjekt.
|
||||
*/
|
||||
public static Color parseHexcode( String hexcode ) {
|
||||
if( hexcode.startsWith("#") ) {
|
||||
@@ -254,8 +345,6 @@ public class Color {
|
||||
} else if( hexcode.length() == 8 ) {
|
||||
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
|
||||
hexcode = hexcode.substring(0, 6);
|
||||
} else {
|
||||
hexcode = hexcode;
|
||||
}
|
||||
|
||||
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
|
||||
@@ -276,7 +365,7 @@ public class Color {
|
||||
if( color1 == null && color2 == null ) {
|
||||
throw new IllegalArgumentException("Color.interpolate() needs at least one color to be not null.");
|
||||
}
|
||||
if( t < 0.0 || color2 == null ) {
|
||||
if( (color1 != null && t < 0.0) || color2 == null ) {
|
||||
return color1.copy();
|
||||
}
|
||||
if( t > 1.0 || color1 == null ) {
|
||||
@@ -302,12 +391,12 @@ public class Color {
|
||||
if( c == 0 ) {
|
||||
h_ = 0;
|
||||
} else if( max == r ) {
|
||||
h_ = (float) (g - b) / c;
|
||||
h_ = (g - b) / c;
|
||||
if( h_ < 0 ) h_ += 6.f;
|
||||
} else if( max == g ) {
|
||||
h_ = (float) (b - r) / c + 2.f;
|
||||
h_ = (b - r) / c + 2.f;
|
||||
} else if( max == b ) {
|
||||
h_ = (float) (r - g) / c + 4.f;
|
||||
h_ = (r - g) / c + 4.f;
|
||||
}
|
||||
float h = 60.f * h_;
|
||||
|
||||
@@ -326,6 +415,14 @@ public class Color {
|
||||
return hsl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert die Komponenten einer Farbe aus dem HSL-Farbraum in den
|
||||
* RGB-Farbraum.
|
||||
*
|
||||
* @param hsl Die HSL-Komponenten als float-Array.
|
||||
* @return Der RGBA-Farbwert.
|
||||
* @see #HSLtoRGB(float[], int)
|
||||
*/
|
||||
public static int HSLtoRGB( float[] hsl ) {
|
||||
return HSLtoRGB(hsl, 255);
|
||||
}
|
||||
@@ -334,9 +431,9 @@ public class Color {
|
||||
* Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den
|
||||
* RGB-Farbraum.
|
||||
* <p>
|
||||
* Die Farbkomponenten werden als Float-Array übergeben. Im Index 0 steht
|
||||
* der h-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den s- und
|
||||
* l-Wert im Bereich von 0 bis 1.
|
||||
* Die Farbkomponenten werden als float-Array übergeben. Im Index 0 steht
|
||||
* der H-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den S- und
|
||||
* L-Wert im Bereich von 0 bis 1.
|
||||
*
|
||||
* @param hsl Die Farbkomponenten im HSL-Farbraum.
|
||||
* @param alpha Ein Transparenzwert im Bereich 0 bis 255.
|
||||
@@ -463,17 +560,37 @@ public class Color {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaintContext createContext( ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints ) {
|
||||
return getJavaColor().createContext(cm, deviceBounds, userBounds, xform, hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTransparency() {
|
||||
int alpha = getAlpha();
|
||||
if( alpha == 0xff ) {
|
||||
return Transparency.OPAQUE;
|
||||
} else if( alpha == 0 ) {
|
||||
return Transparency.BITMASK;
|
||||
} else {
|
||||
return Transparency.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||
* <p>
|
||||
* Die Methode gibt genau dann {@code true} zurück, wenn das andere Objekt
|
||||
* nicht {@code null} ist, vom Typ {@code Color} ist und es dieselben Rot-,
|
||||
* Grün-, Blau- und Transparenzwerte hat.
|
||||
*
|
||||
* Die Methode gibt genau dann {@code true} zurück, wenn das andere
|
||||
* Objekt nicht {@code null} ist, vom Typ {@code Color} ist und es
|
||||
* dieselben Rot-, Grün-, Blau- und Transparenzwerte hat.
|
||||
* @param obj Das zu vergleichende Objekt.
|
||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals( Object obj ) {
|
||||
if( obj == null ) { return false; }
|
||||
if( obj == null ) {
|
||||
return false;
|
||||
}
|
||||
if( obj instanceof Color ) {
|
||||
return ((Color) obj).getRGBA() == this.rgba;
|
||||
} else if( obj instanceof java.awt.Color ) {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
public class ColorLayer extends Layer {
|
||||
|
||||
private Color background;
|
||||
|
||||
public ColorLayer( Color color ) {
|
||||
this.background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
public ColorLayer( int width, int height, Color color ) {
|
||||
super(width, height);
|
||||
this.background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
clear();
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return background;
|
||||
}
|
||||
|
||||
public void setColor( Color color ) {
|
||||
background = color;
|
||||
clear();
|
||||
}
|
||||
|
||||
public void setColor( int gray ) {
|
||||
setColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setColor( int gray, int alpha ) {
|
||||
setColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue ) {
|
||||
setColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setColor( int red, int green, int blue, int alpha ) {
|
||||
setColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
drawing.setColor(background.getJavaColor());
|
||||
drawing.fillRect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +1,27 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.anim.Easing;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
import schule.ngb.zm.util.Noise;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Font;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* Basisklasse für die meisten Objekte der Zeichemaschine, die von Nutzern
|
||||
* Basisklasse für die meisten Objekte der Zeichenmaschine, die von Nutzern
|
||||
* erweitert werden können.
|
||||
* <p>
|
||||
* Die Konstanten stellen viele Funktionen zur einfachen Programmierung bereit
|
||||
* und enthält auch einige dynamische Werte, die von der Zeichenmaschine laufend
|
||||
* aktuell gehalten werden (beispielsweise {@link #runtime}.
|
||||
* aktuell gehalten werden (beispielsweise {@link #runtime}).
|
||||
* <p>
|
||||
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
||||
* {@code Constants} zu erben, um die Methoden und Konstanten einfach im
|
||||
@@ -64,53 +68,89 @@ public class Constants {
|
||||
/**
|
||||
* Patchversion der Zeichenmaschine.
|
||||
*/
|
||||
public static final int APP_VERSION_REV = 22;
|
||||
public static final int APP_VERSION_REV = 32;
|
||||
|
||||
/**
|
||||
* Version der Zeichenmaschine als Text-String.
|
||||
*/
|
||||
public static final String APP_VERSION = APP_VERSION_MAJ + "." + APP_VERSION_MIN + "." + APP_VERSION_REV;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
||||
*/
|
||||
public static final boolean MACOS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
||||
*/
|
||||
public static final boolean WINDOWS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
||||
*/
|
||||
public static final boolean LINUX;
|
||||
|
||||
static {
|
||||
final String name = System.getProperty("os.name");
|
||||
|
||||
if( name.contains("Mac") ) {
|
||||
MACOS = true;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
} else if( name.contains("Windows") ) {
|
||||
MACOS = false;
|
||||
WINDOWS = true;
|
||||
LINUX = false;
|
||||
} else if( name.equals("Linux") ) { // true for the ibm vm
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = true;
|
||||
} else {
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardbreite eines Zeichenfensters.
|
||||
*/
|
||||
public static final int STD_WIDTH = 400;
|
||||
public static final int DEFAULT_WIDTH = 400;
|
||||
|
||||
/**
|
||||
* Standardhöhe eines Zeichenfensters.
|
||||
*/
|
||||
public static final int STD_HEIGHT = 400;
|
||||
public static final int DEFAULT_HEIGHT = 400;
|
||||
|
||||
/**
|
||||
* Standardwert für die Frames pro Sekunde einer Zeichenmaschine.
|
||||
*/
|
||||
public static final int STD_FPS = 60;
|
||||
public static final int DEFAULT_FPS = 60;
|
||||
|
||||
/**
|
||||
* Standardfarbe der Füllungen.
|
||||
*/
|
||||
public static final Color STD_FILLCOLOR = Color.WHITE;
|
||||
public static Color DEFAULT_FILLCOLOR = Color.WHITE;
|
||||
|
||||
/**
|
||||
* Standardfarbe der Konturen.
|
||||
*/
|
||||
public static final Color STD_STROKECOLOR = Color.BLACK;
|
||||
public static Color DEFAULT_STROKECOLOR = Color.BLACK;
|
||||
|
||||
/**
|
||||
* Standardwert für die Dicke der Konturen.
|
||||
*/
|
||||
public static final double STD_STROKEWEIGHT = 1.0;
|
||||
public static double DEFAULT_STROKEWEIGHT = 1.0;
|
||||
|
||||
/**
|
||||
* Standardwert für die Schriftgröße.
|
||||
*/
|
||||
public static final int STD_FONTSIZE = 14;
|
||||
public static int DEFAULT_FONTSIZE = 14;
|
||||
|
||||
/**
|
||||
* Standardwert für den Abstand von Formen.
|
||||
*/
|
||||
public static final int STD_BUFFER = 10;
|
||||
public static int DEFAULT_BUFFER = 10;
|
||||
|
||||
public static int DEFAULT_ANIM_RUNTIME = 1000;
|
||||
|
||||
@@ -405,16 +445,32 @@ public class Constants {
|
||||
*/
|
||||
public static final double TWO_PI = Math.PI * 2.0;
|
||||
|
||||
/**
|
||||
* Konstante für fette Schrift.
|
||||
*/
|
||||
public static final int BOLD = Font.BOLD;
|
||||
|
||||
/**
|
||||
* Konstante für kursive Schrift.
|
||||
*/
|
||||
public static final int ITALIC = Font.ITALIC;
|
||||
|
||||
/**
|
||||
* Konstante für normale Schrift.
|
||||
*/
|
||||
public static final int PLAIN = Font.PLAIN;
|
||||
|
||||
/*
|
||||
* Globale Variablen, die von allen Klassen genutzt werden dürfen. Änderungen
|
||||
* wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der
|
||||
* Zeichenmaschine selbst vorgenommen werden.
|
||||
*/
|
||||
// TODO: (ngb) volatile ?
|
||||
|
||||
/**
|
||||
* Aktuell dargestellte Bilder pro Sekunde.
|
||||
*/
|
||||
public static int framesPerSecond = STD_FPS;
|
||||
public static int framesPerSecond = DEFAULT_FPS;
|
||||
|
||||
/**
|
||||
* Anzahl der Ticks (Frames), die das Programm bisher läuft.
|
||||
@@ -730,6 +786,30 @@ public class Constants {
|
||||
|
||||
// Mathematische Funktionen
|
||||
|
||||
/**
|
||||
* Berechnet das Minimum aller angegebenen Werte.
|
||||
*
|
||||
* <pre><code>
|
||||
* int minimum = min(1, 5, 3); // 1
|
||||
* </code></pre>
|
||||
*
|
||||
* @param numbers Die Werte, aus denen das Minimum ermittelt werden soll.
|
||||
* @return Das Minimum der Werte.
|
||||
* @throws IllegalArgumentException Wenn die Eingabe {@code null} oder leer
|
||||
* ist.
|
||||
*/
|
||||
public static final int min( int... numbers ) {
|
||||
if( numbers == null || numbers.length == 0 ) {
|
||||
throw new IllegalArgumentException("Array may not be <null> or empty.");
|
||||
}
|
||||
|
||||
int min = numbers[0];
|
||||
for( int i = 1; i < numbers.length; i++ ) {
|
||||
min = Math.min(min, numbers[i]);
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet das Minimum aller angegebenen Werte.
|
||||
*
|
||||
@@ -754,6 +834,30 @@ public class Constants {
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet das Maximum aller angegebenen Werte.
|
||||
*
|
||||
* <pre><code>
|
||||
* double maximum = max(1, 5, 3); // 5
|
||||
* </code></pre>
|
||||
*
|
||||
* @param numbers Die Werte, aus denen das Maximum ermittelt werden soll.
|
||||
* @return Das Maximum der Werte.
|
||||
* @throws IllegalArgumentException Wenn die Eingabe {@code null} oder leer
|
||||
* ist.
|
||||
*/
|
||||
public static final int max( int... numbers ) {
|
||||
if( numbers == null || numbers.length == 0 ) {
|
||||
throw new IllegalArgumentException("Array may not be <null> or empty.");
|
||||
}
|
||||
|
||||
int max = numbers[0];
|
||||
for( int i = 1; i < numbers.length; i++ ) {
|
||||
max = Math.max(max, numbers[i]);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet das Maximum aller angegebenen Werte.
|
||||
*
|
||||
@@ -999,7 +1103,7 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt den Arkuskosinus der angegebenen Zahl.
|
||||
* Ermittelt den Arcuskosinus der angegebenen Zahl.
|
||||
*
|
||||
* @param x Eine Zahl.
|
||||
* @return {@code acos(x)}.
|
||||
@@ -1009,7 +1113,7 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt den Arkusktangens der angegebenen Zahl.
|
||||
* Ermittelt den Arcusktangens der angegebenen Zahl.
|
||||
*
|
||||
* @param x Eine Zahl.
|
||||
* @return {@code atan(x)}.
|
||||
@@ -1149,6 +1253,12 @@ public class Constants {
|
||||
return interpolate(toMin, toMax, (value - fromMin) / (fromMax - fromMin));
|
||||
}
|
||||
|
||||
public static final double distance( double fromX, double fromY, double toX, double toY ) {
|
||||
double diffX = toX - fromX;
|
||||
double diffY = toY - fromY;
|
||||
return sqrt(diffX*diffX + diffY*diffY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Geteilte {@code Random}-Instanz für einheitliche Zufallszahlen.
|
||||
*/
|
||||
@@ -1269,7 +1379,8 @@ public class Constants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Pseudozufallszahl nach einer Gaussverteilung.
|
||||
* Erzeugt eine Pseudozufallszahl zwischen -1 und 1 nach einer
|
||||
* Normalverteilung mit Mittelwert 0 und Standardabweichung 1.
|
||||
*
|
||||
* @return Eine Zufallszahl.
|
||||
* @see Random#nextGaussian()
|
||||
@@ -1278,6 +1389,26 @@ public class Constants {
|
||||
return getRandom().nextGaussian();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt ein zufälliges Element aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
*/
|
||||
/*public static final int choice( int... values ) {
|
||||
return values[random(0, values.length - 1)];
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Wählt ein zufälliges Element aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
*/
|
||||
/*public static final double choice( double... values ) {
|
||||
return values[random(0, values.length - 1)];
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Wählt ein zufälliges Element aus dem Array aus.
|
||||
*
|
||||
@@ -1285,7 +1416,7 @@ public class Constants {
|
||||
* @param <T> Datentyp des Elements.
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
*/
|
||||
public static final <T> T choice( T[] values ) {
|
||||
public static final <T> T choice( T... values ) {
|
||||
return values[random(0, values.length - 1)];
|
||||
}
|
||||
|
||||
@@ -1294,15 +1425,149 @@ public class Constants {
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @param n Anzahl der auszuwählenden Elemente.
|
||||
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||
* {@code values.length < n}, also nicht
|
||||
* genug Werte zur Wahl stehen.
|
||||
*/
|
||||
public static final int[] choice( int[] values, int n, boolean unique ) {
|
||||
if( unique && values.length < n )
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||
);
|
||||
|
||||
int[] result = new int[n];
|
||||
int[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
int j = random(0, valuesCopy.length - 1);
|
||||
int l = valuesCopy.length - 1;
|
||||
|
||||
result[i] = valuesCopy[j];
|
||||
valuesCopy[j] = valuesCopy[l];
|
||||
valuesCopy[l] = result[i];
|
||||
|
||||
if( unique )
|
||||
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @param n Anzahl der auszuwählenden Elemente.
|
||||
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||
* {@code values.length < n}, also nicht
|
||||
* genug Werte zur Wahl stehen.
|
||||
*/
|
||||
public static final double[] choice( double[] values, int n, boolean unique ) {
|
||||
if( unique && values.length < n )
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||
);
|
||||
|
||||
double[] result = new double[n];
|
||||
double[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
int j = random(0, valuesCopy.length - 1);
|
||||
int l = valuesCopy.length - 1;
|
||||
|
||||
result[i] = valuesCopy[j];
|
||||
valuesCopy[j] = valuesCopy[l];
|
||||
valuesCopy[l] = result[i];
|
||||
|
||||
if( unique )
|
||||
valuesCopy = Arrays.copyOf(valuesCopy, l);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt die angegebene Anzahl Elemente aus dem Array aus.
|
||||
*
|
||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||
* @param n Anzahl der auszuwählenden Elemente.
|
||||
* @param unique Bei {@code true} werden Elemente im Array nur maximal
|
||||
* einmal ausgewählt (Ziehen ohne Zurücklegen).
|
||||
* @param <T> Datentyp der Elemente.
|
||||
* @return Ein zufälliges Element aus dem Array.
|
||||
* @throws IllegalArgumentException Wenn {@code unique == true} und
|
||||
* {@code values.length < n}, also nicht
|
||||
* genug Werte zur Wahl stehen.
|
||||
*/
|
||||
public static final <T> T[] choice( T[] values, int n ) {
|
||||
Object[] result = new Object[n];
|
||||
public static final <T> T[] choice( T[] values, int n, boolean unique ) {
|
||||
if( unique && values.length < n )
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Need at least <%d> values to choose <%d> unique values (<%d> given).", n, n, values.length)
|
||||
);
|
||||
|
||||
T[] result = Arrays.copyOf(values, n);
|
||||
T[] valuesCopy = Arrays.copyOf(values, values.length);
|
||||
for( int i = 0; i < n; i++ ) {
|
||||
result[i] = choice(values);
|
||||
int last = valuesCopy.length - 1;
|
||||
int j = random(0, last);
|
||||
|
||||
result[i] = valuesCopy[j];
|
||||
valuesCopy[j] = valuesCopy[last];
|
||||
valuesCopy[last] = result[i];
|
||||
|
||||
if( unique )
|
||||
valuesCopy = Arrays.copyOf(valuesCopy, last);
|
||||
}
|
||||
return (T[]) result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||
*
|
||||
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||
* @return Das Array in zufälliger Reihenfolge.
|
||||
*/
|
||||
public static final int[] shuffle( int[] values ) {
|
||||
for( int i = 0; i < values.length - 1; i++ ) {
|
||||
int j = random(i, values.length - 1);
|
||||
int tmp = values[i];
|
||||
values[i] = values[j];
|
||||
values[j] = tmp;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bringt die Zahlen im Array in eine zufällige Reihenfolge.
|
||||
*
|
||||
* @param values Ein Array mit Zahlen, die gemischt werden sollen.
|
||||
* @return Das Array in zufälliger Reihenfolge.
|
||||
*/
|
||||
public static final double[] shuffle( double[] values ) {
|
||||
for( int i = 0; i < values.length - 1; i++ ) {
|
||||
int j = random(i, values.length - 1);
|
||||
double tmp = values[i];
|
||||
values[i] = values[j];
|
||||
values[j] = tmp;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bringt die Werte im Array in eine zufällige Reihenfolge.
|
||||
*
|
||||
* @param values Ein Array mit Werte, die gemischt werden sollen.
|
||||
* @param <T> Datentyp der Elemente.
|
||||
* @return Das Array in zufälliger Reihenfolge.
|
||||
*/
|
||||
public static final <T> T[] shuffle( T[] values ) {
|
||||
java.util.List<T> valueList = Arrays.asList(values);
|
||||
Collections.shuffle(valueList, random);
|
||||
return valueList.toArray(values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1471,38 +1736,93 @@ public class Constants {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen char-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der char-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( char value ) {
|
||||
return (double) value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen byte-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der byte-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( byte value ) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen short-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der short-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( short value ) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen long-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der long-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( long value ) {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen double-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der double-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( double value ) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen float-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der float-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( float value ) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen int-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der int-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( int value ) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen boolean-Wert in einen double-Wert.
|
||||
*
|
||||
* @param value Der boolean-Wert.
|
||||
* @return Ein double-Wert.
|
||||
*/
|
||||
public static final double asDouble( boolean value ) {
|
||||
return value ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen String in einen double-Wert.
|
||||
*
|
||||
* @param value Der String.
|
||||
* @return Ein double-Wert.
|
||||
* @see Double#parseDouble(String)
|
||||
*/
|
||||
public static final double asDouble( String value ) {
|
||||
try {
|
||||
return Double.parseDouble(value);
|
||||
@@ -1511,38 +1831,93 @@ public class Constants {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen char-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der char-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( char value ) {
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen byte-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der byte-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( byte value ) {
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen short-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der short-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( short value ) {
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen int-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der int-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( int value ) {
|
||||
return value != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen long-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der long-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( long value ) {
|
||||
return value != 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen double-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der double-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( double value ) {
|
||||
return value != 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen float-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der float-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( float value ) {
|
||||
return value != 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen boolean-Wert in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der boolean-Wert.
|
||||
* @return Ein boolean-Wert.
|
||||
*/
|
||||
public static final boolean asBool( boolean value ) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert einen String in einen boolean-Wert.
|
||||
*
|
||||
* @param value Der String.
|
||||
* @return Ein boolean-Wert.
|
||||
* @see Boolean#parseBoolean(String)
|
||||
*/
|
||||
public static final boolean asBool( String value ) {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
@@ -1603,7 +1978,7 @@ public class Constants {
|
||||
return Integer.valueOf(binary, 16);
|
||||
}
|
||||
|
||||
// Konstants für Key events (Copied from KeyEvent)
|
||||
// Konstanten für Key events (Copied from KeyEvent)
|
||||
|
||||
/**
|
||||
* Constant for the ENTER virtual key.
|
||||
|
||||
@@ -3,14 +3,29 @@ package schule.ngb.zm;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Zeichenbare Objekte können auf eine Zeichenfläche gezeichnet werden.
|
||||
* In der Regel werden sie einmal pro Frame gezeichnet.
|
||||
* {@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.
|
||||
*/
|
||||
@@ -18,7 +33,7 @@ public interface Drawable {
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, um das Objekt auf die Zeichenfläche <var>graphics</var>
|
||||
* zu draw.
|
||||
* zu zeichnen.
|
||||
* <p>
|
||||
* Das Objekt muss dafür Sorge tragen, dass der Zustand der Zeichenfläche
|
||||
* (Transformationsmatrix, Farbe, ...) erhalten bleibt. Das Objekt sollte
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class DrawableLayer extends Layer {
|
||||
|
||||
protected LinkedList<Drawable> drawables = new LinkedList<>();
|
||||
|
||||
protected boolean clearBeforeDraw = true;
|
||||
|
||||
public DrawableLayer() {
|
||||
}
|
||||
|
||||
public DrawableLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
public void add( Drawable... drawables ) {
|
||||
synchronized( drawables ) {
|
||||
for( Drawable d : drawables ) {
|
||||
this.drawables.add(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public java.util.List<Drawable> getDrawables() {
|
||||
return drawables;
|
||||
}
|
||||
|
||||
public boolean isClearBeforeDraw() {
|
||||
return clearBeforeDraw;
|
||||
}
|
||||
|
||||
public void setClearBeforeDraw( boolean pClearBeforeDraw ) {
|
||||
this.clearBeforeDraw = pClearBeforeDraw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
if( clearBeforeDraw ) {
|
||||
clear();
|
||||
}
|
||||
|
||||
synchronized( drawables ) {
|
||||
for( Drawable d : drawables ) {
|
||||
if( d.isVisible() ) {
|
||||
d.draw(drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,560 +0,0 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.util.Stack;
|
||||
|
||||
public class DrawingLayer extends Layer {
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
private Options.Direction default_anchor = CENTER;
|
||||
|
||||
protected Line2D.Double line = new Line2D.Double();
|
||||
protected Ellipse2D.Double ellipse = new Ellipse2D.Double();
|
||||
protected Rectangle2D.Double rect = new Rectangle2D.Double();
|
||||
protected Arc2D.Double arc = new Arc2D.Double();
|
||||
|
||||
protected Path2D.Double path = new Path2D.Double();
|
||||
|
||||
private boolean pathStarted = false;
|
||||
|
||||
private Stack<AffineTransform> transformStack = new Stack<>();
|
||||
|
||||
private FontMetrics fontMetrics = null;
|
||||
|
||||
public DrawingLayer() {
|
||||
super();
|
||||
transformStack.push(new AffineTransform());
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
public DrawingLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
transformStack.push(new AffineTransform());
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( Color color ) {
|
||||
fillColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
fillColor = null;
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color ) {
|
||||
strokeColor = color;
|
||||
drawing.setColor(color.getJavaColor());
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
strokeColor = null;
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void setStrokeWeight( double pWeight ) {
|
||||
strokeWeight = pWeight;
|
||||
drawing.setStroke(createStroke());
|
||||
}
|
||||
|
||||
protected Stroke createStroke() {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||
case DASHED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{5.0f}, 0.0f);
|
||||
default:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND);
|
||||
}
|
||||
}
|
||||
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
switch( type ) {
|
||||
case DASHED:
|
||||
this.strokeType = DASHED;
|
||||
break;
|
||||
case DOTTED:
|
||||
this.strokeType = DOTTED;
|
||||
break;
|
||||
default:
|
||||
this.strokeType = SOLID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void resetStroke() {
|
||||
setStrokeColor(STD_STROKECOLOR);
|
||||
setStrokeWeight(STD_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
}
|
||||
|
||||
public void setAnchor( Options.Direction anchor ) {
|
||||
default_anchor = anchor;
|
||||
}
|
||||
|
||||
public void clear( int gray ) {
|
||||
clear(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void clear( int gray, int alpha ) {
|
||||
clear(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void clear( int red, int green, int blue ) {
|
||||
clear(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void clear( int red, int green, int blue, int alpha ) {
|
||||
clear(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void clear( Color pColor ) {
|
||||
/*graphics.setBackground(pColor);
|
||||
graphics.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());*/
|
||||
java.awt.Color currentColor = drawing.getColor();
|
||||
pushMatrix();
|
||||
resetMatrix();
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
drawing.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
|
||||
drawing.setColor(currentColor);
|
||||
popMatrix();
|
||||
}
|
||||
|
||||
public void line( double x1, double y1, double x2, double y2 ) {
|
||||
//Shape line = new Line2D.Double(x1, y1, x2, y2);
|
||||
line.setLine(x1, y1, x2, y2);
|
||||
drawShape(line);
|
||||
}
|
||||
|
||||
public void pixel( double x, double y ) {
|
||||
// square(x, y, 1);
|
||||
buffer.setRGB((int)x, (int)y, fillColor.getRGBA());
|
||||
}
|
||||
|
||||
public void square( double x, double y, double w ) {
|
||||
rect(x, y, w, w);
|
||||
}
|
||||
|
||||
public void square( double x, double y, double w, Options.Direction anchor ) {
|
||||
rect(x, y, w, w, anchor);
|
||||
}
|
||||
|
||||
public void rect( double x, double y, double w, double h ) {
|
||||
rect(x, y, w, h, default_anchor);
|
||||
}
|
||||
|
||||
public void rect( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor);
|
||||
// Shape rect = new Rectangle2D.Double(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
||||
rect.setRect(anchorPoint.getX(), anchorPoint.getY(), w, h);
|
||||
fillShape(rect);
|
||||
drawShape(rect);
|
||||
}
|
||||
|
||||
public void point( double x, double y ) {
|
||||
circle(x - 1, y - 1, 2);
|
||||
}
|
||||
|
||||
public void circle( double x, double y, double r ) {
|
||||
ellipse(x, y, r+r, r+r, default_anchor);
|
||||
}
|
||||
|
||||
public void circle( double x, double y, double r, Options.Direction anchor ) {
|
||||
ellipse(x, y, r+r, r+r, anchor);
|
||||
}
|
||||
|
||||
public void ellipse( double x, double y, double w, double h ) {
|
||||
ellipse(x, y, w, h, default_anchor);
|
||||
}
|
||||
|
||||
public void ellipse( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, anchor);
|
||||
// Shape ellipse = new Ellipse2D.Double(anchorPoint.x, anchorPoint.y, w, h);
|
||||
ellipse.setFrame(anchorPoint.x, anchorPoint.y, w, h);
|
||||
fillShape(ellipse);
|
||||
drawShape(ellipse);
|
||||
}
|
||||
|
||||
public void arc( double x, double y, double r, double angle1, double angle2 ) {
|
||||
arc(x, y, r+r, r+r, angle1, angle2);
|
||||
}
|
||||
|
||||
public void arc( double x, double y, double w, double h, double angle1, double angle2 ) {
|
||||
while( angle2 < angle1 ) angle2 += 360.0;
|
||||
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, w, h, CENTER);
|
||||
/*Shape arc = new Arc2D.Double(
|
||||
anchorPoint.x,
|
||||
anchorPoint.y,
|
||||
d, d,
|
||||
angle1, angle2 - angle1,
|
||||
Arc2D.OPEN
|
||||
);*/
|
||||
arc.setArc(
|
||||
anchorPoint.x, anchorPoint.y,
|
||||
w, h,
|
||||
//Math.toRadians(angle1), Math.toRadians(angle2 - angle1),
|
||||
angle1, angle2-angle1,
|
||||
Arc2D.OPEN
|
||||
);
|
||||
|
||||
drawShape(arc);
|
||||
}
|
||||
|
||||
public void pie( double x, double y, double r, double angle1, double angle2 ) {
|
||||
while( angle2 < angle1 ) angle2 += 360.0;
|
||||
|
||||
double d = r+r;
|
||||
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, d, d, CENTER);
|
||||
/*Shape arc = new Arc2D.Double(
|
||||
anchorPoint.x,
|
||||
anchorPoint.y,
|
||||
d, d,
|
||||
angle1, angle2 - angle1,
|
||||
Arc2D.PIE
|
||||
);*/
|
||||
arc.setArc(
|
||||
anchorPoint.x, anchorPoint.y,
|
||||
d, d,
|
||||
angle1, angle2 - angle1,
|
||||
Arc2D.PIE
|
||||
);
|
||||
|
||||
fillShape(arc);
|
||||
drawShape(arc);
|
||||
}
|
||||
|
||||
public void curve( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||
QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, x2, y2, x3, y3);
|
||||
drawShape(curve);
|
||||
}
|
||||
|
||||
public void curve( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4 ) {
|
||||
CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
drawShape(curve);
|
||||
}
|
||||
|
||||
public void triangle( double x1, double y1, double x2, double y2, double x3, double y3 ) {
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(x1, y1);
|
||||
path.lineTo(x2, y2);
|
||||
path.lineTo(x3, y3);
|
||||
path.lineTo(x1, y1);
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
|
||||
public void rhombus( double x, double y, double width, double height ) {
|
||||
rhombus(x, y, width, height, default_anchor);
|
||||
}
|
||||
|
||||
public void rhombus( double x, double y, double width, double height, Options.Direction anchor ) {
|
||||
double whalf = width / 2.0, hhalf = height / 2.0;
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, width, height, anchor);
|
||||
polygon(anchorPoint.x + whalf, anchorPoint.y, anchorPoint.x + width, anchorPoint.y + hhalf, anchorPoint.x + whalf, anchorPoint.y + height, anchorPoint.x, anchorPoint.y + hhalf);
|
||||
}
|
||||
|
||||
public void polygon( double... coordinates ) {
|
||||
if( coordinates.length < 4 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(coordinates[0], coordinates[1]);
|
||||
for( int i = 2; i < coordinates.length; i += 2 ) {
|
||||
if( i + 1 < coordinates.length ) {
|
||||
path.lineTo(coordinates[i], coordinates[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
int len = coordinates.length;
|
||||
if( coordinates[len - 2] != coordinates[0] || coordinates[len - 1] != coordinates[1] ) {
|
||||
path.lineTo(coordinates[0], coordinates[1]);
|
||||
}
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
|
||||
public void polygon( Point2D... points ) {
|
||||
if( points.length < 2 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(points[0].getX(), points[0].getY());
|
||||
for( int i = 1; i < points.length; i += 1 ) {
|
||||
path.lineTo(points[i].getX(), points[i].getY());
|
||||
}
|
||||
|
||||
int len = points.length;
|
||||
if( points[len - 1].equals(points[0]) ) {
|
||||
path.moveTo(points[0].getX(), points[0].getY());
|
||||
}
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
|
||||
// TODO: General shape drawing
|
||||
/*
|
||||
public void beginShape() {
|
||||
path.reset();
|
||||
pathStarted = false;
|
||||
}
|
||||
|
||||
public void lineTo( double x, double y ) {
|
||||
if( !pathStarted ) {
|
||||
path.moveTo(x, y);
|
||||
pathStarted = true;
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public void endShape() {
|
||||
path.closePath();
|
||||
path.trimToSize();
|
||||
pathStarted = false;
|
||||
|
||||
fillShape(path);
|
||||
drawShape(path);
|
||||
}
|
||||
*/
|
||||
|
||||
public void image( String imageSource, double x, double y ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, default_anchor);
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, double scale ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, scale, default_anchor);
|
||||
}
|
||||
|
||||
public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
|
||||
image(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y ) {
|
||||
image(image, x, y, 1.0, default_anchor);
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y, double scale ) {
|
||||
image(image, x, y, scale, default_anchor);
|
||||
}
|
||||
|
||||
public void image( Image image, double x, double y, double scale, Options.Direction anchor ) {
|
||||
if( image != null ) {
|
||||
double neww = image.getWidth(null) * scale;
|
||||
double newh = image.getHeight(null) * scale;
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y, neww, newh, anchor);
|
||||
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void text( String text, double x, double y ) {
|
||||
text(text, x, y, default_anchor);
|
||||
}
|
||||
|
||||
public void text( String text, double x, double y, Options.Direction anchor ) {
|
||||
FontMetrics fm = drawing.getFontMetrics();
|
||||
Point2D.Double anchorPoint = getAnchorPoint(x, y + fm.getAscent(), fm.stringWidth(text), fm.getHeight(), anchor);
|
||||
|
||||
drawing.drawString(text, (float) anchorPoint.x, (float) anchorPoint.y);
|
||||
}
|
||||
|
||||
public void setFontSize( int size ) {
|
||||
setFont(drawing.getFont().deriveFont((float)size));
|
||||
}
|
||||
|
||||
public void setFont( String fontName ) {
|
||||
Font font = new Font(fontName, drawing.getFont().getStyle(), drawing.getFont().getSize());
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public void setFont( String fontName, int size ) {
|
||||
Font font = new Font(fontName, drawing.getFont().getStyle(), size);
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public void setFont( String fontName, int size, int style ) {
|
||||
Font font = new Font(fontName, style, size);
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public void setFont( Font font ) {
|
||||
drawing.setFont(font);
|
||||
fontMetrics = drawing.getFontMetrics();
|
||||
}
|
||||
|
||||
|
||||
private Point2D.Double transformToCanvas( double x, double y ) {
|
||||
return transformToCanvas(new Point2D.Double(x,y));
|
||||
}
|
||||
|
||||
private Point2D.Double transformToCanvas( Point2D.Double pPoint ) {
|
||||
AffineTransform matrix = getMatrix();
|
||||
matrix.transform(pPoint, pPoint);
|
||||
return pPoint;
|
||||
}
|
||||
|
||||
private Point2D.Double transformToUser( double x, double y ) {
|
||||
return transformToUser(new Point2D.Double(x,y));
|
||||
}
|
||||
|
||||
private Point2D.Double transformToUser( Point2D.Double pPoint ) {
|
||||
AffineTransform matrix = getMatrix();
|
||||
|
||||
try {
|
||||
matrix.inverseTransform(pPoint, pPoint);
|
||||
} catch( NoninvertibleTransformException e ) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return pPoint;
|
||||
}
|
||||
|
||||
private Point2D.Double getAnchorPoint( double x, double y, double w, double h, Options.Direction anchor ) {
|
||||
double whalf = w * .5, hhalf = h * .5;
|
||||
|
||||
// anchor == CENTER
|
||||
Point2D.Double anchorPoint = new Point2D.Double(x - whalf, y - hhalf);
|
||||
|
||||
if( NORTH.in(anchor) ) {
|
||||
anchorPoint.y += hhalf;
|
||||
}
|
||||
if( SOUTH.in(anchor) ) {
|
||||
anchorPoint.y -= hhalf;
|
||||
}
|
||||
if( WEST.in(anchor) ) {
|
||||
anchorPoint.x += whalf;
|
||||
}
|
||||
if( EAST.in(anchor) ) {
|
||||
anchorPoint.x -= whalf;
|
||||
}
|
||||
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
private void fillShape( Shape pShape ) {
|
||||
if( fillColor != null && fillColor.getAlpha() > 0.0 ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
drawing.fill(pShape);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawShape( Shape pShape ) {
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0.0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.draw(pShape);
|
||||
}
|
||||
}
|
||||
|
||||
public void translate( double dx, double dy ) {
|
||||
drawing.translate(dx, dy);
|
||||
}
|
||||
|
||||
public void scale( double factor ) {
|
||||
drawing.scale(factor, factor);
|
||||
}
|
||||
|
||||
public void rotate( double pAngle ) {
|
||||
drawing.rotate(Math.toRadians(pAngle));
|
||||
}
|
||||
|
||||
public void shear( double dx, double dy ) {
|
||||
drawing.shear(dx, dy);
|
||||
}
|
||||
|
||||
public AffineTransform getMatrix() {
|
||||
return drawing.getTransform();
|
||||
}
|
||||
|
||||
public void pushMatrix() {
|
||||
transformStack.push(drawing.getTransform());
|
||||
}
|
||||
|
||||
public void popMatrix() {
|
||||
if( transformStack.isEmpty() ) {
|
||||
resetMatrix();
|
||||
} else {
|
||||
drawing.setTransform(transformStack.pop());
|
||||
}
|
||||
}
|
||||
|
||||
public void resetMatrix() {
|
||||
drawing.setTransform(new AffineTransform());
|
||||
}
|
||||
|
||||
}
|
||||
279
src/main/java/schule/ngb/zm/Fillable.java
Normal file
@@ -0,0 +1,279 @@
|
||||
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,11 +0,0 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
public class GraphicsLayer extends Layer {
|
||||
|
||||
public Graphics2D getGraphics() {
|
||||
return drawing;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
|
||||
public class ImageLayer extends Layer {
|
||||
|
||||
protected Image image;
|
||||
|
||||
protected double x = 0;
|
||||
|
||||
protected double y = 0;
|
||||
|
||||
protected boolean redraw = true;
|
||||
|
||||
public ImageLayer(String source) {
|
||||
image = ImageLoader.loadImage(source);
|
||||
}
|
||||
|
||||
public ImageLayer(Image image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public ImageLayer(int width, int height, Image image) {
|
||||
super(width, height);
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image = image;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(double pX) {
|
||||
this.x = pX;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(double pY) {
|
||||
this.y = pY;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,33 +6,80 @@ 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(STD_WIDTH, STD_HEIGHT);
|
||||
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 ) {
|
||||
@@ -41,17 +88,26 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
} 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 Width des neuen Puffers.
|
||||
* @param width Breite des neuen Puffers.
|
||||
* @param height Höhe des neuen Puffers.
|
||||
*/
|
||||
private void createCanvas( int width, int height ) {
|
||||
@@ -78,7 +134,7 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
* 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 Width des neuen Puffers.
|
||||
* @param width Breite des neuen Puffers.
|
||||
* @param height Höhe des neuen Puffers.
|
||||
*/
|
||||
private void recreateCanvas( int width, int height ) {
|
||||
@@ -102,14 +158,14 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet den Puffer auf die Grafik-Instanz.
|
||||
* Zeichnet den Puffer auf den Grafikkontext.
|
||||
*
|
||||
* @param pGraphics
|
||||
* @param graphics Der Grafikkontext, auf den gezeichnet wird.
|
||||
*/
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( visible ) {
|
||||
pGraphics.drawImage(buffer, 0, 0, null);
|
||||
graphics.drawImage(buffer, 0, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,14 +174,26 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versteckt die Ebene.
|
||||
*/
|
||||
public void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
public void view() {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -139,4 +207,22 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,17 +11,71 @@ public final class Options {
|
||||
private Options() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Linienstile für Konturlinien.
|
||||
*/
|
||||
public enum StrokeType {
|
||||
SOLID, DASHED, DOTTED
|
||||
/**
|
||||
* Durchgezogene Linien.
|
||||
*/
|
||||
SOLID,
|
||||
|
||||
/**
|
||||
* Getrichelte Linien.
|
||||
*/
|
||||
DASHED,
|
||||
|
||||
/**
|
||||
* Gepunktete Linien.
|
||||
*/
|
||||
DOTTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Stile für Pfeilspitzen.
|
||||
*/
|
||||
public enum ArrowHead {
|
||||
LINES, FILLED
|
||||
/**
|
||||
* 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 {
|
||||
OPEN(Arc2D.OPEN), CLOSED(Arc2D.CHORD), PIE(Arc2D.PIE);
|
||||
/**
|
||||
* 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 ) {
|
||||
@@ -33,22 +87,100 @@ public final class Options {
|
||||
* 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,
|
||||
UPDATING,
|
||||
DRAWING,
|
||||
|
||||
/**
|
||||
* 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,
|
||||
IDLE
|
||||
|
||||
/**
|
||||
* 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 repräsentiert, wodurch mit ihnen
|
||||
* gerechnet werden kann. Jede Richtung ist zusätzlich als Himmelsrichtung
|
||||
* definiert.
|
||||
* 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),
|
||||
@@ -86,30 +218,116 @@ public final class Options {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die entgegengesetzte Richtung zu dieser zurück.
|
||||
* 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
|
||||
* @return Die entgegengesetzte Richtung zu dieser.
|
||||
*/
|
||||
public Direction inverse() {
|
||||
for( Direction dir : Direction.values() ) {
|
||||
if( dir.x == -this.x && dir.y == -this.y ) {
|
||||
return dir;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.util.LinkedList;
|
||||
import schule.ngb.zm.layers.DrawableLayer;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Spielemaschine extends Zeichenmaschine {
|
||||
|
||||
private LinkedList<Drawable> drawables;
|
||||
|
||||
private LinkedList<Updatable> updatables;
|
||||
|
||||
private GraphicsLayer mainLayer;
|
||||
private GameLayer mainLayer;
|
||||
|
||||
public Spielemaschine( String title ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title);
|
||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +28,7 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
drawables = new LinkedList<>();
|
||||
updatables = new LinkedList<>();
|
||||
|
||||
mainLayer = new GraphicsLayer();
|
||||
mainLayer = new GameLayer();
|
||||
canvas.addLayer(mainLayer);
|
||||
}
|
||||
|
||||
@@ -79,9 +85,10 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
@Override
|
||||
public final void update( double delta ) {
|
||||
synchronized( updatables ) {
|
||||
for( Updatable updatable : updatables ) {
|
||||
if( updatable.isActive() ) {
|
||||
updatable.update(delta);
|
||||
List<Updatable> it = List.copyOf(updatables);
|
||||
for( Updatable u: it ) {
|
||||
if( u.isActive() ) {
|
||||
u.update(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,14 +98,27 @@ public class Spielemaschine extends Zeichenmaschine {
|
||||
|
||||
@Override
|
||||
public final void draw() {
|
||||
mainLayer.clear();
|
||||
synchronized( drawables ) {
|
||||
for( Drawable drawable : drawables ) {
|
||||
if( drawable.isVisible() ) {
|
||||
drawable.draw(mainLayer.getGraphics());
|
||||
|
||||
}
|
||||
|
||||
private class GameLayer extends Layer {
|
||||
|
||||
public Graphics2D getGraphics() {
|
||||
return drawing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics ) {
|
||||
clear();
|
||||
List<Drawable> it = List.copyOf(drawables);
|
||||
for( Drawable d: it ) {
|
||||
if( d.isVisible() ) {
|
||||
d.draw(drawing);
|
||||
}
|
||||
}
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
229
src/main/java/schule/ngb/zm/Strokeable.java
Normal file
@@ -0,0 +1,229 @@
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsmethode, um ein {@link Stroke} Objekt mit den aktuellen
|
||||
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
||||
* zwischengespeichert.
|
||||
*
|
||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||
*/
|
||||
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight ) {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||
case DASHED:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{5.0f}, 0.0f);
|
||||
case SOLID:
|
||||
default:
|
||||
return new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +1,44 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
/**
|
||||
* Aktualisierbare Objekte können in regelmäßigen Intervallen (meist einmal
|
||||
* pro Frame) ihren Zustand update. Diese Änderung kann abhängig vom
|
||||
* {@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.
|
||||
* @return {@code true}, wenn das Objekt aktiv ist.
|
||||
* <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.
|
||||
*/
|
||||
public boolean isActive();
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* Änderung des Zustandes des Objekts abhängig vom Zeitintervall
|
||||
* <var>delta</var> in Sekunden.
|
||||
* {@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).
|
||||
*/
|
||||
public void update( double delta );
|
||||
void update( double delta );
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.awt.geom.Point2D;
|
||||
* Der Vektor der Zeichenmaschine erweitert die Klasse {@link Point2D} und lässt
|
||||
* sich dadurch einfach mit den Klassen des {@link java.awt} Pakets benutzen.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
@@ -127,7 +128,7 @@ public class Vector extends Point2D.Double {
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Vektor mit derselben Richtun wie der angegebene
|
||||
* Erzeugt einen neuen Vektor mit derselben Richtung wie der angegebene
|
||||
* Vektor und der Länge 1.
|
||||
*
|
||||
* @param vector Der original Vektor.
|
||||
@@ -197,6 +198,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param y Der neue y-Wert.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector set( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
@@ -210,6 +212,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param vector Ein Vektor.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector set( Vector vector ) {
|
||||
x = vector.x;
|
||||
y = vector.y;
|
||||
@@ -223,6 +226,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param pPunkt Ein Punkt.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector set( Point2D pPunkt ) {
|
||||
x = pPunkt.getX();
|
||||
x = pPunkt.getY();
|
||||
@@ -271,6 +275,7 @@ public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
* Legt die Länge des Vektors fest.
|
||||
*
|
||||
* @param length Die neue Länge des Vektors.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@@ -294,9 +299,9 @@ public class Vector extends Point2D.Double {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector normalize() {
|
||||
double len = length();
|
||||
if( len != 0 && len != 1 ) {
|
||||
@@ -308,9 +313,11 @@ public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
* Addiert den Vektor {@code vector} zu diesem.
|
||||
*
|
||||
* @param vector Ein anderer Vektor.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector add( Vector vector ) {
|
||||
x += vector.x;
|
||||
y += vector.y;
|
||||
@@ -319,10 +326,12 @@ public class Vector extends Point2D.Double {
|
||||
|
||||
/**
|
||||
* Addiert die angegebenen Werte zur x- und y-Komponente des Vektors.
|
||||
*
|
||||
* @param x Summand x-Komponente.
|
||||
* @param y Summand y-Komponente.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector add( double x, double y ) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
@@ -342,24 +351,28 @@ public class Vector extends Point2D.Double {
|
||||
return vec;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector sub( Vector vector ) {
|
||||
x -= vector.x;
|
||||
y -= vector.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector sub( double x, double y ) {
|
||||
this.x -= x;
|
||||
this.y -= y;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector scale( double scalar ) {
|
||||
x *= scalar;
|
||||
y *= scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector div( double scalar ) {
|
||||
if( scalar == 0.0 ) {
|
||||
throw new IllegalArgumentException("Can't divide by zero.");
|
||||
@@ -409,8 +422,8 @@ public class Vector extends Point2D.Double {
|
||||
* dem quadrierten Abstand durchführen, wenn auch die gewünschte Entfernung
|
||||
* quadriert wird.
|
||||
*
|
||||
* @param vector
|
||||
* @return
|
||||
* @param vector Ein anderer Vektor.
|
||||
* @return Das Quadrat der Entfernung zum anderen Vektor.
|
||||
*/
|
||||
public double distanceSq( Vector vector ) {
|
||||
return super.distanceSq(vector);
|
||||
@@ -487,6 +500,7 @@ public class Vector extends Point2D.Double {
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #setLength(double)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector limit( double max ) {
|
||||
if( lengthSq() > max * max ) {
|
||||
setLength(max);
|
||||
@@ -503,6 +517,7 @@ public class Vector extends Point2D.Double {
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #setLength(double)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector limit( double min, double max ) {
|
||||
if( min > max ) {
|
||||
double t = min;
|
||||
@@ -556,6 +571,7 @@ public class Vector extends Point2D.Double {
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
* @see #rotate(double)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector rotateRad( double rad ) {
|
||||
double temp = x;
|
||||
x = x * Math.cos(rad) - y * Math.sin(rad);
|
||||
@@ -587,6 +603,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector morph( Vector vector, double t ) {
|
||||
double tt = Math.min(Math.max(t, 0.0), 1.0);
|
||||
x = x + (vector.x - x) * tt;
|
||||
@@ -631,6 +648,7 @@ public class Vector extends Point2D.Double {
|
||||
* @param t Ein Wert zwischen 0 und 1.
|
||||
* @return Dieser Vektor selbst (method chaining)
|
||||
*/
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public Vector interpolate( Vector vector, double t ) {
|
||||
x = x + (vector.x - x) * t;
|
||||
y = y + (vector.y - y) * t;
|
||||
|
||||
@@ -1,6 +1,363 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
// TODO: Auslagern des Frames in diese Klasse (Trennung Swing-GUI/Canvas, Zeichenmaschine und Drawing-Thread)
|
||||
public class Zeichenfenster {
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
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.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 erstyen 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).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 ) {
|
||||
URL iconUrl = Zeichenmaschine.class.getResource("icon_512.png");
|
||||
if( iconUrl != null ) {
|
||||
Image icon = ImageIO.read(iconUrl);
|
||||
// Dock Icon in macOS setzen
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
taskbar.setIconImage(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));
|
||||
}
|
||||
}
|
||||
|
||||
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,12 +1,16 @@
|
||||
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
|
||||
@@ -20,10 +24,14 @@ import java.util.LinkedList;
|
||||
*/
|
||||
public class Zeichenleinwand extends Canvas {
|
||||
|
||||
/**
|
||||
* Liste der hinzugefügten Ebenen.
|
||||
*/
|
||||
private LinkedList<Layer> layers;
|
||||
// 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.
|
||||
@@ -33,17 +41,55 @@ public class Zeichenleinwand extends Canvas {
|
||||
*/
|
||||
public Zeichenleinwand( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
this.setPreferredSize(this.getSize());
|
||||
this.setMinimumSize(this.getSize());
|
||||
this.setPreferredSize(getSize());
|
||||
this.setMinimumSize(getSize());
|
||||
this.setBackground(Constants.DEFAULT_BACKGROUND.getJavaColor());
|
||||
|
||||
// Liste der Ebenen initialisieren und die Standardebenen einfügen
|
||||
layers = new LinkedList<>();
|
||||
synchronized( layers ) {
|
||||
layers.add(new ColorLayer(width, height, Constants.DEFAULT_BACKGROUND));
|
||||
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>
|
||||
@@ -56,8 +102,8 @@ public class Zeichenleinwand extends Canvas {
|
||||
@Override
|
||||
public void setSize( int width, int height ) {
|
||||
super.setSize(width, height);
|
||||
this.setPreferredSize(this.getSize());
|
||||
this.setMinimumSize(this.getSize());
|
||||
this.setPreferredSize(getSize());
|
||||
this.setMinimumSize(getSize());
|
||||
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
@@ -74,32 +120,28 @@ public class Zeichenleinwand extends Canvas {
|
||||
*/
|
||||
public void addLayer( Layer layer ) {
|
||||
if( layer != null ) {
|
||||
synchronized( layers ) {
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
layers.add(layer);
|
||||
}
|
||||
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
|
||||
* {@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 i Index der Ebene beginnend mit 0.
|
||||
* @param layer Die neue Ebene.
|
||||
*/
|
||||
public void addLayer( int i, Layer layer ) {
|
||||
if( layer != null ) {
|
||||
synchronized( layers ) {
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
if( i > layers.size() ) {
|
||||
layers.add(layer);
|
||||
} else {
|
||||
layers.add(i, layer);
|
||||
}
|
||||
layer.setSize(getWidth(), getHeight());
|
||||
if( i > layers.size() ) {
|
||||
layers.add(layer);
|
||||
} else {
|
||||
layers.add(i, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,19 +156,19 @@ public class Zeichenleinwand extends Canvas {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Liste der bisher hinzugefügten Ebenen zurück.
|
||||
* Gibt eine Kopie der Liste der bisher hinzugefügten Ebenen zurück.
|
||||
*
|
||||
* @return Liste der Ebenen.
|
||||
*/
|
||||
public java.util.List<Layer> getLayers() {
|
||||
return layers;
|
||||
public List<Layer> getLayers() {
|
||||
return List.copyOf(layers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt die Ebene am Index <var>i</var> (beginnend bei 0).
|
||||
* Holt die Ebene am Index {@code i} (beginnend bei 0).
|
||||
*
|
||||
* @param i Index der Ebene (beginnend bei 0).
|
||||
* @return Die Ebene am Index <var>i</var> oder {@code null}.
|
||||
* @return Die Ebene am Index {@code i} oder {@code null}.
|
||||
* @throws IndexOutOfBoundsException Falls der Index nicht existiert.
|
||||
*/
|
||||
public Layer getLayer( int i ) {
|
||||
@@ -141,14 +183,16 @@ public class Zeichenleinwand extends Canvas {
|
||||
* Sucht die erste Ebene des angegebenen Typs aus der Liste der Ebenen.
|
||||
* Existiert keine solche Ebene, wird {@code null} zurückgegeben.
|
||||
*
|
||||
* @param clazz Typ der Ebene.
|
||||
* @param <L>
|
||||
* @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> clazz ) {
|
||||
for( Layer layer : layers ) {
|
||||
if( layer.getClass().equals(clazz) ) {
|
||||
return clazz.cast(layer);
|
||||
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;
|
||||
@@ -159,43 +203,67 @@ public class Zeichenleinwand extends Canvas {
|
||||
* gibt diese als Liste zurück. Die Reihenfolge in der Liste entspricht der
|
||||
* Reihenfolge der Ebenen in der Leinwand (von unten nach oben).
|
||||
*
|
||||
* @param pClazz
|
||||
* @param <L>
|
||||
* @return
|
||||
* @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.
|
||||
*/
|
||||
public <L extends Layer> java.util.List<L> getLayers( Class<L> pClazz ) {
|
||||
@SuppressWarnings( "unused" )
|
||||
public <L extends Layer> List<L> getLayers( Class<L> type ) {
|
||||
ArrayList<L> result = new ArrayList<>(layers.size());
|
||||
for( Layer layer : layers ) {
|
||||
if( layer.getClass().equals(pClazz) ) {
|
||||
result.add(pClazz.cast(layer));
|
||||
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 ) {
|
||||
synchronized( layers ) {
|
||||
return layers.remove(pLayer);
|
||||
}
|
||||
return layers.remove(pLayer);
|
||||
}
|
||||
|
||||
public void removeLayers( Layer... pLayers ) {
|
||||
/**
|
||||
* 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 : pLayers ) {
|
||||
for( Layer layer : removeLayers ) {
|
||||
layers.remove(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt alle vorhandenen Ebenen von dieser Zeichenleinwand.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public void clearLayers() {
|
||||
synchronized( layers ) {
|
||||
layers.clear();
|
||||
}
|
||||
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 : layers ) {
|
||||
for( Layer layer : List.copyOf(layers) ) {
|
||||
layer.update(delta);
|
||||
}
|
||||
}
|
||||
@@ -225,9 +293,9 @@ public class Zeichenleinwand extends Canvas {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafik-Kontext.
|
||||
* Zeichnet den Inhalt aller {@link Layer Ebenen} in den Grafikkontext.
|
||||
*
|
||||
* @param graphics
|
||||
* @param graphics Der Grafikkontext.
|
||||
*/
|
||||
public void draw( Graphics graphics ) {
|
||||
Graphics2D g2d = (Graphics2D) graphics.create();
|
||||
@@ -243,38 +311,45 @@ public class Zeichenleinwand extends Canvas {
|
||||
* Zeigt den aktuellen Inhalt der Zeichenleinwand an.
|
||||
*/
|
||||
public void render() {
|
||||
if( getBufferStrategy() == null ) {
|
||||
allocateBuffer();
|
||||
}
|
||||
if( !suspended && isDisplayable() ) {
|
||||
if( getBufferStrategy() == null ) {
|
||||
allocateBuffer();
|
||||
}
|
||||
|
||||
if( isDisplayable() ) {
|
||||
BufferStrategy strategy = this.getBufferStrategy();
|
||||
if( strategy != null ) {
|
||||
do {
|
||||
synchronized( renderLock ) {
|
||||
rendering = true;
|
||||
BufferStrategy strategy = this.getBufferStrategy();
|
||||
if( strategy != null ) {
|
||||
do {
|
||||
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
|
||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||
do {
|
||||
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
|
||||
g2d.clearRect(0, 0, getWidth(), getHeight());
|
||||
|
||||
synchronized( layers ) {
|
||||
for( Layer layer : layers ) {
|
||||
layer.draw(g2d);
|
||||
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();
|
||||
}
|
||||
|
||||
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() );
|
||||
// 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,12 +1,14 @@
|
||||
package schule.ngb.zm;
|
||||
|
||||
import schule.ngb.zm.anim.Animation;
|
||||
import schule.ngb.zm.shapes.ShapesLayer;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.layers.ColorLayer;
|
||||
import schule.ngb.zm.layers.DrawingLayer;
|
||||
import schule.ngb.zm.layers.ImageLayer;
|
||||
import schule.ngb.zm.layers.ShapesLayer;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.MouseInputListener;
|
||||
import java.awt.*;
|
||||
@@ -15,7 +17,7 @@ import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Hauptklasse der Zeichenmaschine.
|
||||
@@ -24,7 +26,7 @@ import java.util.logging.Level;
|
||||
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der
|
||||
* nötigen Komponenten.
|
||||
*/
|
||||
// TODO: Refactorings (besonders in Bezug auf Nebenläufigkeit)
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Zeichenmaschine extends Constants {
|
||||
|
||||
/**
|
||||
@@ -36,43 +38,6 @@ public class Zeichenmaschine extends Constants {
|
||||
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter macOS gestartet wurde.
|
||||
*/
|
||||
public static final boolean MACOS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Windows gestartet wurde.
|
||||
*/
|
||||
public static final boolean WINDOWS;
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Zeichenmaschine unter Linux gestartet wurde.
|
||||
*/
|
||||
public static final boolean LINUX;
|
||||
|
||||
static {
|
||||
final String name = System.getProperty("os.name");
|
||||
|
||||
if( name.contains("Mac") ) {
|
||||
MACOS = true;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
} else if( name.contains("Windows") ) {
|
||||
MACOS = false;
|
||||
WINDOWS = true;
|
||||
LINUX = false;
|
||||
} else if( name.equals("Linux") ) { // true for the ibm vm
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = true;
|
||||
} else {
|
||||
MACOS = false;
|
||||
WINDOWS = false;
|
||||
LINUX = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Objektvariablen, die von Unterklassen benutzt werden können.
|
||||
*/
|
||||
@@ -110,54 +75,14 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Das Zeichenfenster der Zeichenmaschine
|
||||
*/
|
||||
private JFrame frame;
|
||||
|
||||
/**
|
||||
* Die Graphics-Umgebung für das aktuelle Fenster.
|
||||
*/
|
||||
private GraphicsEnvironment environment;
|
||||
|
||||
/**
|
||||
* Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich
|
||||
* dem Aktuellen sein, wenn das Fenster verschoben wurde).
|
||||
*/
|
||||
private GraphicsDevice displayDevice;
|
||||
|
||||
/**
|
||||
* Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)}
|
||||
* in den Vollbildmodus versetzt wurde.
|
||||
*/
|
||||
private boolean fullscreen = false;
|
||||
|
||||
/**
|
||||
* Höhe und Breite der Zeichenmaschine, bevor sie mit
|
||||
* {@link #setFullscreen(boolean)} in den Vollbild-Modus versetzt wurde.
|
||||
* Wird verwendet, um die Fenstergröße wiederherzustellen, sobald der
|
||||
* Vollbild-Modus verlassen wird.
|
||||
*/
|
||||
private int initialWidth, initialHeight;
|
||||
|
||||
/**
|
||||
* {@code KeyListener}, um den Vollbild-Modus mit der Escape-Taste zu
|
||||
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
|
||||
* hinzugefügt und entfernt.
|
||||
*/
|
||||
private KeyListener fullscreenExitListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed( KeyEvent e ) {
|
||||
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
|
||||
// canvas.removeKeyListener(this);
|
||||
setFullscreen(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
protected Zeichenfenster frame;
|
||||
|
||||
// Aktueller Zustand der Zeichenmaschine.
|
||||
|
||||
/**
|
||||
* Zustand der Zeichenmaschine insgesamt
|
||||
*/
|
||||
private Options.AppState state = Options.AppState.INITIALIZING;
|
||||
private Options.AppState state;
|
||||
|
||||
/**
|
||||
* Zustand des update/draw Threads
|
||||
@@ -167,7 +92,9 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Ob der Zeichenthread noch laufen soll, oder beendet.
|
||||
*/
|
||||
private boolean running = false;
|
||||
private boolean running;
|
||||
|
||||
private boolean terminateImediately = false;
|
||||
|
||||
/**
|
||||
* Ob die ZM nach dem nächsten Frame pausiert werden soll.
|
||||
@@ -178,7 +105,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* Ob die ZM bei nicht überschriebener update() Methode stoppen soll,
|
||||
* oder trotzdem weiterläuft.
|
||||
*/
|
||||
private boolean run_once = true;
|
||||
private final boolean run_once;
|
||||
|
||||
/**
|
||||
* Aktuelle Frames pro Sekunde der Zeichenmaschine.
|
||||
@@ -188,23 +115,23 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Hauptthread der Zeichenmaschine.
|
||||
*/
|
||||
private Thread mainThread;
|
||||
private final Thread mainThread;
|
||||
|
||||
/**
|
||||
* Queue für geplante Aufgaben
|
||||
*/
|
||||
private DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
||||
private final DelayQueue<DelayedTask> taskQueue = new DelayQueue<>();
|
||||
|
||||
/**
|
||||
* Queue für abgefangene InputEvents
|
||||
*/
|
||||
private BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||
private final BlockingQueue<InputEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
/**
|
||||
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
|
||||
* oder das Zeichenfenster weiter geöffnet bleibt.
|
||||
*/
|
||||
private boolean quitAfterTeardown = false;
|
||||
private boolean quitAfterShutdown = false;
|
||||
|
||||
// Mauszeiger
|
||||
/**
|
||||
@@ -255,7 +182,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* @param title Der Titel, der oben im Fenster steht.
|
||||
*/
|
||||
public Zeichenmaschine( String title ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title, true);
|
||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,7 +197,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* Aufruf von {@code draw()}.
|
||||
*/
|
||||
public Zeichenmaschine( String title, boolean run_once ) {
|
||||
this(STD_WIDTH, STD_HEIGHT, title, run_once);
|
||||
this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, run_once);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,78 +238,41 @@ public class Zeichenmaschine extends Constants {
|
||||
* @param run_once {@code true} beendet die Zeichenmaschine nach einem
|
||||
* Aufruf von {@code draw()}.
|
||||
*/
|
||||
@SuppressWarnings("static-access")
|
||||
public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
|
||||
LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
|
||||
|
||||
// Setzen des "Look&Feel"
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch( Exception ex ) {
|
||||
LOG.log(Level.SEVERE, "Error setting the look and feel: " + ex.getMessage(), ex);
|
||||
// Register Cmd+Q on macOS
|
||||
if( Constants.MACOS ) {
|
||||
System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
|
||||
}
|
||||
|
||||
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
|
||||
// das Zeichenfenster dort zu zentrieren.
|
||||
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
|
||||
environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice[] devices = environment.getScreenDevices();
|
||||
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();
|
||||
}
|
||||
|
||||
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
|
||||
this.canvasWidth = width;
|
||||
this.canvasHeight = height;
|
||||
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
|
||||
this.screenWidth = (int) displayBounds.getWidth();
|
||||
this.screenHeight = (int) displayBounds.getHeight();
|
||||
|
||||
// Erstellen des Zeichenfensters
|
||||
frame = new JFrame(displayDevice.getDefaultConfiguration());
|
||||
frame.setTitle(title);
|
||||
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
// Das Icon des Fensters ändern
|
||||
|
||||
try {
|
||||
// TODO: Add image sizes
|
||||
ImageIcon icon = new ImageIcon(ImageIO.read(new File("res/icon_64.png")));
|
||||
|
||||
if( MACOS ) {
|
||||
// Dock Icon in macOS setzen
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
taskbar.setIconImage(icon.getImage());
|
||||
} else {
|
||||
// Kleines Icon des Frames setzen
|
||||
frame.setIconImage(icon.getImage());
|
||||
}
|
||||
} catch( IOException e ) {
|
||||
}
|
||||
|
||||
|
||||
// Erstellen der Leinwand
|
||||
canvas = new Zeichenleinwand(width, height);
|
||||
frame.add(canvas);
|
||||
|
||||
// Die drei Standardebenen merken, für den einfachen Zugriff aus unterklassen.
|
||||
// Erstellen des Zeichenfensters
|
||||
frame = createFrame(canvas, title);
|
||||
|
||||
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
|
||||
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
||||
this.canvasWidth = canvasBounds.width;
|
||||
this.canvasHeight = canvasBounds.height;
|
||||
java.awt.Rectangle displayBounds = frame.getScreenBounds();
|
||||
this.screenWidth = displayBounds.width;
|
||||
this.screenHeight = displayBounds.height;
|
||||
|
||||
// Die drei Standardebenen merken, für den einfachen Zugriff aus Unterklassen.
|
||||
background = getBackgroundLayer();
|
||||
drawing = getDrawingLayer();
|
||||
shapes = getShapesLayer();
|
||||
|
||||
// FPS setzen
|
||||
framesPerSecondInternal = STD_FPS;
|
||||
framesPerSecondInternal = DEFAULT_FPS;
|
||||
this.run_once = run_once;
|
||||
|
||||
// Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen
|
||||
// verändert werden soll.
|
||||
// TODO: When to call settings?
|
||||
// TODO: (ngb) Wann sollte settings() aufgerufen werden?
|
||||
settings();
|
||||
|
||||
// Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören.
|
||||
@@ -392,31 +282,28 @@ public class Zeichenmaschine extends Constants {
|
||||
canvas.addMouseWheelListener(inputListener);
|
||||
canvas.addKeyListener(inputListener);
|
||||
|
||||
/*KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent( KeyEvent e ) {
|
||||
enqueueEvent(e);
|
||||
return false;
|
||||
}
|
||||
});*/
|
||||
|
||||
// Programm beenden, wenn Fenster geschlossen wird
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing( WindowEvent e ) {
|
||||
if( running ) {
|
||||
running = false;
|
||||
teardown();
|
||||
cleanup();
|
||||
}
|
||||
// Give the app a minimum amount of time to shut down
|
||||
// then kill it.
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch( InterruptedException ex ) {
|
||||
} finally {
|
||||
if( isTerminated() ) {
|
||||
quit(true);
|
||||
} else {
|
||||
exitNow();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fenster zusammenbauen, auf dem Bildschirm zentrieren ...
|
||||
frame.pack();
|
||||
frame.setResizable(false);
|
||||
centerFrame();
|
||||
// ... und anzeigen!
|
||||
// Fenster anzeigen
|
||||
frame.centerFrame();
|
||||
frame.setVisible(true);
|
||||
|
||||
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
|
||||
@@ -440,25 +327,28 @@ public class Zeichenmaschine extends Constants {
|
||||
*
|
||||
* @param title
|
||||
*/
|
||||
// TODO: Implement in conjunction with Zeichenfenster
|
||||
private final Zeichenfenster createFrame( String title ) {
|
||||
return null;
|
||||
private Zeichenfenster createFrame( Zeichenleinwand c, String title ) {
|
||||
while( frame == null ) {
|
||||
try {
|
||||
TaskRunner.invokeLater(() -> {
|
||||
Zeichenfenster.setLookAndFeel();
|
||||
frame = new Zeichenfenster(canvas, title);
|
||||
}).get();
|
||||
} catch( InterruptedException e ) {
|
||||
// Keep waiting
|
||||
} catch( ExecutionException e ) {
|
||||
LOG.error(e, "Error initializing application frame: %s", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
|
||||
*/
|
||||
public final void centerFrame() {
|
||||
// TODO: Center on current display (not main display by default)
|
||||
// TODO: Position at current BlueJ windows if IN_BLUEJ
|
||||
//frame.setLocationRelativeTo(null);
|
||||
//frame.setLocationRelativeTo(displayDevice.getFullScreenWindow());
|
||||
|
||||
java.awt.Rectangle bounds = displayDevice.getDefaultConfiguration().getBounds();
|
||||
frame.setLocation(
|
||||
(int) (bounds.x + (screenWidth - frame.getWidth()) / 2.0),
|
||||
(int) (bounds.y + (screenHeight - frame.getHeight()) / 2.0)
|
||||
);
|
||||
frame.centerFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -475,37 +365,33 @@ public class Zeichenmaschine extends Constants {
|
||||
* ansonsten deaktiviert.
|
||||
*/
|
||||
public final void setFullscreen( boolean pEnable ) {
|
||||
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
|
||||
if( displayDevice.isFullScreenSupported() ) {
|
||||
if( pEnable && !fullscreen ) {
|
||||
// frame.setUndecorated(true);
|
||||
frame.setResizable(false); // Should be set anyway
|
||||
displayDevice.setFullScreenWindow(frame);
|
||||
// Update width / height
|
||||
initialWidth = canvasWidth;
|
||||
initialHeight = canvasHeight;
|
||||
changeSize(screenWidth, screenHeight);
|
||||
// Register ESC as exit fullscreen
|
||||
canvas.addKeyListener(fullscreenExitListener);
|
||||
if( pEnable && !frame.isFullscreen() ) {
|
||||
frame.setFullscreen(true);
|
||||
|
||||
fullscreen = true;
|
||||
fullscreenChanged();
|
||||
} else if( !pEnable && fullscreen ) {
|
||||
fullscreen = false;
|
||||
canvasWidth = canvas.getWidth();
|
||||
canvasHeight = canvas.getHeight();
|
||||
|
||||
canvas.removeKeyListener(fullscreenExitListener);
|
||||
displayDevice.setFullScreenWindow(null);
|
||||
changeSize(initialWidth, initialHeight);
|
||||
frame.pack();
|
||||
// frame.setUndecorated(false);
|
||||
if( frame.isFullscreen() )
|
||||
fullscreenChanged();
|
||||
} else if( !pEnable && frame.isFullscreen() ) {
|
||||
frame.setFullscreen(false);
|
||||
|
||||
canvasWidth = canvas.getWidth();
|
||||
canvasHeight = canvas.getHeight();
|
||||
|
||||
if( !frame.isFullscreen() )
|
||||
fullscreenChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob das Zeichenfenster im Vollbild läuft.
|
||||
*
|
||||
* @return {@code true}, wenn sich das Fesnter im Vollbildmodus befindet,
|
||||
* {@code false} sonst.
|
||||
*/
|
||||
public boolean isFullscreen() {
|
||||
Window win = displayDevice.getFullScreenWindow();
|
||||
return fullscreen && win != null;
|
||||
return frame.isFullscreen();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -541,9 +427,18 @@ public class Zeichenmaschine extends Constants {
|
||||
* im Zeichenfenster an.
|
||||
*/
|
||||
public final void redraw() {
|
||||
if( state == Options.AppState.PAUSED ) {
|
||||
if( state == Options.AppState.PAUSED
|
||||
|| state == Options.AppState.TERMINATED ) {
|
||||
draw();
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt den aktuellen Inhalt der {@link Zeichenleinwand}
|
||||
* im Zeichenfenster an, ohne vorher {@link #draw()} aufzurufen.
|
||||
*/
|
||||
public final void render() {
|
||||
canvas.render();
|
||||
// canvas.invalidate();
|
||||
// frame.repaint();
|
||||
@@ -583,14 +478,26 @@ public class Zeichenmaschine extends Constants {
|
||||
return state == Options.AppState.PAUSED;
|
||||
}
|
||||
|
||||
public final boolean isTerminated() {
|
||||
return state == Options.AppState.TERMINATED;
|
||||
}
|
||||
|
||||
public final boolean isTerminating() {
|
||||
return state == Options.AppState.STOPPED || state == Options.AppState.TERMINATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stoppt die Zeichenmaschine.
|
||||
* <p>
|
||||
* Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine
|
||||
* in den Zustand {@link Options.AppState#STOPPED} und ruft
|
||||
* {@link #teardown()} auf. Nachdem {@code teardown()} ausgeführt wurde
|
||||
* {@link #shutdown()} auf. Nachdem {@code teardown()} ausgeführt wurde
|
||||
* wechselt der Zustand zu {@link Options.AppState#TERMINATED}. Das
|
||||
* Zeichenfenster bleibt weiter geöffnet.
|
||||
* <p>
|
||||
* Die Zeichenmaschine reagiert in diesem Zustand weiter auf Eingaben,
|
||||
* allerdings muss die Zeichnung nun manuell mit {@link #redraw()}
|
||||
* aktualisiert werden.
|
||||
*/
|
||||
public final void stop() {
|
||||
running = false;
|
||||
@@ -601,7 +508,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* <p>
|
||||
* Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und
|
||||
* verbleibende Threads, Tasks, etc. zu stoppen und aufzuräumen. Die
|
||||
* Äquivalente Methode für Unterklassen ist {@link #teardown()}, die direkt
|
||||
* Äquivalente Methode für Unterklassen ist {@link #shutdown()}, die direkt
|
||||
* vor {@code cleanup()} aufgerufen wird.
|
||||
*/
|
||||
private void cleanup() {
|
||||
@@ -624,7 +531,23 @@ public class Zeichenmaschine extends Constants {
|
||||
public final void exit() {
|
||||
if( running ) {
|
||||
running = false;
|
||||
this.quitAfterTeardown = true;
|
||||
quitAfterShutdown = true;
|
||||
} else {
|
||||
quit(true);
|
||||
}
|
||||
}
|
||||
|
||||
public final void exitNow() {
|
||||
// Do nothing, when already quitting
|
||||
if( state == Options.AppState.QUITING ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( running ) {
|
||||
running = false;
|
||||
terminateImediately = true;
|
||||
quitAfterShutdown = true;
|
||||
mainThread.interrupt();
|
||||
} else {
|
||||
quit(true);
|
||||
}
|
||||
@@ -632,6 +555,10 @@ public class Zeichenmaschine extends Constants {
|
||||
|
||||
/**
|
||||
* Beendet das Programm vollständig.
|
||||
* <p>
|
||||
* Enspricht dem Aufruf {@code quit(true)}.
|
||||
*
|
||||
* @see #quit(boolean)
|
||||
*/
|
||||
public final void quit() {
|
||||
//quit(!IN_BLUEJ);
|
||||
@@ -641,40 +568,29 @@ public class Zeichenmaschine extends Constants {
|
||||
/**
|
||||
* Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird
|
||||
* die komplette VM beendet.
|
||||
* <p>
|
||||
* Die Methode sorgt nicht für ein ordnungsgemäßes herunterfahren und
|
||||
* freigeben aller Ressourcen, da die Zeichenmaschine gegebenenfalls
|
||||
* geöffnet bleiben und weitere Aufgaben erfüllen soll. Aufrufende Methoden
|
||||
* sollten dies berücksichtigen.
|
||||
* <p>
|
||||
* Soll das Programm vollständig beendet werden, ist es ratsamer
|
||||
* {@link #exit()} zu verwenden.
|
||||
*
|
||||
* @param exit Ob die VM beendet werden soll.
|
||||
* @see System#exit(int)
|
||||
*/
|
||||
public final void quit( boolean exit ) {
|
||||
frame.setVisible(false);
|
||||
canvas.dispose();
|
||||
frame.dispose();
|
||||
state = Options.AppState.QUITING;
|
||||
TaskRunner.invokeLater(() -> {
|
||||
frame.setVisible(false);
|
||||
canvas.dispose();
|
||||
frame.dispose();
|
||||
|
||||
if( exit ) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode um die Größe der Zeichenfläche zu ändern.
|
||||
* <p>
|
||||
* Die Methode berücksichtigt nicht den Zustand des Fensters (z.B.
|
||||
* Vollbildmodus) und geht davon aus, dass die aufrufende Methode
|
||||
* sichergestellt hat, dass eine Änderung der Größe der Zeichenfläche
|
||||
* zulässig und sinnvoll ist.
|
||||
*
|
||||
* @param newWidth Neue Breite der Zeichenleinwand.
|
||||
* @param newHeight Neue Höhe der Zeichenleinwand.
|
||||
* @see #setSize(int, int)
|
||||
* @see #setFullscreen(boolean)
|
||||
*/
|
||||
private void changeSize( int newWidth, int newHeight ) {
|
||||
canvasWidth = Math.min(Math.max(newWidth, 100), screenWidth);
|
||||
canvasHeight = Math.min(Math.max(newHeight, 100), screenHeight);
|
||||
|
||||
if( canvas != null ) {
|
||||
canvas.setSize(canvasWidth, canvasHeight);
|
||||
}
|
||||
if( exit ) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -685,16 +601,11 @@ public class Zeichenmaschine extends Constants {
|
||||
* @see Zeichenleinwand#setSize(int, int)
|
||||
*/
|
||||
public final void setSize( int width, int height ) {
|
||||
if( fullscreen ) {
|
||||
initialWidth = Math.min(Math.max(width, 100), screenWidth);
|
||||
initialHeight = Math.min(Math.max(height, 100), screenHeight);
|
||||
setFullscreen(false);
|
||||
} else {
|
||||
changeSize(width, height);
|
||||
frame.setCanvasSize(width, height);
|
||||
|
||||
//frame.setSize(width, height);
|
||||
frame.pack();
|
||||
}
|
||||
java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
|
||||
canvasWidth = canvasBounds.width;
|
||||
canvasHeight = canvasBounds.height;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -768,9 +679,9 @@ public class Zeichenmaschine extends Constants {
|
||||
* Gibt die erste (unterste) {@link Layer Ebene} der angegebenen Klasse
|
||||
* zurück.
|
||||
*
|
||||
* <pre>
|
||||
* DrawingLayer draw = getLayer(DrawingLayer.class);
|
||||
* </pre>
|
||||
* <pre><code>
|
||||
* DrawingLayer draw = getLayer(DrawingLayer.class);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param layerClass
|
||||
* @param <LT>
|
||||
@@ -952,13 +863,15 @@ public class Zeichenmaschine extends Constants {
|
||||
return;
|
||||
}
|
||||
|
||||
if( state != Options.AppState.RUNNING ) {
|
||||
if( state == Options.AppState.INITIALIZING ||
|
||||
state == Options.AppState.INITIALIZED ) {
|
||||
LOG.warn("Don't use delay(int) from within settings() or setup().");
|
||||
return;
|
||||
}
|
||||
|
||||
long timer = 0L;
|
||||
if( updateState == Options.AppState.DRAWING ) {
|
||||
if( /*updateState == Options.AppState.DRAWING*/
|
||||
isTerminating() ) {
|
||||
// Falls gerade draw() ausgeführt wird, zeigen wir den aktuellen
|
||||
// Stand der Zeichnung auf der Leinwand an. Die Zeit für das
|
||||
// Rendern wird gemessen und von der Wartezeit abgezogen.
|
||||
@@ -967,6 +880,7 @@ public class Zeichenmaschine extends Constants {
|
||||
timer = System.nanoTime() - timer;
|
||||
}
|
||||
|
||||
Options.AppState oldState = updateState;
|
||||
try {
|
||||
int sub = (int) Math.ceil(timer / 1000000.0);
|
||||
|
||||
@@ -974,9 +888,12 @@ public class Zeichenmaschine extends Constants {
|
||||
return;
|
||||
}
|
||||
|
||||
updateState = Options.AppState.DELAYED;
|
||||
Thread.sleep(ms - sub, (int) (timer % 1000000L));
|
||||
} catch( InterruptedException ex ) {
|
||||
} catch( InterruptedException ignored ) {
|
||||
// Nothing
|
||||
} finally {
|
||||
updateState = oldState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,9 +947,9 @@ public class Zeichenmaschine extends Constants {
|
||||
* <p>
|
||||
* Die Konstanten der Klasse {@link Cursor} definieren 13 Standardzeiger,
|
||||
* die durch angabe der Nummer geladen werden können.
|
||||
* <pre>
|
||||
* setCursor(Cursor.HAND_CURSOR);
|
||||
* </pre>
|
||||
* <pre><code>
|
||||
* setCursor(Cursor.HAND_CURSOR);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param pPredefinedCursor Eine der Cursor-Konstanten.
|
||||
* @see Cursor
|
||||
@@ -1105,9 +1022,9 @@ public class Zeichenmaschine extends Constants {
|
||||
* {@code delta} wird in Sekunden angegeben. Um eine Form zum Beispiel um
|
||||
* {@code 50} Pixel pro Sekunde in {@code x}-Richtung zu bewegen, kann so
|
||||
* vorgegangen werden:
|
||||
* <pre>
|
||||
* <pre><code>
|
||||
* shape.move(50*delta, 0.0);
|
||||
* </pre>
|
||||
* </code></pre>
|
||||
*
|
||||
* @param delta
|
||||
*/
|
||||
@@ -1140,7 +1057,7 @@ public class Zeichenmaschine extends Constants {
|
||||
* Spiels oder der Abspann einer Animation angezeigt werden, oder mit
|
||||
* {@link #saveImage()} die erstellte Zeichnung abgespeichert werden.
|
||||
*/
|
||||
public void teardown() {
|
||||
public void shutdown() {
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
@@ -1175,39 +1092,44 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
/*
|
||||
* Mouse handling
|
||||
* Input handling
|
||||
*/
|
||||
private void enqueueEvent( InputEvent evt ) {
|
||||
eventQueue.add(evt);
|
||||
if( updateState != Options.AppState.DELAYED ) {
|
||||
eventQueue.add(evt);
|
||||
}
|
||||
|
||||
if( isPaused() ) {
|
||||
if( isPaused() || isTerminated() ) {
|
||||
if( MouseEvent.class.isInstance(evt) ) {
|
||||
saveMousePosition((MouseEvent)evt);
|
||||
}
|
||||
dispatchEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchEvents() {
|
||||
synchronized( eventQueue ) {
|
||||
while( !eventQueue.isEmpty() ) {
|
||||
InputEvent evt = eventQueue.poll();
|
||||
//synchronized( eventQueue ) {
|
||||
while( !eventQueue.isEmpty() ) {
|
||||
InputEvent evt = eventQueue.poll();
|
||||
|
||||
switch( evt.getID() ) {
|
||||
case KeyEvent.KEY_TYPED:
|
||||
case KeyEvent.KEY_PRESSED:
|
||||
case KeyEvent.KEY_RELEASED:
|
||||
handleKeyEvent((KeyEvent) evt);
|
||||
break;
|
||||
switch( evt.getID() ) {
|
||||
case KeyEvent.KEY_TYPED:
|
||||
case KeyEvent.KEY_PRESSED:
|
||||
case KeyEvent.KEY_RELEASED:
|
||||
handleKeyEvent((KeyEvent) evt);
|
||||
break;
|
||||
|
||||
case MouseEvent.MOUSE_CLICKED:
|
||||
case MouseEvent.MOUSE_PRESSED:
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
case MouseEvent.MOUSE_MOVED:
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
case MouseEvent.MOUSE_WHEEL:
|
||||
handleMouseEvent((MouseEvent) evt);
|
||||
break;
|
||||
}
|
||||
case MouseEvent.MOUSE_CLICKED:
|
||||
case MouseEvent.MOUSE_PRESSED:
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
case MouseEvent.MOUSE_MOVED:
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
case MouseEvent.MOUSE_WHEEL:
|
||||
handleMouseEvent((MouseEvent) evt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
private void handleKeyEvent( KeyEvent evt ) {
|
||||
@@ -1245,7 +1167,7 @@ public class Zeichenmaschine extends Constants {
|
||||
case MouseEvent.MOUSE_RELEASED:
|
||||
mousePressed = false;
|
||||
mouseButton = NOMOUSE;
|
||||
mousePressed(evt);
|
||||
mouseReleased(evt);
|
||||
break;
|
||||
case MouseEvent.MOUSE_DRAGGED:
|
||||
//saveMousePosition(evt);
|
||||
@@ -1376,9 +1298,9 @@ public class Zeichenmaschine extends Constants {
|
||||
* // Next frame has started
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* Die {@link schule.ngb.zm.tasks.FrameSynchronizedTask} implementiert eine
|
||||
* {@link schule.ngb.zm.tasks.Task}, die sich automatisch auf diese Wiese
|
||||
* mit dem Zeichenthread synchronisiert.
|
||||
* Die {@link schule.ngb.zm.util.tasks.FrameSynchronizedTask} implementiert
|
||||
* eine {@link schule.ngb.zm.util.tasks.Task}, die sich automatisch auf
|
||||
* diese Wiese mit dem Zeichenthread synchronisiert.
|
||||
*/
|
||||
public static final Object globalSyncLock = new Object[0];
|
||||
|
||||
@@ -1393,48 +1315,51 @@ public class Zeichenmaschine extends Constants {
|
||||
public final void run() {
|
||||
// Wait for full initialization before start
|
||||
while( state != Options.AppState.INITIALIZED ) {
|
||||
delay(1);
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
// ThreadExecutor for the update/draw Thread
|
||||
final UpdateThreadExecutor updateThreadExecutor = new UpdateThreadExecutor();
|
||||
|
||||
// start of thread in ms
|
||||
// Start des Thread in ms
|
||||
final long start = System.currentTimeMillis();
|
||||
// current time in ns
|
||||
long beforeTime = System.nanoTime();
|
||||
// Aktuelle Zeit in ns
|
||||
long beforeTime;
|
||||
long updateBeforeTime = System.nanoTime();
|
||||
// store for deltas
|
||||
// Speicher für Änderung
|
||||
long overslept = 0L;
|
||||
// internal counters for tick and runtime
|
||||
// Interne Zähler für tick und runtime
|
||||
int _tick = 0;
|
||||
long _runtime = 0;
|
||||
// public counters for access by subclasses
|
||||
long _runtime;
|
||||
// Öffentliche Zähler für Unterklassen
|
||||
tick = 0;
|
||||
runtime = 0;
|
||||
|
||||
// call setup of subclass and wait
|
||||
// setup() der Unterklasse aufrufen
|
||||
setup();
|
||||
|
||||
// Alles startklar ...
|
||||
state = Options.AppState.RUNNING;
|
||||
while( running ) {
|
||||
// delta in seconds
|
||||
// Aktuelle Zeit in ns merken
|
||||
beforeTime = System.nanoTime();
|
||||
|
||||
// Mausposition einmal pro Frame merken
|
||||
saveMousePosition(mouseEvent);
|
||||
|
||||
if( state != Options.AppState.PAUSED ) {
|
||||
//handleUpdate(delta);
|
||||
//handleDraw();
|
||||
|
||||
// Update and draw are executed in a new thread,
|
||||
// but we wait for them to finish unless the user
|
||||
// did call any blocking method, that would also block
|
||||
// rendering of new frames.
|
||||
// update() und draw() der Unterklasse werden in einem
|
||||
// eigenen Thread ausgeführt, aber der Zeichenthread
|
||||
// wartet, bis der Thread fertig ist. Außer die Unterklasse
|
||||
// ruft delay() auf und lässt den Thread eine länger Zeit
|
||||
// schlafen. Dann wird der nächst Frame vorzeitig gerendert,
|
||||
// bis der update-Thread wieder bereit ist. Dadurch können
|
||||
// nebenläufige Aufgaben (z.B. Animationen) weiterlaufen.
|
||||
if( !updateThreadExecutor.isRunning() ) {
|
||||
delta = (System.nanoTime() - updateBeforeTime) / 1000000000.0;
|
||||
updateBeforeTime = System.nanoTime();
|
||||
|
||||
// uddate()/draw() ausführen
|
||||
updateThreadExecutor.execute(() -> {
|
||||
if( state == Options.AppState.RUNNING
|
||||
&& updateState == Options.AppState.IDLE ) {
|
||||
@@ -1446,10 +1371,11 @@ public class Zeichenmaschine extends Constants {
|
||||
// Call to draw()
|
||||
updateState = Options.AppState.DRAWING;
|
||||
Zeichenmaschine.this.draw();
|
||||
updateState = Options.AppState.IDLE;
|
||||
updateState = Options.AppState.DISPATCHING;
|
||||
// Send latest input events after finishing draw
|
||||
// since these may also block
|
||||
dispatchEvents();
|
||||
updateState = Options.AppState.IDLE;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1458,16 +1384,21 @@ public class Zeichenmaschine extends Constants {
|
||||
while( updateThreadExecutor.isRunning()
|
||||
&& !updateThreadExecutor.isWaiting() ) {
|
||||
Thread.yield();
|
||||
|
||||
if( Thread.interrupted() ) {
|
||||
running = false;
|
||||
terminateImediately = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Display the current buffer content
|
||||
if( canvas != null ) {
|
||||
if( canvas != null && frame.isDisplayable() ) {
|
||||
canvas.render();
|
||||
// canvas.invalidate();
|
||||
// frame.repaint();
|
||||
}
|
||||
|
||||
|
||||
// dispatchEvents();
|
||||
}
|
||||
|
||||
@@ -1510,41 +1441,32 @@ public class Zeichenmaschine extends Constants {
|
||||
pause_pending = false;
|
||||
}
|
||||
}
|
||||
// Shutdown the updateThreads
|
||||
updateThreadExecutor.shutdownNow();
|
||||
state = Options.AppState.STOPPED;
|
||||
|
||||
// Cleanup
|
||||
teardown();
|
||||
cleanup();
|
||||
state = Options.AppState.TERMINATED;
|
||||
|
||||
if( quitAfterTeardown ) {
|
||||
quit();
|
||||
// Shutdown the updateThread
|
||||
while( !terminateImediately && updateThreadExecutor.isRunning() ) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
updateThreadExecutor.shutdownNow();
|
||||
try {
|
||||
updateThreadExecutor.awaitTermination(500, TimeUnit.MILLISECONDS);
|
||||
} catch( InterruptedException ex ) {
|
||||
|
||||
public void handleUpdate( double delta ) {
|
||||
if( state == Options.AppState.RUNNING ) {
|
||||
state = Options.AppState.UPDATING;
|
||||
update(delta);
|
||||
canvas.updateLayers(delta);
|
||||
state = Options.AppState.RUNNING;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Cleanup
|
||||
shutdown();
|
||||
cleanup();
|
||||
state = Options.AppState.TERMINATED;
|
||||
|
||||
public void handleDraw() {
|
||||
if( state == Options.AppState.RUNNING ) {
|
||||
state = Options.AppState.DRAWING;
|
||||
draw();
|
||||
state = Options.AppState.RUNNING;
|
||||
if( quitAfterShutdown ) {
|
||||
quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
class DelayedTask implements Delayed {
|
||||
static class DelayedTask implements Delayed {
|
||||
|
||||
long startTime; // in ms
|
||||
|
||||
@@ -1633,25 +1555,42 @@ public class Zeichenmaschine extends Constants {
|
||||
|
||||
}
|
||||
|
||||
// TODO: (ngb) exception handling when update/draw throws ex
|
||||
class UpdateThreadExecutor extends ThreadPoolExecutor {
|
||||
|
||||
private Thread updateThread;
|
||||
|
||||
private boolean running = false, waiting = false;
|
||||
private boolean running = false;
|
||||
|
||||
public UpdateThreadExecutor() {
|
||||
super(1, 1, 0L,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new ThreadFactory() {
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
@Override
|
||||
public Thread newThread( Runnable r ) {
|
||||
Thread t = new Thread(mainThread.getThreadGroup(), r,
|
||||
"updateThread-" + threadNumber.getAndIncrement(),
|
||||
0);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
updateState = Options.AppState.IDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( Runnable command ) {
|
||||
running = true;
|
||||
super.execute(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeExecute( Thread t, Runnable r ) {
|
||||
// We store the one Thread this Executor holds
|
||||
// We store the one Thread this Executor holds,
|
||||
// but it might change if a new Thread needed to be spawned
|
||||
// due to en error.
|
||||
updateThread = t;
|
||||
running = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1682,7 +1621,9 @@ public class Zeichenmaschine extends Constants {
|
||||
* @return
|
||||
*/
|
||||
public boolean isWaiting() {
|
||||
return running && updateThread.getState() == Thread.State.TIMED_WAITING;
|
||||
//return running && updateThread.getState() == Thread.State.TIMED_WAITING;
|
||||
//return running && updateThread != null && updateThread.getState() == Thread.State.TIMED_WAITING;
|
||||
return running && updateThread != null && updateState == Options.AppState.DELAYED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@ package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Updatable;
|
||||
import schule.ngb.zm.events.EventDispatcher;
|
||||
import schule.ngb.zm.tasks.FrameSynchronizedTask;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
import schule.ngb.zm.util.events.EventDispatcher;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public abstract class Animation<T> implements Updatable {
|
||||
public abstract class Animation<T> extends Constants implements Updatable {
|
||||
|
||||
protected int runtime;
|
||||
|
||||
protected int elapsed_time = 0;
|
||||
protected int elapsedTime = 0;
|
||||
|
||||
protected boolean running = false, finished = false;
|
||||
|
||||
@@ -25,7 +24,7 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public Animation( DoubleUnaryOperator easing ) {
|
||||
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
||||
this.easing = easing;
|
||||
this.easing = Validator.requireNotNull(easing);
|
||||
}
|
||||
|
||||
public Animation( int runtime ) {
|
||||
@@ -35,7 +34,7 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
||||
this.runtime = runtime;
|
||||
this.easing = easing;
|
||||
this.easing = Validator.requireNotNull(easing);
|
||||
}
|
||||
|
||||
public int getRuntime() {
|
||||
@@ -58,17 +57,17 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public final void start() {
|
||||
this.initialize();
|
||||
elapsed_time = 0;
|
||||
elapsedTime = 0;
|
||||
running = true;
|
||||
finished = false;
|
||||
interpolate(easing.applyAsDouble(0.0));
|
||||
animate(easing.applyAsDouble(0.0));
|
||||
initializeEventDispatcher().dispatchEvent("start", this);
|
||||
}
|
||||
|
||||
public final void stop() {
|
||||
running = false;
|
||||
// Make sure the last animation frame was interpolated correctly
|
||||
interpolate(easing.applyAsDouble((double) elapsed_time / (double) runtime));
|
||||
animate(easing.applyAsDouble((double) elapsedTime / (double) runtime));
|
||||
this.finish();
|
||||
finished = true;
|
||||
initializeEventDispatcher().dispatchEvent("stop", this);
|
||||
@@ -84,11 +83,7 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
public final void await() {
|
||||
while( !finished ) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch( InterruptedException ex ) {
|
||||
// Keep waiting
|
||||
}
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,16 +94,16 @@ public abstract class Animation<T> implements Updatable {
|
||||
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
elapsed_time += (int) (delta * 1000);
|
||||
if( elapsed_time > runtime )
|
||||
elapsed_time = runtime;
|
||||
elapsedTime += (int) (delta * 1000);
|
||||
if( elapsedTime > runtime )
|
||||
elapsedTime = runtime;
|
||||
|
||||
double t = (double) elapsed_time / (double) runtime;
|
||||
double t = (double) elapsedTime / (double) runtime;
|
||||
if( t >= 1.0 ) {
|
||||
running = false;
|
||||
stop();
|
||||
} else {
|
||||
interpolate(easing.applyAsDouble(t));
|
||||
animate(easing.applyAsDouble(t));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +118,10 @@ public abstract class Animation<T> implements Updatable {
|
||||
* e = Constants.limit(e, 0, 1);
|
||||
* </code></pre>
|
||||
*
|
||||
* @param e
|
||||
* @param e Fortschritt der Animation nachdem die Easingfunktion angewandt
|
||||
* wurde.
|
||||
*/
|
||||
public abstract void interpolate( double e );
|
||||
public abstract void animate( double e );
|
||||
|
||||
EventDispatcher<Animation, AnimationListener> eventDispatcher;
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ public class AnimationFacade<S> extends Animation<S> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
anim.interpolate(e);
|
||||
public void animate( double e ) {
|
||||
anim.animate(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,76 +1,116 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.shapes.Shape;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class AnimationGroup extends Animation<Shape> {
|
||||
@SuppressWarnings( "unused" )
|
||||
public class AnimationGroup<T> extends Animation<T> {
|
||||
|
||||
Animation<? extends Shape>[] anims;
|
||||
|
||||
private boolean overrideRuntime = false;
|
||||
List<Animation<T>> anims;
|
||||
|
||||
|
||||
public AnimationGroup( DoubleUnaryOperator easing, Animation<? extends Shape>... anims ) {
|
||||
super(easing);
|
||||
this.anims = anims;
|
||||
private boolean overrideEasing = false;
|
||||
|
||||
int maxRuntime = Arrays.stream(this.anims).mapToInt((a) -> a.getRuntime()).reduce(0, Integer::max);
|
||||
setRuntime(maxRuntime);
|
||||
private int overrideRuntime = -1;
|
||||
|
||||
private int lag = 0;
|
||||
|
||||
private int active = 0;
|
||||
|
||||
public AnimationGroup( Collection<Animation<T>> anims ) {
|
||||
this(0, -1, null, anims);
|
||||
}
|
||||
|
||||
public AnimationGroup( int runtime, DoubleUnaryOperator easing, Animation<? extends Shape>... anims ) {
|
||||
super(runtime, easing);
|
||||
this.anims = anims;
|
||||
overrideRuntime = true;
|
||||
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;
|
||||
}
|
||||
|
||||
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 Shape getAnimationTarget() {
|
||||
return null;
|
||||
public T getAnimationTarget() {
|
||||
for( Animation<T> anim : anims ) {
|
||||
if( anim.isActive() ) {
|
||||
return anim.getAnimationTarget();
|
||||
}
|
||||
}
|
||||
return anims.get(anims.size() - 1).getAnimationTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
if( overrideRuntime ) {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
if( anim.isActive() ) {
|
||||
anim.update(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();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.update(delta);
|
||||
running = false;
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
anim.interpolate(e);
|
||||
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 initialize() {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
anim.initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
synchronized( anims ) {
|
||||
for( Animation<? extends Shape> anim: anims ) {
|
||||
anim.finish();
|
||||
}
|
||||
}
|
||||
public void animate( double e ) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.events.Listener;
|
||||
import schule.ngb.zm.util.events.Listener;
|
||||
|
||||
public interface AnimationListener extends Listener<Animation> {
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ package schule.ngb.zm.anim;
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Vector;
|
||||
import schule.ngb.zm.tasks.FrameSynchronizedTask;
|
||||
import schule.ngb.zm.tasks.FramerateLimitedTask;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
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;
|
||||
|
||||
@@ -15,6 +14,7 @@ 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 ) {
|
||||
@@ -92,27 +92,28 @@ public class Animations {
|
||||
});
|
||||
}
|
||||
|
||||
private static final <T, R> R callGetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
@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 != null && getter.getReturnType().equals(propType) ) {
|
||||
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 final <T, R> Method findSetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException {
|
||||
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 != null && setter.getReturnType().equals(void.class) && setter.getParameterCount() == 1 ) {
|
||||
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 final String makeMethodName( String prefix, String propName ) {
|
||||
private static String makeMethodName( String prefix, String propName ) {
|
||||
String firstChar = propName.substring(0, 1).toUpperCase();
|
||||
String tail = "";
|
||||
if( propName.length() > 1 ) {
|
||||
@@ -124,31 +125,33 @@ public class Animations {
|
||||
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
||||
Validator.requireNotNull(target);
|
||||
Validator.requireNotNull(propSetter);
|
||||
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
||||
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 animate(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e)));
|
||||
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 animate(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e)));
|
||||
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 animate(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r));
|
||||
return play(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r));
|
||||
}
|
||||
|
||||
|
||||
public static final <T, R> Future<T> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, BiConsumer<T, R> applicator ) {
|
||||
return animate(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e)));
|
||||
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> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||
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]
|
||||
@@ -164,8 +167,8 @@ public class Animations {
|
||||
}, target);
|
||||
}
|
||||
|
||||
public static final <T> T animateAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||
Future<T> future = animate(target, runtime, easing, stepper);
|
||||
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();
|
||||
@@ -179,16 +182,17 @@ public class Animations {
|
||||
return target;
|
||||
}
|
||||
|
||||
public static final <T, R> Future<T> animate( T target, int runtime, Animator<T, R> animator ) {
|
||||
/*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>> animate( Animation<T> animation ) {
|
||||
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() {
|
||||
@@ -203,13 +207,13 @@ public class Animations {
|
||||
}, animation);
|
||||
}
|
||||
|
||||
public static <T> Animation<T> animateAndWait( Animation<T> animation ) {
|
||||
Future<Animation<T>> future = animate(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>> animate( Animation<T> animation, DoubleUnaryOperator easing ) {
|
||||
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
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
public interface Animator<T, R> {
|
||||
|
||||
double easing(double t);
|
||||
|
||||
R interpolator(double e);
|
||||
|
||||
void applicator(T target, R value);
|
||||
|
||||
}
|
||||
39
src/main/java/schule/ngb/zm/anim/CircleAnimation.java
Normal file
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
|
||||
public class CircleAnimation extends Animation<Shape> {
|
||||
|
||||
private Shape object;
|
||||
|
||||
private double centerx, centery, radius, startangle;
|
||||
|
||||
public CircleAnimation( Shape target, double cx, double cy, int runtime, DoubleUnaryOperator easing ) {
|
||||
super(runtime, easing);
|
||||
object = target;
|
||||
centerx = cx;
|
||||
centery = cy;
|
||||
Vector vec = new Vector(target.getX(), target.getY()).sub(cx, cy);
|
||||
startangle = vec.heading();
|
||||
radius = vec.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getAnimationTarget() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animate( double e ) {
|
||||
double angle = startangle + Constants.radians(Constants.interpolate(0, 360, e));
|
||||
double x = centerx + radius * Constants.cos(angle);
|
||||
double y = centery + radius * Constants.sin(angle);
|
||||
object.moveTo(x, y);
|
||||
}
|
||||
|
||||
}
|
||||
74
src/main/java/schule/ngb/zm/anim/ContinousAnimation.java
Normal file
@@ -0,0 +1,74 @@
|
||||
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(), baseAnimation.getEasing());
|
||||
this.baseAnimation = baseAnimation;
|
||||
this.lag = lag;
|
||||
this.easeInOnly = easeInOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getAnimationTarget() {
|
||||
return baseAnimation.getAnimationTarget();
|
||||
}
|
||||
|
||||
@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 animate( double e ) {
|
||||
baseAnimation.animate(e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class FadeAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e)));
|
||||
object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e)));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class FillAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setFillColor(Color.interpolate(oFill, tFill, e));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class MorphAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
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));
|
||||
|
||||
@@ -31,7 +31,7 @@ public class MoveAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setX(Constants.interpolate(oX, tX, e));
|
||||
object.setY(Constants.interpolate(oY, tY, e));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class RotateAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.rotateTo(Constants.interpolate(oA, tA, e));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class StrokeAnimation extends Animation<Shape> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate( double e ) {
|
||||
public void animate( double e ) {
|
||||
object.setStrokeColor(Color.interpolate(oFill, tFill, e));
|
||||
}
|
||||
|
||||
|
||||
37
src/main/java/schule/ngb/zm/anim/WaveAnimation.java
Normal file
@@ -0,0 +1,37 @@
|
||||
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,58 +0,0 @@
|
||||
package schule.ngb.zm.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;
|
||||
|
||||
public class EventDispatcher<E, L extends Listener<E>> {
|
||||
|
||||
private CopyOnWriteArraySet<L> listeners;
|
||||
|
||||
private 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);
|
||||
Validator.requireNotNull(dispatcher);
|
||||
|
||||
if( !eventRegistered(eventKey) ) {
|
||||
eventRegistry.put(eventKey, dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener( L listener ) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener( L listener ) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
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);
|
||||
Validator.requireNotNull(event);
|
||||
|
||||
if( eventRegistered(eventKey) ) {
|
||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||
listeners.forEach(( listener ) -> {
|
||||
dispatcher.accept(event, listener);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package schule.ngb.zm.events;
|
||||
|
||||
public interface Listener<E> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
224
src/main/java/schule/ngb/zm/layers/ColorLayer.java
Normal file
@@ -0,0 +1,224 @@
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
106
src/main/java/schule/ngb/zm/layers/DrawableLayer.java
Normal file
@@ -0,0 +1,106 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
1443
src/main/java/schule/ngb/zm/layers/DrawingLayer.java
Normal file
139
src/main/java/schule/ngb/zm/layers/ImageLayer.java
Normal file
@@ -0,0 +1,139 @@
|
||||
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,45 +1,53 @@
|
||||
package schule.ngb.zm;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import java.awt.*;
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Stroke;
|
||||
import java.util.LinkedList;
|
||||
|
||||
@SuppressWarnings( "unused" )
|
||||
public final class Shape2DLayer extends Layer {
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
private schule.ngb.zm.Color strokeColor = DEFAULT_STROKECOLOR;
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
private schule.ngb.zm.Color fillColor = DEFAULT_FILLCOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
private double strokeWeight = DEFAULT_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
private Options.StrokeType strokeType = SOLID;
|
||||
|
||||
private LinkedList<java.awt.Shape> shapes;
|
||||
private final LinkedList<java.awt.Shape> shapes;
|
||||
|
||||
private boolean instantDraw = false;
|
||||
|
||||
public Shape2DLayer() {
|
||||
super();
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
}
|
||||
|
||||
public Shape2DLayer( boolean instantDraw ) {
|
||||
super();
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
this.instantDraw = instantDraw;
|
||||
}
|
||||
|
||||
public Shape2DLayer( int width, int height ) {
|
||||
super(width, height);
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
}
|
||||
|
||||
public Shape2DLayer( int width, int height, boolean instantDraw ) {
|
||||
super(width, height);
|
||||
shapes = new LinkedList<java.awt.Shape>();
|
||||
shapes = new LinkedList<>();
|
||||
this.instantDraw = instantDraw;
|
||||
}
|
||||
|
||||
public Color getFillColor() {
|
||||
public schule.ngb.zm.Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
@@ -47,7 +55,7 @@ public final class Shape2DLayer extends Layer {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( Color pColor ) {
|
||||
public void setFillColor( schule.ngb.zm.Color pColor ) {
|
||||
fillColor = pColor;
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
}
|
||||
@@ -65,10 +73,10 @@ public final class Shape2DLayer extends Layer {
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
setFillColor(new schule.ngb.zm.Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
public schule.ngb.zm.Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
@@ -76,7 +84,7 @@ public final class Shape2DLayer extends Layer {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color pColor ) {
|
||||
public void setStrokeColor( schule.ngb.zm.Color pColor ) {
|
||||
strokeColor = pColor;
|
||||
drawing.setColor(pColor.getJavaColor());
|
||||
}
|
||||
@@ -102,7 +110,7 @@ public final class Shape2DLayer extends Layer {
|
||||
drawing.setStroke(createStroke());
|
||||
}
|
||||
|
||||
protected Stroke createStroke() {
|
||||
private Stroke createStroke() {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
return new BasicStroke(
|
||||
@@ -149,7 +157,7 @@ public final class Shape2DLayer extends Layer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( !instantDraw ) {
|
||||
for( Shape shape : shapes ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
@@ -160,7 +168,7 @@ public final class Shape2DLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +1,32 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.Layer;
|
||||
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.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
private List<Shape> shapes;
|
||||
private final List<Shape> shapes;
|
||||
|
||||
private List<Animation<? extends Shape>> animations;
|
||||
private final List<Animation<? extends Shape>> animations;
|
||||
|
||||
public ShapesLayer() {
|
||||
super();
|
||||
@@ -39,55 +47,49 @@ public class ShapesLayer extends Layer {
|
||||
public <ST extends Shape> ST getShape( Class<ST> shapeClass ) {
|
||||
for( Shape s : shapes ) {
|
||||
if( shapeClass.isInstance(s) ) {
|
||||
return (ST) s;
|
||||
return shapeClass.cast(s);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public java.util.List<Shape> getShapes() {
|
||||
public List<Shape> getShapes() {
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public <ST extends Shape> java.util.List<ST> getShapes( Class<ST> shapeClass ) {
|
||||
java.util.List<ST> result = new LinkedList<>();
|
||||
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((ST) s);
|
||||
result.add(shapeClass.cast(s));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void add( Shape... shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
synchronized( this.shapes ) {
|
||||
Collections.addAll(this.shapes, shapes);
|
||||
}
|
||||
}
|
||||
|
||||
public void add( Collection<Shape> shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.add(s);
|
||||
}
|
||||
synchronized( this.shapes ) {
|
||||
this.shapes.addAll(shapes);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove( Shape... shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s: shapes ) {
|
||||
synchronized( this.shapes ) {
|
||||
for( Shape s : shapes ) {
|
||||
this.shapes.remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove( Collection<Shape> shapes ) {
|
||||
synchronized( shapes ) {
|
||||
for( Shape s: shapes ) {
|
||||
this.shapes.remove(s);
|
||||
}
|
||||
synchronized( this.shapes ) {
|
||||
this.shapes.removeAll(shapes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,16 +101,16 @@ public class ShapesLayer extends Layer {
|
||||
|
||||
public void showAll() {
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
pShape.show();
|
||||
for( Shape s : shapes ) {
|
||||
s.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void hideAll() {
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
pShape.hide();
|
||||
for( Shape s : shapes ) {
|
||||
s.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +120,14 @@ public class ShapesLayer extends Layer {
|
||||
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);
|
||||
}
|
||||
@@ -135,26 +145,27 @@ public class ShapesLayer extends Layer {
|
||||
anim.update(delta);
|
||||
|
||||
if( !anim.isActive() ) {
|
||||
animations.remove(anim);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( clearBeforeDraw ) {
|
||||
clear();
|
||||
}
|
||||
|
||||
synchronized( shapes ) {
|
||||
for( Shape pShape : shapes ) {
|
||||
if( pShape.isVisible() ) {
|
||||
pShape.draw(drawing);
|
||||
List<Shape> it = List.copyOf(shapes);
|
||||
for( Shape s : it ) {
|
||||
if( s.isVisible() ) {
|
||||
s.draw(drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.draw(pGraphics);
|
||||
super.draw(graphics);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,25 @@
|
||||
package schule.ngb.zm.turtle;
|
||||
package schule.ngb.zm.layers;
|
||||
|
||||
import schule.ngb.zm.*;
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Layer;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.Vector;
|
||||
import schule.ngb.zm.shapes.FilledShape;
|
||||
import schule.ngb.zm.Fillable;
|
||||
import schule.ngb.zm.Strokeable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class TurtleLayer extends Layer {
|
||||
/**
|
||||
* Eine Ebene, auf der eine Turtle gesteuert werden kann, die Grafiken plottet.
|
||||
* <p>
|
||||
* Die Turtle verhält sich ähnlich zu ihren Entsprechungen in Logo oder
|
||||
* TigerJython.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class TurtleLayer extends Layer implements Strokeable, Fillable {
|
||||
|
||||
// Rotating by the clock
|
||||
public static final int H1 = 30;
|
||||
@@ -39,7 +46,7 @@ public class TurtleLayer extends Layer {
|
||||
|
||||
public static final int H12 = 360;
|
||||
|
||||
private static Stack<Color> turtleColors;
|
||||
private final static Stack<Color> turtleColors;
|
||||
|
||||
static {
|
||||
turtleColors = new Stack<>();
|
||||
@@ -51,9 +58,9 @@ public class TurtleLayer extends Layer {
|
||||
turtleColors.add(Color.BLUE);
|
||||
}
|
||||
|
||||
private Turtle mainTurtle = null;
|
||||
private final Turtle mainTurtle;
|
||||
|
||||
private ArrayList<Turtle> turtles = new ArrayList<Turtle>(6);
|
||||
private final List<Turtle> turtles = new ArrayList<>(6);
|
||||
|
||||
public TurtleLayer() {
|
||||
super();
|
||||
@@ -117,7 +124,20 @@ public class TurtleLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
// Begin of delegate methods (auto-generated)
|
||||
// begin of delegate methods (auto-generated)
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return mainTurtle.isVisible();
|
||||
}
|
||||
|
||||
public void beginPath() {
|
||||
mainTurtle.beginPath();
|
||||
}
|
||||
|
||||
public void closePath() {
|
||||
mainTurtle.closePath();
|
||||
}
|
||||
|
||||
public void fill() {
|
||||
mainTurtle.fill();
|
||||
@@ -175,100 +195,196 @@ public class TurtleLayer extends Layer {
|
||||
mainTurtle.moveTo(x, y);
|
||||
}
|
||||
|
||||
public Color getFillColor() {
|
||||
return mainTurtle.getFillColor();
|
||||
@Override
|
||||
public void setFill( Paint fill ) {
|
||||
mainTurtle.setFill(fill);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Paint getFill() {
|
||||
return mainTurtle.getFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( Color color ) {
|
||||
mainTurtle.setFillColor(color);
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
mainTurtle.setFillColor(gray);
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
mainTurtle.noFill();
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
mainTurtle.setFillColor(gray, alpha);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
mainTurtle.setFillColor(red, green, blue);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setFillColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public void resetFill() {
|
||||
mainTurtle.resetFill();
|
||||
}
|
||||
|
||||
public Color getStrokeColor() {
|
||||
return mainTurtle.getStrokeColor();
|
||||
@Override
|
||||
public Color getFillColor() {
|
||||
return mainTurtle.getFillColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( Color color ) {
|
||||
mainTurtle.setStrokeColor(color);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
mainTurtle.setStrokeColor(gray);
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
mainTurtle.noStroke();
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
mainTurtle.setStrokeColor(gray, alpha);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public double getStrokeWeight() {
|
||||
return mainTurtle.getStrokeWeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeWeight( double weight ) {
|
||||
mainTurtle.setStrokeWeight(weight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return mainTurtle.getStrokeType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
mainTurtle.setStrokeType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( Color from, Color to, Options.Direction dir ) {
|
||||
mainTurtle.setGradient(from, to, dir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( Color from, Color to ) {
|
||||
mainTurtle.setGradient(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFill() {
|
||||
return mainTurtle.hasFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFillColor() {
|
||||
return mainTurtle.hasFillColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasGradient() {
|
||||
return mainTurtle.hasGradient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
mainTurtle.setFillColor(color, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int gray ) {
|
||||
mainTurtle.setFillColor(gray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
mainTurtle.setFillColor(gray, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
mainTurtle.setFillColor(red, green, blue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setFillColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noFill() {
|
||||
mainTurtle.noFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetFill() {
|
||||
mainTurtle.resetFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipleGradientPaint getGradient() {
|
||||
return mainTurtle.getGradient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( double fromX, double fromY, Color from, double toX, double toY, Color to ) {
|
||||
mainTurtle.setGradient(fromX, fromY, from, toX, toY, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGradient( double centerX, double centerY, double radius, Color from, Color to ) {
|
||||
mainTurtle.setGradient(centerX, centerY, radius, from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noGradient() {
|
||||
mainTurtle.noGradient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStroke( Stroke stroke ) {
|
||||
mainTurtle.setStroke(stroke);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stroke getStroke() {
|
||||
return mainTurtle.getStroke();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStroke() {
|
||||
return mainTurtle.hasStroke();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getStrokeColor() {
|
||||
return mainTurtle.getStrokeColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( Color color, int alpha ) {
|
||||
mainTurtle.setStrokeColor(color, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int gray ) {
|
||||
mainTurtle.setStrokeColor(gray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
mainTurtle.setStrokeColor(gray, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
mainTurtle.setStrokeColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noStroke() {
|
||||
mainTurtle.noStroke();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetStroke() {
|
||||
mainTurtle.resetStroke();
|
||||
}
|
||||
|
||||
public void addPosToPath() {
|
||||
mainTurtle.addPosToPath();
|
||||
@Override
|
||||
public double getStrokeWeight() {
|
||||
return mainTurtle.getStrokeWeight();
|
||||
}
|
||||
|
||||
public void closePath() {
|
||||
mainTurtle.closePath();
|
||||
}
|
||||
|
||||
// End of delegate methods (auto-generated)
|
||||
// end of delegate methods (auto-generated)
|
||||
|
||||
|
||||
public class Turtle extends FilledShape {
|
||||
/**
|
||||
* Die Turtle der Zeichenmaschine.
|
||||
*/
|
||||
public class Turtle extends BasicDrawable {
|
||||
|
||||
private static final int STD_SIZE = 12;
|
||||
private static final int DEFAULT_SIZE = 12;
|
||||
|
||||
boolean penDown = true;
|
||||
|
||||
@@ -282,7 +398,31 @@ public class TurtleLayer extends Layer {
|
||||
|
||||
boolean pathOpen = false;
|
||||
|
||||
Turtle() {}
|
||||
/**
|
||||
* Path-Objekt für die Darstellung der Turtle.
|
||||
*/
|
||||
Path2D.Double turtlePath;
|
||||
|
||||
Turtle() {
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public void beginPath() {
|
||||
pathOpen = false;
|
||||
addPosToPath();
|
||||
}
|
||||
|
||||
public void closePath() {
|
||||
if( pathOpen ) {
|
||||
addPosToPath();
|
||||
path.closePath();
|
||||
path.trimToSize();
|
||||
pathOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addPosToPath() {
|
||||
if( !pathOpen ) {
|
||||
@@ -294,53 +434,39 @@ public class TurtleLayer extends Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private void closePath() {
|
||||
if( pathOpen ) {
|
||||
addPosToPath();
|
||||
path.closePath();
|
||||
path.trimToSize();
|
||||
pathOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void fill() {
|
||||
closePath();
|
||||
|
||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
drawing.setColor(fillColor.getJavaColor());
|
||||
if( hasFill() ) {
|
||||
drawing.setPaint(getFill());
|
||||
drawing.fill(path);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw( Graphics2D graphics ) {
|
||||
/*Shape shape = new RoundRectangle2D.Double(
|
||||
-12, -5, 16, 10, 5, 3
|
||||
);*/
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(STD_SIZE, 0);
|
||||
path.lineTo(-STD_SIZE, -STD_SIZE/2);
|
||||
path.lineTo(-STD_SIZE, STD_SIZE/2);
|
||||
path.lineTo(STD_SIZE, 0);
|
||||
if( turtlePath == null ) {
|
||||
turtlePath = new Path2D.Double();
|
||||
path.moveTo(DEFAULT_SIZE, 0);
|
||||
path.lineTo(-DEFAULT_SIZE, -DEFAULT_SIZE / 2.0);
|
||||
path.lineTo(-DEFAULT_SIZE, DEFAULT_SIZE / 2.0);
|
||||
path.lineTo(DEFAULT_SIZE, 0);
|
||||
}
|
||||
|
||||
AffineTransform verzerrung = new AffineTransform();
|
||||
verzerrung.translate(position.x, position.y);
|
||||
verzerrung.rotate(Math.toRadians(direction.angle()));
|
||||
|
||||
Shape shape = verzerrung.createTransformedShape(path);
|
||||
java.awt.Shape shape = verzerrung.createTransformedShape(turtlePath);
|
||||
|
||||
if( strokeColor != null ) {
|
||||
if( hasStroke() ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
} else {
|
||||
graphics.setColor(STD_STROKECOLOR.getJavaColor());
|
||||
graphics.setColor(DEFAULT_STROKECOLOR.getJavaColor());
|
||||
}
|
||||
graphics.fill(shape);
|
||||
graphics.setColor(Color.BLACK.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
|
||||
@@ -354,9 +480,9 @@ public class TurtleLayer extends Layer {
|
||||
Vector positionStart = position.copy();
|
||||
position.add(Vector.setLength(direction, dist));
|
||||
|
||||
if( penDown && strokeColor != null ) {
|
||||
if( penDown && hasStroke() ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.setStroke(getStroke());
|
||||
drawing.drawLine((int) positionStart.x, (int) positionStart.y, (int) position.x, (int) position.y);
|
||||
}
|
||||
}
|
||||
@@ -407,9 +533,9 @@ public class TurtleLayer extends Layer {
|
||||
position.x = x;
|
||||
position.y = y;
|
||||
|
||||
if( penDown && strokeColor != null ) {
|
||||
if( penDown && hasStroke() ) {
|
||||
drawing.setColor(strokeColor.getJavaColor());
|
||||
drawing.setStroke(createStroke());
|
||||
drawing.setStroke(getStroke());
|
||||
drawing.drawLine((int) x, (int) y, (int) position.x, (int) position.y);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,21 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
/**
|
||||
* Interface für Audio-Medien.
|
||||
* 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();
|
||||
|
||||
/**
|
||||
@@ -17,7 +28,7 @@ public interface Audio {
|
||||
|
||||
/**
|
||||
* Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn
|
||||
* {@code isLooping() == true}, dann muss auch immer
|
||||
* {@code isLooping() == true} gilt, dann muss auch immer
|
||||
* {@code isPlaying() == true} gelten.
|
||||
*
|
||||
* @return {@code true}, wenn das Medium in einer Schleife abgespielt wird,
|
||||
@@ -30,7 +41,7 @@ public interface Audio {
|
||||
* <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.
|
||||
* Mediums. Negative Werte setzen die Lautstärke aud 0.
|
||||
*
|
||||
* @param volume Die neue Lautstärke zwischen 0 und 1.
|
||||
* @see <a
|
||||
@@ -39,7 +50,7 @@ public interface Audio {
|
||||
void setVolume( double volume );
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück.
|
||||
* 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
|
||||
@@ -50,8 +61,16 @@ public interface Audio {
|
||||
double getVolume();
|
||||
|
||||
/**
|
||||
* Startet die Wiedergabe des Mediums und beendet die Methode. Das
|
||||
* Audio-Medium wird einmal abgespielt und stoppt dann.
|
||||
* 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();
|
||||
|
||||
@@ -63,21 +82,36 @@ public interface Audio {
|
||||
|
||||
/**
|
||||
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
|
||||
* startet die Wiedergabe und beendet dann direkt die Methode. Um die
|
||||
* Wiedergabe zu stoppen muss {@link #stop()} aufgerufen werden.
|
||||
* 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.
|
||||
* ({@code isPlaying() == false}), dann passiert nichts.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Stoppt die Wiedergabe und gibt alle Resourcen, die für das Medium
|
||||
* 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,11 +1,34 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.events.Listener;
|
||||
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> {
|
||||
|
||||
void start( Audio source );
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
void stop( 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,9 +1,11 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -18,24 +20,14 @@ import java.util.List;
|
||||
* Darüber hinaus kann ein Mixer Effekte wie einen
|
||||
* {@link #fade(double, int) fadeIn} auf die Medien anwenden.
|
||||
*/
|
||||
public class Mixer implements Audio {
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Mixer implements Audio, AudioListener {
|
||||
|
||||
private List<AudioWrapper> audios;
|
||||
|
||||
private float volume = 0.8f;
|
||||
|
||||
class AudioWrapper {
|
||||
|
||||
Audio audio;
|
||||
|
||||
float volumeFactor;
|
||||
|
||||
public AudioWrapper( Audio audio, float volumeFactor ) {
|
||||
this.audio = audio;
|
||||
this.volumeFactor = volumeFactor;
|
||||
}
|
||||
|
||||
}
|
||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||
|
||||
public Mixer() {
|
||||
this.audios = new ArrayList<>(4);
|
||||
@@ -45,66 +37,93 @@ public class Mixer implements Audio {
|
||||
return "";
|
||||
}
|
||||
|
||||
public void add( Audio pAudio ) {
|
||||
add(pAudio, 1f);
|
||||
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 ) {
|
||||
audios.add(new AudioWrapper(pAudio, (float) 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isLooping() {
|
||||
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setVolume( double pVolume ) {
|
||||
volume = (float) pVolume;
|
||||
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume));
|
||||
public void setVolume( double volume ) {
|
||||
this.volume = volume < 0 ? 0f : (float) volume;
|
||||
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * volume));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
audios.stream().forEach(aw -> aw.audio.play());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void playAndWait() {
|
||||
audios.stream().forEach(aw -> aw.audio.play());
|
||||
@@ -117,25 +136,16 @@ public class Mixer implements Audio {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
audios.stream().forEach(aw -> aw.audio.loop());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
audios.stream().forEach(aw -> aw.audio.stop());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
if( isPlaying() ) {
|
||||
@@ -178,4 +188,58 @@ public class Mixer implements Audio {
|
||||
});
|
||||
}
|
||||
|
||||
@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,85 +1,103 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.anim.Animation;
|
||||
import schule.ngb.zm.anim.AnimationListener;
|
||||
import schule.ngb.zm.events.EventDispatcher;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Ein Musikstück, dass im Projekt abgespielt werden soll.
|
||||
* Eine Musik, die abgespielt werden kann.
|
||||
* <p>
|
||||
* Im gegensatz zu einem {@link Sound} sind Musikstücke längere Audiodateien,
|
||||
* die zum Beispiel als Hintergrundmusik ablaufen sollen. Die Musik wird daher
|
||||
* nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle
|
||||
* gestreamt und wiedergegeben.
|
||||
* 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 {
|
||||
|
||||
// size of the byte buffer used to read/write the audio stream
|
||||
/**
|
||||
* Größe des verwendeten Input-Puffers für die Audiodaten.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
|
||||
/**
|
||||
* Ob der Sound gerade abgespielt wird.
|
||||
* Ob der Sound aktuell abgespielt wird.
|
||||
*/
|
||||
private boolean playing = false;
|
||||
|
||||
/**
|
||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
||||
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
||||
*/
|
||||
private boolean looping = false;
|
||||
|
||||
/**
|
||||
* Die Quelle des Musikstücks.
|
||||
* Die Quelle der Audiodaten.
|
||||
*/
|
||||
private String audioSource;
|
||||
|
||||
/**
|
||||
* Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet
|
||||
* wurde. Sonst {@code null}.
|
||||
* Der {@link AudioInputStream}, um die Audiosdaten zu lesen. {@code null},
|
||||
* falls noch kein Stream geöffnet wurde.
|
||||
*/
|
||||
private AudioInputStream audioStream;
|
||||
|
||||
/**
|
||||
* Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst
|
||||
* {@code null}.
|
||||
* Die {@link SourceDataLine} für die Ausgabe. {@code null}, falls die
|
||||
* Audiodatei noch nicht geöffnet wurde.
|
||||
*/
|
||||
private SourceDataLine audioLine;
|
||||
|
||||
/**
|
||||
* Die Lautstärke der Musik.
|
||||
* Die aktuelle Lautstärke des Mediums.
|
||||
*/
|
||||
private float volume = 0.8f;
|
||||
|
||||
/**
|
||||
* Dispatcher für Audio-Events (start und stop).
|
||||
*/
|
||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||
|
||||
public Music( String source ) {
|
||||
Validator.requireNotNull(source);
|
||||
this.audioSource = source;
|
||||
/**
|
||||
* 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);
|
||||
this.audioSource = audioSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSource() {
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
return playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isLooping() {
|
||||
if( !playing ) {
|
||||
@@ -88,28 +106,21 @@ public class Music implements Audio {
|
||||
return looping;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setVolume( double volume ) {
|
||||
this.volume = (float) volume;
|
||||
this.volume = volume < 0 ? 0f : (float) volume;
|
||||
if( audioLine != null ) {
|
||||
applyVolume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
||||
* anzuwenden.
|
||||
* Wendet die Lautstärke vor dem Abspielen auf den Audiostream an.
|
||||
*/
|
||||
private void applyVolume() {
|
||||
FloatControl gainControl =
|
||||
@@ -120,24 +131,13 @@ public class Music implements Audio {
|
||||
gainControl.setValue(vol);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
if( openLine() ) {
|
||||
TaskRunner.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stream();
|
||||
}
|
||||
});
|
||||
TaskRunner.run(this::stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void playAndWait() {
|
||||
if( openLine() ) {
|
||||
@@ -145,18 +145,12 @@ public class Music implements Audio {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
looping = true;
|
||||
play();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
playing = false;
|
||||
@@ -164,11 +158,8 @@ public class Music implements Audio {
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
public synchronized void dispose() {
|
||||
if( audioLine != null ) {
|
||||
if( audioLine.isRunning() ) {
|
||||
playing = false;
|
||||
@@ -177,7 +168,6 @@ public class Music implements Audio {
|
||||
if( audioLine.isOpen() ) {
|
||||
audioLine.drain();
|
||||
audioLine.close();
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -191,7 +181,17 @@ public class Music implements Audio {
|
||||
audioStream = null;
|
||||
}
|
||||
|
||||
private void stream() {
|
||||
/**
|
||||
* 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 ) {
|
||||
@@ -228,6 +228,14 @@ public class Music implements Audio {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ö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;
|
||||
@@ -264,6 +272,15 @@ public class Music implements Audio {
|
||||
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();
|
||||
|
||||
@@ -279,24 +296,27 @@ public class Music implements Audio {
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener( AudioListener listener ) {
|
||||
@Override
|
||||
public void addAudioListener( AudioListener listener ) {
|
||||
initializeEventDispatcher().addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener( AudioListener listener ) {
|
||||
@Override
|
||||
public void removeAudioListener( AudioListener listener ) {
|
||||
initializeEventDispatcher().removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||
* aufgerufen, soblad sich auch ein Listener registrieren möchte.
|
||||
* @return
|
||||
* 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.start(a));
|
||||
eventDispatcher.registerEventType("stop", (a,l) -> l.stop(a));
|
||||
eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
|
||||
eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
|
||||
}
|
||||
return eventDispatcher;
|
||||
}
|
||||
|
||||
@@ -1,87 +1,96 @@
|
||||
package schule.ngb.zm.media;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
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.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden.
|
||||
* Ein kurzer Soundclip, der mehrmals wiederverwendet werden kann.
|
||||
* <p>
|
||||
* In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer Sounds,
|
||||
* die zusammen mit bestimmten Aktionen wiedergegeben werden (zum Beispiel, wenn
|
||||
* die Spielfigur springt, wenn zwei Objekte kollidieren, usw.). Sounds werden
|
||||
* komplett in den Speicher geladen und können dadurch immer wieder, als
|
||||
* Schleife oder auch nur Abschnittsweise abgespielt werden.
|
||||
* 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ängre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die
|
||||
* KLasse {@link Music} an.
|
||||
* 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 gerade abgespielt wird.
|
||||
* Ob der Sound aktuell abgespielt wird.
|
||||
*/
|
||||
private boolean playing = false;
|
||||
|
||||
/**
|
||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
||||
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
||||
*/
|
||||
private boolean looping = false;
|
||||
|
||||
/**
|
||||
* Die Quelle des Musikstücks.
|
||||
* Die Quelle der Audiodaten.
|
||||
*/
|
||||
private String audioSource;
|
||||
|
||||
/**
|
||||
* Der Clip, falls er schon geladen wurde, sonst {@code null}.
|
||||
* Der Clip, falls er schon geladen wurde. Ansonsten {@code null}.
|
||||
*/
|
||||
private Clip audioClip;
|
||||
|
||||
/**
|
||||
* Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen
|
||||
* Ob die Ressourcen des Clips im Speicher nach dem nächsten Abspielen
|
||||
* freigegeben werden sollen.
|
||||
*/
|
||||
private boolean disposeAfterPlay = false;
|
||||
|
||||
/**
|
||||
* Die Lautstärke des Clips.
|
||||
* 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 Ein Dateipfad oder eine Webadresse.
|
||||
* @param source Quelle der Audiodaten.
|
||||
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
||||
* @see ResourceStreamProvider#getResourceURL(String)
|
||||
*/
|
||||
public Sound( String source ) {
|
||||
Validator.requireNotNull(source);
|
||||
this.audioSource = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSource() {
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
// return audioClip != null && audioClip.isRunning();
|
||||
return playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isLooping() {
|
||||
if( !playing ) {
|
||||
@@ -90,28 +99,21 @@ public class Sound implements Audio {
|
||||
return looping;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setVolume( double volume ) {
|
||||
this.volume = (float) volume;
|
||||
this.volume = volume < 0 ? 0f : (float) volume;
|
||||
if( audioClip != null ) {
|
||||
applyVolume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
||||
* anzuwenden.
|
||||
* Wendet die Lautstärke vor dem Abspielen auf den Clip an.
|
||||
*/
|
||||
private void applyVolume() {
|
||||
FloatControl gainControl =
|
||||
@@ -122,9 +124,6 @@ public class Sound implements Audio {
|
||||
gainControl.setValue(vol);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
looping = false;
|
||||
@@ -134,9 +133,6 @@ public class Sound implements Audio {
|
||||
playing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
if( this.openClip() ) {
|
||||
@@ -145,9 +141,6 @@ public class Sound implements Audio {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void playAndWait() {
|
||||
this.play();
|
||||
@@ -166,7 +159,7 @@ public class Sound implements Audio {
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
||||
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
||||
* frei.
|
||||
* <p>
|
||||
* Der Aufruf ist effektiv gleich zu
|
||||
@@ -183,7 +176,7 @@ public class Sound implements Audio {
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
||||
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
||||
* frei.
|
||||
* <p>
|
||||
* Der Aufruf entspricht
|
||||
@@ -197,17 +190,19 @@ public class Sound implements Audio {
|
||||
playAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void loop() {
|
||||
loop(Clip.LOOP_CONTINUOUSLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt
|
||||
* 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 ) {
|
||||
@@ -228,21 +223,25 @@ public class Sound implements Audio {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
public synchronized void dispose() {
|
||||
if( audioClip != null ) {
|
||||
if( audioClip.isRunning() ) {
|
||||
audioClip.stop();
|
||||
stop();
|
||||
}
|
||||
audioClip.close();
|
||||
audioClip = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean openClip() {
|
||||
/**
|
||||
* Lädt falls nötig den {@link Clip} für die
|
||||
* {@link #audioSource Audioquelle} und startet die Wiedergabe.
|
||||
*
|
||||
* @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;
|
||||
@@ -259,8 +258,16 @@ public class Sound implements Audio {
|
||||
audioClip.addLineListener(new LineListener() {
|
||||
@Override
|
||||
public void update( LineEvent event ) {
|
||||
if( event.getType() == LineEvent.Type.STOP ) {
|
||||
if( event.getType() == LineEvent.Type.START ) {
|
||||
if( eventDispatcher != null ) {
|
||||
eventDispatcher.dispatchEvent("start", Sound.this);
|
||||
}
|
||||
} else if( event.getType() == LineEvent.Type.STOP ) {
|
||||
playbackStopped();
|
||||
|
||||
if( eventDispatcher != null ) {
|
||||
eventDispatcher.dispatchEvent("stop", Sound.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -296,18 +303,47 @@ public class Sound implements Audio {
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Interne Methode, die aufgerufen wird, wenn die Wiedergabe gestoppt wird.
|
||||
* Entweder durch einen Aufruf von {@link #stop()} oder, weil die Wiedergabe
|
||||
* nach {@link #playOnce()} beendet wurde.
|
||||
* 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( 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);
|
||||
|
||||
}
|
||||
|
||||
337
src/main/java/schule/ngb/zm/ml/DoubleMatrix.java
Normal file
@@ -0,0 +1,337 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
316
src/main/java/schule/ngb/zm/ml/MLMatrix.java
Normal file
@@ -0,0 +1,316 @@
|
||||
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,82 +0,0 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO: Move Math into Matrix class
|
||||
// TODO: Implement support for optional sci libs
|
||||
public class Matrix {
|
||||
|
||||
private int columns, rows;
|
||||
|
||||
double[][] coefficients;
|
||||
|
||||
public Matrix( int rows, int cols ) {
|
||||
this.rows = rows;
|
||||
this.columns = cols;
|
||||
coefficients = new double[rows][cols];
|
||||
}
|
||||
|
||||
public Matrix( double[][] coefficients ) {
|
||||
this.coefficients = coefficients;
|
||||
this.rows = coefficients.length;
|
||||
this.columns = coefficients[0].length;
|
||||
}
|
||||
|
||||
public int getColumns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
public int getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public double[][] getCoefficients() {
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
public double get( int row, int col ) {
|
||||
return coefficients[row][col];
|
||||
}
|
||||
|
||||
public void initializeRandom() {
|
||||
coefficients = MLMath.matrixApply(coefficients, (d) -> Constants.randomGaussian());
|
||||
}
|
||||
|
||||
public void initializeRandom( double lower, double upper ) {
|
||||
coefficients = MLMath.matrixApply(coefficients, (d) -> ((upper-lower) * (Constants.randomGaussian()+1) * .5) + lower);
|
||||
}
|
||||
|
||||
public void initializeIdentity() {
|
||||
initializeZero();
|
||||
for( int i = 0; i < Math.min(rows, columns); i++ ) {
|
||||
this.coefficients[i][i] = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeOne() {
|
||||
coefficients = MLMath.matrixApply(coefficients, (d) -> 1.0);
|
||||
}
|
||||
|
||||
public void initializeZero() {
|
||||
coefficients = MLMath.matrixApply(coefficients, (d) -> 0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
//return Arrays.deepToString(coefficients);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('[');
|
||||
sb.append('\n');
|
||||
for( int i = 0; i < coefficients.length; i++ ) {
|
||||
sb.append('\t');
|
||||
sb.append(Arrays.toString(coefficients[i]));
|
||||
sb.append('\n');
|
||||
}
|
||||
sb.append(']');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
246
src/main/java/schule/ngb/zm/ml/MatrixFactory.java
Normal file
@@ -0,0 +1,246 @@
|
||||
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,7 +1,7 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.ResourceStreamProvider;
|
||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
@@ -15,7 +15,7 @@ public class NeuralNetwork {
|
||||
Writer writer = ResourceStreamProvider.getWriter(source);
|
||||
PrintWriter out = new PrintWriter(writer)
|
||||
) {
|
||||
for( NeuronLayer layer: network.layers ) {
|
||||
for( NeuronLayer layer : network.layers ) {
|
||||
out.print(layer.getNeuronCount());
|
||||
out.print(' ');
|
||||
out.print(layer.getInputCount());
|
||||
@@ -23,20 +23,44 @@ public class NeuralNetwork {
|
||||
|
||||
for( int i = 0; i < layer.getInputCount(); i++ ) {
|
||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||
out.print(layer.weights.coefficients[i][j]);
|
||||
out.print(layer.weights.get(i, j));
|
||||
out.print(' ');
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
for( int j = 0; j < layer.getNeuronCount(); j++ ) {
|
||||
out.print(layer.biases[j]);
|
||||
out.print(layer.biases.get(0, j));
|
||||
out.print(' ');
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
out.flush();
|
||||
} catch( IOException ex ) {
|
||||
LOG.warn(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, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +80,13 @@ public class NeuralNetwork {
|
||||
for( int i = 0; i < inputs; i++ ) {
|
||||
split = in.readLine().split(" ");
|
||||
for( int j = 0; j < neurons; j++ ) {
|
||||
layer.weights.coefficients[i][j] = Double.parseDouble(split[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[j] = Double.parseDouble(split[j]);
|
||||
layer.biases.set(0, j, Double.parseDouble(split[j]));
|
||||
}
|
||||
|
||||
layers.add(layer);
|
||||
@@ -70,29 +94,30 @@ public class NeuralNetwork {
|
||||
|
||||
return new NeuralNetwork(layers);
|
||||
} catch( IOException | NoSuchElementException ex ) {
|
||||
LOG.warn(ex, "Could not load neural network from source <%s>", source);
|
||||
LOG.error(ex, "Could not load neural network from source <%s>", source);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*public static NeuralNetwork loadFromFile( String source ) {
|
||||
public static NeuralNetwork loadFromDataFile( String source ) {
|
||||
try(
|
||||
InputStream stream = ResourceStreamProvider.getInputStream(source);
|
||||
Scanner in = new Scanner(stream)
|
||||
DataInputStream in = new DataInputStream(stream)
|
||||
) {
|
||||
List<NeuronLayer> layers = new LinkedList<>();
|
||||
while( in.hasNext() ) {
|
||||
int neurons = in.nextInt();
|
||||
int inputs = in.nextInt();
|
||||
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.coefficients[i][j] = in.nextDouble();
|
||||
layer.weights.set(i, j, in.readDouble());
|
||||
}
|
||||
}
|
||||
// Load Biases
|
||||
for( int j = 0; j < neurons; j++ ) {
|
||||
layer.biases[j] = in.nextDouble();
|
||||
layer.biases.set(0, j, in.readDouble());
|
||||
}
|
||||
|
||||
layers.add(layer);
|
||||
@@ -100,14 +125,14 @@ public class NeuralNetwork {
|
||||
|
||||
return new NeuralNetwork(layers);
|
||||
} catch( IOException | NoSuchElementException ex ) {
|
||||
LOG.warn(ex, "Could not load neural network from source <%s>", source);
|
||||
LOG.error(ex, "Could not load neural network from source <%s>", source);
|
||||
}
|
||||
return null;
|
||||
}*/
|
||||
}
|
||||
|
||||
private NeuronLayer[] layers;
|
||||
|
||||
private double[][] output;
|
||||
private MLMatrix output;
|
||||
|
||||
private double learningRate = 0.1;
|
||||
|
||||
@@ -128,7 +153,7 @@ public class NeuralNetwork {
|
||||
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]);
|
||||
this.layers[i - 1].setNextLayer(this.layers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +163,7 @@ public class NeuralNetwork {
|
||||
for( int i = 0; i < layers.length; i++ ) {
|
||||
this.layers[i] = layers[i];
|
||||
if( i > 0 ) {
|
||||
this.layers[i-1].setNextLayer(this.layers[i]);
|
||||
this.layers[i - 1].setNextLayer(this.layers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +171,7 @@ public class NeuralNetwork {
|
||||
public int getLayerCount() {
|
||||
return layers.length;
|
||||
}
|
||||
|
||||
public NeuronLayer[] getLayers() {
|
||||
return layers;
|
||||
}
|
||||
@@ -162,17 +188,28 @@ public class NeuralNetwork {
|
||||
this.learningRate = pLearningRate;
|
||||
}
|
||||
|
||||
public double[][] getOutput() {
|
||||
public MLMatrix getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public double[][] predict( double[][] inputs ) {
|
||||
//this.output = layers[1].apply(layers[0].apply(inputs));
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +1,66 @@
|
||||
package schule.ngb.zm.ml;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class NeuronLayer implements Function<double[][], double[][]> {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
public static NeuronLayer fromArray( double[][] weights ) {
|
||||
NeuronLayer 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++ ) {
|
||||
layer.weights.coefficients[i][j] = weights[i][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 ) {
|
||||
NeuronLayer 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++ ) {
|
||||
layer.weights.coefficients[i][j] = weights[i][j];
|
||||
}
|
||||
}
|
||||
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[j] = biases[j];
|
||||
layer.biases.set(0, j, biases[j]);
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
Matrix weights;
|
||||
|
||||
double[] biases;
|
||||
MLMatrix weights;
|
||||
|
||||
MLMatrix biases;
|
||||
|
||||
NeuronLayer previous, next;
|
||||
|
||||
DoubleUnaryOperator activationFunction, activationFunctionDerivative;
|
||||
|
||||
double[][] lastOutput, lastInput;
|
||||
MLMatrix lastOutput, lastInput;
|
||||
|
||||
public NeuronLayer( int neurons, int inputs ) {
|
||||
weights = new Matrix(inputs, neurons);
|
||||
weights.initializeRandom(-1, 1);
|
||||
weights = MatrixFactory
|
||||
.create(inputs, neurons)
|
||||
.initializeRandom();
|
||||
|
||||
biases = new double[neurons];
|
||||
Arrays.fill(biases, 0.0); // TODO: Random?
|
||||
biases = MatrixFactory
|
||||
.create(1, neurons)
|
||||
.initializeZero();
|
||||
|
||||
activationFunction = MLMath::sigmoid;
|
||||
activationFunctionDerivative = MLMath::sigmoidDerivative;
|
||||
@@ -85,45 +101,42 @@ public class NeuronLayer implements Function<double[][], double[][]> {
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix getWeights() {
|
||||
public MLMatrix getWeights() {
|
||||
return weights;
|
||||
}
|
||||
|
||||
public MLMatrix getBiases() {
|
||||
return biases;
|
||||
}
|
||||
|
||||
public int getNeuronCount() {
|
||||
return weights.coefficients[0].length;
|
||||
return weights.columns();
|
||||
}
|
||||
|
||||
public int getInputCount() {
|
||||
return weights.coefficients.length;
|
||||
return weights.rows();
|
||||
}
|
||||
|
||||
public double[][] getLastOutput() {
|
||||
public MLMatrix getLastOutput() {
|
||||
return lastOutput;
|
||||
}
|
||||
|
||||
public void setWeights( double[][] newWeights ) {
|
||||
weights.coefficients = MLMath.copyMatrix(newWeights);
|
||||
}
|
||||
|
||||
public void adjustWeights( double[][] adjustment ) {
|
||||
weights.coefficients = MLMath.matrixAdd(weights.coefficients, adjustment);
|
||||
public void setWeights( MLMatrix newWeights ) {
|
||||
weights = newWeights.duplicate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return weights.toString() + "\n" + Arrays.toString(biases);
|
||||
return "weights:\n" + weights.toString() + "\nbiases:\n" + biases.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[][] apply( double[][] inputs ) {
|
||||
lastInput = inputs;
|
||||
lastOutput = MLMath.matrixApply(
|
||||
MLMath.biasAdd(
|
||||
MLMath.matrixMultiply(inputs, weights.coefficients),
|
||||
biases
|
||||
),
|
||||
activationFunction
|
||||
);
|
||||
public MLMatrix apply( MLMatrix inputs ) {
|
||||
lastInput = inputs.duplicate();
|
||||
lastOutput = inputs
|
||||
.multiplyAddBias(weights, biases)
|
||||
.applyInPlace(activationFunction);
|
||||
|
||||
if( next != null ) {
|
||||
return next.apply(lastOutput);
|
||||
} else {
|
||||
@@ -132,36 +145,41 @@ public class NeuronLayer implements Function<double[][], double[][]> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Function<V, double[][]> compose( Function<? super V, ? extends double[][]> before ) {
|
||||
public <V> Function<V, MLMatrix> compose( Function<? super V, ? extends MLMatrix> before ) {
|
||||
return ( in ) -> apply(before.apply(in));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> Function<double[][], V> andThen( Function<? super double[][], ? extends V> after ) {
|
||||
public <V> Function<MLMatrix, V> andThen( Function<? super MLMatrix, ? extends V> after ) {
|
||||
return ( in ) -> after.apply(apply(in));
|
||||
}
|
||||
|
||||
public void backprop( double[][] expected, double learningRate ) {
|
||||
double[][] error, delta, adjustment;
|
||||
public void backprop( MLMatrix expected, double learningRate ) {
|
||||
MLMatrix error, adjustment;
|
||||
if( next == null ) {
|
||||
error = MLMath.matrixSub(expected, this.lastOutput);
|
||||
error = expected.sub(lastOutput);
|
||||
} else {
|
||||
error = MLMath.matrixMultiply(expected, MLMath.matrixTranspose(next.weights.coefficients));
|
||||
error = expected.multiplyTransposed(next.weights);
|
||||
}
|
||||
|
||||
delta = MLMath.matrixScale(error, MLMath.matrixApply(this.lastOutput, this.activationFunctionDerivative));
|
||||
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(delta, learningRate);
|
||||
previous.backprop(error, learningRate);
|
||||
}
|
||||
|
||||
biases = MLMath.biasAdjust(biases, MLMath.matrixApply(delta, ( x ) -> learningRate * x));
|
||||
biases.addInPlace(
|
||||
error.colSums().scaleInPlace(
|
||||
-learningRate / (double) error.rows()
|
||||
)
|
||||
);
|
||||
|
||||
adjustment = MLMath.matrixMultiply(MLMath.matrixTranspose(lastInput), delta);
|
||||
adjustment = MLMath.matrixApply(adjustment, ( x ) -> learningRate * x);
|
||||
this.adjustWeights(adjustment);
|
||||
adjustment = lastInput.transposedMultiplyAndScale(error, learningRate);
|
||||
weights.addInPlace(adjustment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
public abstract class FilledShape extends StrokedShape {
|
||||
|
||||
protected Color fillColor = STD_FILLCOLOR;
|
||||
|
||||
public Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
public void setFillColor( Color color ) {
|
||||
fillColor = color;
|
||||
}
|
||||
|
||||
public void setFillColor( Color color, int alpha ) {
|
||||
setFillColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
public void setFillColor( int gray ) {
|
||||
setFillColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void noFill() {
|
||||
setFillColor(null);
|
||||
}
|
||||
|
||||
public void setFillColor( int gray, int alpha ) {
|
||||
setFillColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue ) {
|
||||
setFillColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setFillColor( int red, int green, int blue, int alpha ) {
|
||||
setFillColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public void resetFill() {
|
||||
setFillColor(STD_FILLCOLOR);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.ImageLoader;
|
||||
import schule.ngb.zm.util.io.ImageLoader;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
@@ -15,6 +15,8 @@ public class Rectangle extends Shape {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.anchor = Options.Direction.NORTHWEST;
|
||||
|
||||
//this.cacheEnabled = getClass().equals(Rectangle.class);
|
||||
}
|
||||
|
||||
public Rectangle( Rectangle pRechteck ) {
|
||||
@@ -24,9 +26,25 @@ public class Rectangle extends Shape {
|
||||
copyFrom(pRechteck);
|
||||
}
|
||||
|
||||
public Rectangle( Bounds pBounds ) {
|
||||
this(
|
||||
pBounds.x, pBounds.y,
|
||||
pBounds.width, pBounds.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein Rechteck zur Darstellung der
|
||||
* @param pShape
|
||||
*/
|
||||
public Rectangle( Shape pShape ) {
|
||||
this(pShape, true);
|
||||
}
|
||||
|
||||
public Rectangle( Shape pShape, boolean transformed ) {
|
||||
java.awt.Shape s = pShape.getShape();
|
||||
s = pShape.getTransform().createTransformedShape(s);
|
||||
if( transformed ) {
|
||||
s = pShape.getTransform().createTransformedShape(s);
|
||||
}
|
||||
Rectangle2D bounds = s.getBounds2D();
|
||||
x = bounds.getX();
|
||||
y = bounds.getY();
|
||||
|
||||
@@ -1,47 +1,118 @@
|
||||
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 java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public abstract class Shape extends FilledShape {
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* x-Koordinate der Form.
|
||||
*/
|
||||
protected double x;
|
||||
|
||||
/**
|
||||
* y-Koordinate der Form.
|
||||
*/
|
||||
protected double y;
|
||||
|
||||
/**
|
||||
* Rotation in Grad um den Punkt (x, y).
|
||||
*/
|
||||
protected double rotation = 0.0;
|
||||
|
||||
/**
|
||||
* Skalierungsfaktor.
|
||||
*/
|
||||
protected double scale = 1.0;
|
||||
|
||||
protected boolean visible = true;
|
||||
|
||||
/**
|
||||
* Ankerpunkt der Form.
|
||||
*/
|
||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
||||
|
||||
/**
|
||||
* Setzt die x- und y-Koordinate der Form auf 0.
|
||||
*/
|
||||
public Shape() {
|
||||
this(0.0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die x- und y-Koordinate der Form.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
public Shape( double x, double y ) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Shape() {
|
||||
this(0.0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die x-Koordinate der Form zurück.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die y-Koordinate der Form zurück.
|
||||
*
|
||||
* @return Die y-Koordinate.
|
||||
*/
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die y-Koordinate der Form.
|
||||
*
|
||||
* @param y Die neue y-Koordinate.
|
||||
*/
|
||||
public void setY( double y ) {
|
||||
this.y = y;
|
||||
}
|
||||
@@ -49,8 +120,9 @@ public abstract class Shape extends FilledShape {
|
||||
/**
|
||||
* Gibt die Breite dieser Form zurück.
|
||||
* <p>
|
||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung, <b>bevor</b>
|
||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
||||
* 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 kann mit
|
||||
* {@link #getBounds()} abgerufen werden.
|
||||
@@ -62,8 +134,9 @@ public abstract class Shape extends FilledShape {
|
||||
/**
|
||||
* Gibt die Höhe dieser Form zurück.
|
||||
* <p>
|
||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung, <b>bevor</b>
|
||||
* Drehungen und andere Transformationen auf sei angewandt wurden.
|
||||
* 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 kann mit
|
||||
* {@link #getBounds()} abgerufen werden.
|
||||
@@ -72,28 +145,74 @@ public abstract class Shape extends FilledShape {
|
||||
*/
|
||||
public abstract double getHeight();
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Rotation in Grad zurück.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
public void rotateTo( double angle ) {
|
||||
this.rotation = angle % 360;
|
||||
}
|
||||
|
||||
public void rotate( Point2D center, double angle ) {
|
||||
rotate(center.getX(), center.getY(), angle);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den aktuellen Skalierungsfaktor zurück.
|
||||
*
|
||||
* @return Der Skalierungsfaktor.
|
||||
*/
|
||||
public double getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
public void scale( double factor ) {
|
||||
scale = factor;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
public void toggle() {
|
||||
visible = !visible;
|
||||
public void scaleBy( double factor ) {
|
||||
scale(scale * factor);
|
||||
}
|
||||
|
||||
public Options.Direction getAnchor() {
|
||||
@@ -117,16 +236,26 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
}
|
||||
|
||||
protected static Point2D.Double getAnchorPoint( double width, double height, Options.Direction 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.
|
||||
*
|
||||
* @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;
|
||||
|
||||
// anchor == CENTER
|
||||
Point2D.Double anchorPoint = new Point2D.Double(
|
||||
return new Point2D.Double(
|
||||
wHalf + wHalf * anchor.x,
|
||||
hHalf + hHalf * anchor.y
|
||||
);
|
||||
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,12 +269,10 @@ public abstract class Shape extends FilledShape {
|
||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||
|
||||
// anchor == CENTER
|
||||
Point2D.Double anchorPoint = new Point2D.Double(
|
||||
return new Point2D.Double(
|
||||
wHalf * (anchor.x - this.anchor.x),
|
||||
hHalf * (anchor.y - this.anchor.y)
|
||||
);
|
||||
|
||||
return anchorPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,14 +291,22 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
|
||||
/**
|
||||
* Kopiert die Eigenschaften der übergebenen Form in diese.
|
||||
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
||||
* <p>
|
||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). Mit
|
||||
* dem Aufruf {@code super.copyFrom(shape)} sollten die Basiseigenschaften
|
||||
* kopiert werden.
|
||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises).
|
||||
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)}
|
||||
* die Basiseigenschaften kopieren.
|
||||
* <p>
|
||||
* Die Methode sollte so viele Eigenschaften wie möglich von der anderen
|
||||
* Form in diese kopieren. 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.
|
||||
* Implementierende Unterklassen können soweit sinnvoll auch andere Werte
|
||||
* übernehmen. Eine {@link Ellipse} kann beispielsweise auch die Breite und
|
||||
* Höhe eines {@link Rectangle} übernehmen.
|
||||
*
|
||||
* @param shape
|
||||
* @param shape Die Originalform, von der kopiert werden soll.
|
||||
*/
|
||||
public void copyFrom( Shape shape ) {
|
||||
if( shape != null ) {
|
||||
@@ -181,16 +316,55 @@ public abstract class Shape extends FilledShape {
|
||||
setStrokeWeight(shape.getStrokeWeight());
|
||||
setStrokeType(shape.getStrokeType());
|
||||
visible = shape.isVisible();
|
||||
rotation = shape.rotation;
|
||||
scale(shape.scale);
|
||||
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 sollte aber jede Form einen Konstruktor
|
||||
* besitzen, die alle Werte einer andern Form übernimmt. Die gezeigte
|
||||
* Implementierung dürfte daher im Regelfall ausreichend sein.
|
||||
*
|
||||
* @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, kann die
|
||||
* Methode {@code null} zurückgeben.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
@@ -273,7 +447,7 @@ public abstract class Shape extends FilledShape {
|
||||
}
|
||||
|
||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
||||
nextTo(shape, dir, STD_BUFFER);
|
||||
nextTo(shape, dir, DEFAULT_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,40 +469,6 @@ public abstract class Shape extends FilledShape {
|
||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||
}
|
||||
|
||||
public void scale( double factor ) {
|
||||
scale = factor;
|
||||
}
|
||||
|
||||
public void scaleBy( double factor ) {
|
||||
scale(scale * factor);
|
||||
}
|
||||
|
||||
public void rotate( double angle ) {
|
||||
this.rotation += angle % 360;
|
||||
}
|
||||
|
||||
public void rotateTo( double angle ) {
|
||||
this.rotation = angle % 360;
|
||||
}
|
||||
|
||||
public void rotate( Point2D center, double angle ) {
|
||||
rotate(center.getX(), center.getY(), angle);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*public void shear( double dx, double dy ) {
|
||||
verzerrung.shear(dx, dy);
|
||||
}*/
|
||||
@@ -375,17 +515,9 @@ public abstract class Shape extends FilledShape {
|
||||
shape = transform.createTransformedShape(shape);
|
||||
}
|
||||
|
||||
Color currentColor = graphics.getColor();
|
||||
if( fillColor != null && fillColor.getAlpha() > 0 ) {
|
||||
graphics.setColor(fillColor.getJavaColor());
|
||||
graphics.fill(shape);
|
||||
}
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.draw(shape);
|
||||
}
|
||||
java.awt.Color currentColor = graphics.getColor();
|
||||
fillShape(shape, graphics);
|
||||
strokeShape(shape, graphics);
|
||||
graphics.setColor(currentColor);
|
||||
}
|
||||
}
|
||||
@@ -396,8 +528,8 @@ public abstract class Shape extends FilledShape {
|
||||
* verglichen. Unterklassen überschreiben die Methode, um weitere
|
||||
* Eigenschaften zu berücksichtigen.
|
||||
* <p>
|
||||
* Die Eigenschaften von {@link FilledShape} und {@link StrokedShape} werden
|
||||
* nicht verglichen.
|
||||
* Die Eigenschaften, die durch {@link Fillable} und {@link Strokeable}
|
||||
* impliziert werden, werden nicht verglichen.
|
||||
*
|
||||
* @param o Ein anderes Objekt.
|
||||
* @return
|
||||
@@ -413,4 +545,43 @@ public abstract class Shape extends FilledShape {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -35,7 +36,7 @@ import java.util.List;
|
||||
* {@link #arrangeInGrid(int, Options.Direction, double, int)} und
|
||||
* {@link #align(Options.Direction)} verwendet werden, die jeweils die Position
|
||||
* der Formen in der Gruppe ändern und nicht die Position der Gruppe selbst (so
|
||||
* wie z.B. {@link #alignTo(Shape, Options.Direction)}.
|
||||
* wie z.B. {@link #alignTo(Shape, Options.Direction)}).
|
||||
*/
|
||||
public class ShapeGroup extends Shape {
|
||||
|
||||
@@ -43,7 +44,7 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
public static final int ARRANGE_COLS = 1;
|
||||
|
||||
private List<Shape> shapes;
|
||||
private final List<Shape> shapes;
|
||||
|
||||
private double groupWidth = -1.0;
|
||||
|
||||
@@ -51,7 +52,7 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
public ShapeGroup() {
|
||||
super();
|
||||
shapes = new ArrayList<>(10);
|
||||
shapes = Collections.synchronizedList(new ArrayList<>(10));
|
||||
}
|
||||
|
||||
public ShapeGroup( double x, double y ) {
|
||||
@@ -62,9 +63,7 @@ public class ShapeGroup extends Shape {
|
||||
public ShapeGroup( double x, double y, Shape... shapes ) {
|
||||
super(x, y);
|
||||
this.shapes = new ArrayList<>(shapes.length);
|
||||
for( Shape pShape : shapes ) {
|
||||
this.shapes.add(pShape);
|
||||
}
|
||||
Collections.addAll(this.shapes, shapes);
|
||||
}
|
||||
|
||||
public Shape copy() {
|
||||
@@ -99,9 +98,11 @@ public class ShapeGroup extends Shape {
|
||||
|
||||
public <ShapeType extends Shape> List<ShapeType> getShapes( Class<ShapeType> typeClass ) {
|
||||
LinkedList<ShapeType> list = new LinkedList<>();
|
||||
for( Shape s : shapes ) {
|
||||
if( typeClass.getClass().isInstance(s) ) {
|
||||
list.add((ShapeType) s);
|
||||
synchronized( shapes ) {
|
||||
for( Shape shape : shapes ) {
|
||||
if( typeClass.isInstance(shape) ) {
|
||||
list.add(typeClass.cast(shape));
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@@ -170,19 +171,15 @@ public class ShapeGroup extends Shape {
|
||||
int rows, cols;
|
||||
if( mode == ARRANGE_ROWS ) {
|
||||
rows = n;
|
||||
cols = (int) ceil(shapes.size() / n);
|
||||
cols = (int) ceil(shapes.size() / (double)n);
|
||||
} else {
|
||||
cols = n;
|
||||
rows = (int) ceil(shapes.size() / n);
|
||||
rows = (int) ceil(shapes.size() / (double)n);
|
||||
}
|
||||
|
||||
// Calculate grid cell size
|
||||
double maxHeight = shapes.stream().mapToDouble(
|
||||
( s ) -> s.getHeight()
|
||||
).reduce(0.0, Double::max);
|
||||
double maxWidth = shapes.stream().mapToDouble(
|
||||
( s ) -> s.getWidth()
|
||||
).reduce(0.0, Double::max);
|
||||
double maxHeight = shapes.stream().mapToDouble(Shape::getHeight).reduce(0.0, Double::max);
|
||||
double maxWidth = shapes.stream().mapToDouble(Shape::getWidth).reduce(0.0, Double::max);
|
||||
double halfHeight = maxHeight * .5;
|
||||
double halfWidth = maxWidth * .5;
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Drawable;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.Noise;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.FlatteningPathIterator;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public abstract class StrokedShape extends Constants implements Drawable {
|
||||
|
||||
protected Color strokeColor = STD_STROKECOLOR;
|
||||
|
||||
protected double strokeWeight = STD_STROKEWEIGHT;
|
||||
|
||||
protected Options.StrokeType strokeType = SOLID;
|
||||
|
||||
protected Stroke stroke = null;
|
||||
|
||||
public Color getStrokeColor() {
|
||||
return strokeColor;
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color ) {
|
||||
this.strokeColor = color;
|
||||
}
|
||||
|
||||
public void setStrokeColor( Color color, int alpha ) {
|
||||
setStrokeColor(new Color(color, alpha));
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray ) {
|
||||
setStrokeColor(gray, gray, gray, 255);
|
||||
}
|
||||
|
||||
public void noStroke() {
|
||||
setStrokeColor(null);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int gray, int alpha ) {
|
||||
setStrokeColor(gray, gray, gray, alpha);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue ) {
|
||||
setStrokeColor(red, green, blue, 255);
|
||||
}
|
||||
|
||||
public void setStrokeColor( int red, int green, int blue, int alpha ) {
|
||||
setStrokeColor(new Color(red, green, blue, alpha));
|
||||
}
|
||||
|
||||
public double getStrokeWeight() {
|
||||
return strokeWeight;
|
||||
}
|
||||
|
||||
public void setStrokeWeight( double weight ) {
|
||||
this.strokeWeight = weight;
|
||||
this.stroke = null;
|
||||
}
|
||||
|
||||
public Options.StrokeType getStrokeType() {
|
||||
return strokeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||
* {@link #DOTTED} und {@link #SOLID}.
|
||||
*
|
||||
* @param type
|
||||
*/
|
||||
public void setStrokeType( Options.StrokeType type ) {
|
||||
this.strokeType = type;
|
||||
this.stroke = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void draw( Graphics2D graphics );
|
||||
|
||||
/**
|
||||
* Erstellt ein {@link Stroke} Objekt für den Konturtyp.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected Stroke createStroke() {
|
||||
// TODO: Used global cached Stroke Objects?
|
||||
if( stroke == null ) {
|
||||
switch( strokeType ) {
|
||||
case DOTTED:
|
||||
stroke = new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||
break;
|
||||
case DASHED:
|
||||
stroke = new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND,
|
||||
10.0f, new float[]{5.0f}, 0.0f);
|
||||
break;
|
||||
case SOLID:
|
||||
default:
|
||||
stroke = new BasicStroke(
|
||||
(float) strokeWeight,
|
||||
BasicStroke.CAP_ROUND,
|
||||
BasicStroke.JOIN_ROUND);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stroke;
|
||||
}
|
||||
|
||||
public void resetStroke() {
|
||||
setStrokeColor(STD_STROKECOLOR);
|
||||
setStrokeWeight(STD_STROKEWEIGHT);
|
||||
setStrokeType(SOLID);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package schule.ngb.zm.shapes;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.util.FontLoader;
|
||||
import schule.ngb.zm.util.io.FontLoader;
|
||||
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Font;
|
||||
@@ -22,15 +22,16 @@ public class Text extends Shape {
|
||||
protected int width = 0, height = 0, ascent = 0;
|
||||
|
||||
public Text( double x, double y, String text ) {
|
||||
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE));
|
||||
this(x, y, text, new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE));
|
||||
}
|
||||
|
||||
public Text( double x, double y, String text, String fontname ) {
|
||||
super(x, y);
|
||||
Font userfont = FontLoader.loadFont(fontname);
|
||||
if( userfont != null ) {
|
||||
font = userfont;
|
||||
} else {
|
||||
font = new Font(Font.SANS_SERIF, Font.PLAIN, STD_FONTSIZE);
|
||||
font = new Font(Font.SANS_SERIF, Font.PLAIN, DEFAULT_FONTSIZE);
|
||||
}
|
||||
setText(text);
|
||||
fillColor = null;
|
||||
@@ -60,29 +61,58 @@ public class Text extends Shape {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setFont( String fontname ) {
|
||||
Font newFont = FontLoader.loadFont(fontname);
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setFont( Font newFont ) {
|
||||
//font = newFont.deriveFont(font.getStyle(), font.getSize2D());
|
||||
font = new Font(newFont.getFontName(), newFont.getStyle(), newFont.getSize());
|
||||
//font = newFont;
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public void setFont( String fontName ) {
|
||||
Font newFont = FontLoader.loadFont(fontName);
|
||||
if( newFont != null ) {
|
||||
setFont(newFont);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFont( Font newFont ) {
|
||||
font = newFont.deriveFont(font.getSize2D());
|
||||
public void setFont( String fontName, double fontSize ) {
|
||||
Font newFont = FontLoader.loadFont(fontName);
|
||||
if( newFont != null ) {
|
||||
setFont(newFont);
|
||||
}
|
||||
font = newFont.deriveFont((float) fontSize);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
public void setFont( String fontName, double fontSize, int style ) {
|
||||
Font newFont = FontLoader.loadFont(fontName);
|
||||
if( newFont != null ) {
|
||||
setFont(newFont);
|
||||
}
|
||||
font = newFont.deriveFont(style, (float) fontSize);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public void setFontsize( double size ) {
|
||||
public double getFontSize() {
|
||||
return font.getSize2D();
|
||||
}
|
||||
|
||||
public void setFontSize( double size ) {
|
||||
font = font.deriveFont((float) size);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public double getFontsize() {
|
||||
return font.getSize2D();
|
||||
public int getFontStyle() {
|
||||
return font.getStyle();
|
||||
}
|
||||
|
||||
public void setFontStyle( int fontStyle ) {
|
||||
font = font.deriveFont(fontStyle);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
@@ -151,19 +181,19 @@ public class Text extends Shape {
|
||||
|
||||
@Override
|
||||
public void copyFrom( Shape shape ) {
|
||||
super.copyFrom(shape);
|
||||
if( shape instanceof Text ) {
|
||||
Text pText = (Text) shape;
|
||||
this.text = pText.getText();
|
||||
this.font = pText.getFont();
|
||||
calculateBounds();
|
||||
}
|
||||
super.copyFrom(shape);
|
||||
calculateBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale( double factor ) {
|
||||
super.scale(factor);
|
||||
setFontsize(font.getSize2D() * factor);
|
||||
setFontSize(font.getSize2D() * factor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,7 +221,7 @@ public class Text extends Shape {
|
||||
if( strokeColor != null && strokeColor.getAlpha() > 0
|
||||
&& strokeWeight > 0.0 ) {
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.drawRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
@@ -9,6 +9,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class BarChart extends Rectangle {
|
||||
|
||||
public static String DEFAULT_LABEL = "%.2f";
|
||||
@@ -105,10 +106,10 @@ public class BarChart extends Rectangle {
|
||||
}
|
||||
|
||||
public void addValue( double pValue, Color pColor ) {
|
||||
addValue(pValue, pColor, String.format(DEFAULT_LABEL, pValue));
|
||||
addValue(pValue, String.format(DEFAULT_LABEL, pValue), pColor);
|
||||
}
|
||||
|
||||
public void addValue( double pValue, Color pColor, String pLabel ) {
|
||||
public void addValue( double pValue, String pLabel, Color pColor ) {
|
||||
addValue(new BasicChartValue(pValue, pLabel, pColor));
|
||||
}
|
||||
|
||||
@@ -288,7 +289,7 @@ public class BarChart extends Rectangle {
|
||||
barY += barHeight + gap;
|
||||
}
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
if( inverted ) {
|
||||
graphics.drawLine((int) width, 0, (int) width, (int) height);
|
||||
} else {
|
||||
@@ -360,7 +361,7 @@ public class BarChart extends Rectangle {
|
||||
}
|
||||
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
if( inverted ) {
|
||||
graphics.drawLine(0, 0, (int) width, 0);
|
||||
} else {
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
@@ -136,57 +136,36 @@ public class BasicChartValue implements ChartValue {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getX() {
|
||||
return xValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setValue( double pValue ) {
|
||||
this.value = pValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setLabel( String pLabel ) {
|
||||
this.label = pLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setColor( Color pColor ) {
|
||||
this.color = pColor;
|
||||
@@ -1,7 +1,6 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
|
||||
@@ -53,7 +52,7 @@ public class ChartAxes extends Rectangle {
|
||||
}
|
||||
|
||||
graphics.setColor(strokeColor.getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.drawLine(0, (int)(height), (int)((xMax-xMin) * xUnit), (int)(height));
|
||||
graphics.drawLine(0, (int)(height), 0, (int)(height - (yMax-yMin) * yUnit));
|
||||
if( showArrows ) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package schule.ngb.zm.charts;
|
||||
package schule.ngb.zm.shapes.charts;
|
||||
|
||||
import schule.ngb.zm.shapes.Rectangle;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Graphics2D;
|
||||
@@ -92,7 +91,7 @@ public class LineChart extends Rectangle {
|
||||
for( ChartValue lcv : val ) {
|
||||
if( drawLines && lastLcv != null ) {
|
||||
graphics.setColor(getStrokeColor().getJavaColor());
|
||||
graphics.setStroke(createStroke());
|
||||
graphics.setStroke(getStroke());
|
||||
graphics.drawLine((int)(lastLcv.getX()*xUnit), (int)(height - lastLcv.getValue()*yUnit), (int)(lcv.getX()*xUnit), (int)(height - lcv.getValue()*yUnit));
|
||||
drawDot(graphics, lastLcv, xUnit, yUnit);
|
||||
}
|
||||