Feel free to join the next Helmholtz Hacky Hour #26 on Wednesday, April 21, 2021 from 2PM to 3PM!

Commit 11c40524 authored by Martin Lange's avatar Martin Lange

table of contents, moved appendix to separate file

parent 0b719bbd
Pipeline #12100 passed with stage
in 12 seconds
......@@ -2,7 +2,20 @@
This projects demonstrates the use of an Entity-Component-System for the implementation of an individual-based model (IBM). We use Java and the ECS library [Artemis-odb](https://github.com/junkdog/artemis-odb) to build a simple grassing model.
The model description is structured following the ODD protocol (Grimm et al. 2006, 2010) to demonstrate the good fit between ECS and ODD.
The model description is structured following the ODD protocol (Grimm et al. 2006, 2010) to demonstrate the good fit of ECS and ODD.
**Contents**
* [How to use this project?](#how-to-use-this-project)
* [What聽is聽an聽ECS?](#what-is-an-ecs)
* [Model purpose](#model-purpose)
* [Entities, state variables and scales](#entities-state-variables-and-scales)
* [Process overview and scheduling](#process-overview-and-scheduling)
* [Design details](#design-details)
* [Initialization](#initialization)
* [Submodels](#submodels)
* [References](#references)
* [Appendix](#appendix)
<img title="Screenshot of the model" src="https://git.ufz.de/oesa/ecs-tutorial/uploads/aca134ea4e37bada52effa167172d51a/image.png" alt="Screenshot of the model" width="400">
......@@ -14,7 +27,7 @@ See the **[How to](md/howto.md)** for details on the possible ways to use this p
## What is an ECS?
## Purpose
## Model purpose
The purpose of this model is to demonstrate the use of Entity-Component-Systems for individual-based models (IBMs).
......@@ -204,10 +217,6 @@ private static void createEntities(World world, Random rng) {
}
```
## Input data
The model uses no input data.
## Submodels
Submodels are described in their order of execution.
......@@ -492,239 +501,9 @@ Grimm V, Berger U, DeAngelis DL, Polhill G, Giske J, Railsback SF. 2010. **The O
## Appendix
This section describes code that is not vital for understanding the model or the ECS concept.
### Graphics
The graphics system draws the amount of grass per cell by shades of green. Further, it draws grassers as triangles pointing in the direction of their heading. Grassers are coloured according their current behaviour: white for grassing, yellow for searching.
```java
/// file:src/main/java/grassing/sys/Graphics.java
package grassing.sys;
import com.artemis.Aspect;
import com.artemis.BaseSystem;
import com.artemis.ComponentMapper;
import com.artemis.EntitySubscription;
import com.artemis.annotations.Wire;
import com.artemis.utils.IntBag;
import grassing.comp.*;
import grassing.res.Grass;
import grassing.util.Grid;
import grassing.util.MathUtil;
import javax.swing.*;
import java.awt.*;
public class Graphics extends BaseSystem {
private EntitySubscription grasserSubs;
protected ComponentMapper<Position> mPosition;
protected ComponentMapper<Heading> mHeading;
protected ComponentMapper<IsGrassing> mGrassing;
protected ComponentMapper<IsSearching> mSearching;
@Wire Grass grass;
final private int cellSize;
private Canvas canvas;
public Graphics(int cellSize) {
super();
this.cellSize = cellSize;
}
@Override
protected void initialize() {
grasserSubs = world.getAspectSubscriptionManager()
.get(Aspect.all(Position.class).one(IsGrassing.class, IsSearching.class));
var frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
canvas = new Canvas();
var dim = new Dimension(grass.grass.width * cellSize, grass.grass.height * cellSize);
canvas.setPreferredSize( dim );
frame.add(canvas);
frame.pack();
frame.setVisible(true);
}
@Override
protected void processSystem() {
canvas.paintImmediately(0, 0, grass.grass.width * cellSize, grass.grass.height * cellSize);
}
class Canvas extends JPanel {
@Override
public void paint(java.awt.Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
Grid.Float grass = Graphics.this.grass.grass;
int s = Graphics.this.cellSize;
setBackground(Color.BLACK);
for(int y=0; y<grass.height; y++) {
for(int x=0; x<grass.width; x++) {
g2d.setPaint(new Color(0f, 0.7f * grass.get(x, y), 0f));
g2d.fillRect(x*s, y*s, s, s);
}
}
int[] x = new int[3];
int[] y = new int[3];
IntBag grassers = Graphics.this.grasserSubs.getEntities();
for (int i = 0; i < grassers.size(); i++) {
int entity = grassers.get(i);
var pos = mPosition.get(entity);
var head = mHeading.get(entity);
if(mGrassing.has(entity)) {
g2d.setPaint(Color.WHITE);
} else if(mSearching.has(entity)) {
g2d.setPaint(Color.ORANGE);
} else {
g2d.setPaint(Color.MAGENTA);
}
//g2d.fillOval((int) (pos.x*s)-1, (int) (pos.y*s)-1, 2, 2);
var f = MathUtil.heading(head.angle);
var r = MathUtil.heading(head.angle + 0.5f * (float) Math.PI);
x[0] = (int) ((pos.x + f.x) * s);
y[0] = (int) ((pos.y + f.y) * s);
x[1] = (int) ((pos.x + 0.4f * r.x) * s);
y[1] = (int) ((pos.y + 0.4f * r.y) * s);
x[2] = (int) ((pos.x - 0.4f * r.x) * s);
y[2] = (int) ((pos.y - 0.4f * r.y) * s);
g2d.fillPolygon(x, y, 3);
}
}
}
}
```
### Utilities
```java
/// file:src/main/java/grassing/util/Grid.java
package grassing.util;
public abstract class Grid {
final public int width;
final public int height;
final public int length;
protected final Object data;
protected Grid(Object data, int width, int height) {
this.data = data;
this.width = width;
this.height = height;
this.length = width * height;
}
public final int getIndex(int x, int y) {
return (y * width + x);
}
public final boolean contains(int x, int y) {
return x >= 0 && y >= 0 && x < width && y < height;
}
public static final class Float extends Grid {
public Float(int width, int height) {
super((Object) (new float[width * height]), width, height);
}
private float[] getData() {
return (float[]) data;
}
public final float get(int idx) {
return getData()[idx];
}
public final float get(int x, int y) {
return getData()[getIndex(x, y)];
}
public final void set(int idx, float value) {
getData()[idx] = value;
}
public final void set(int x, int y, float value) {
getData()[getIndex(x, y)] = value;
}
}
}
```
```java
/// file:src/main/java/grassing/util/MathUtil.java
package grassing.util;
The [Appendix](md/appendix.md) describes code that is not vital for understanding the model or the ECS concept.
public abstract class MathUtil {
public static Point heading(float angle) {
return new Point((float) Math.cos(angle), (float) Math.sin(angle));
}
public static float deg2rad(float deg) {
return (float) (deg * Math.PI / 180f);
}
}
```
```java
/// file:src/main/java/grassing/util/Point.java
package grassing.util;
public class Point {
public float x;
public float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
}
```
### Dependencies with Gradle
[Gradle](https://gradle.org/) is used as build tool and for dependency management.
```groovy
/// file:build.gradle
apply plugin: 'application'
sourceCompatibility = 11
targetCompatibility = 11
repositories {
mavenCentral()
}
dependencies {
implementation group: 'net.onedaybeard.artemis', name: 'artemis-odb', version: '2.3.0'
}
application {
getMainClass().set('grassing.Main')
}
```
```groovy
/// file:settings.gradle
rootProject.name = 'Grassing'
```
### Git ignore file
Regarding VCS/Git, we ignore files generated by Gradle, as well as build output.
```
/// file:.gitignore
.gradle
build
```
* [Graphics](md/appendix.md#graphics)
* [Utilities](md/appendix.md#utilities)
* [Gradle files](md/appendix.md#gradle-files)
* [Git ignore file](md/appendix.md#git-ignore-file)
# Appendix
This section describes code that is not vital for understanding the model or the ECS concept.
**Contents**
- [Graphics](#graphics)
- [Utilities](#utilities)
- [Gradle files](#gradle-files)
- [Git ignore file](#git-ignore-file)
## Graphics
The graphics system draws the amount of grass per cell by shades of green. Further, it draws grassers as triangles pointing in the direction of their heading. Grassers are coloured according their current behaviour: white for grassing, yellow for searching.
```java
/// file:src/main/java/grassing/sys/Graphics.java
package grassing.sys;
import com.artemis.Aspect;
import com.artemis.BaseSystem;
import com.artemis.ComponentMapper;
import com.artemis.EntitySubscription;
import com.artemis.annotations.Wire;
import com.artemis.utils.IntBag;
import grassing.comp.*;
import grassing.res.Grass;
import grassing.util.Grid;
import grassing.util.MathUtil;
import javax.swing.*;
import java.awt.*;
public class Graphics extends BaseSystem {
private EntitySubscription grasserSubs;
protected ComponentMapper<Position> mPosition;
protected ComponentMapper<Heading> mHeading;
protected ComponentMapper<IsGrassing> mGrassing;
protected ComponentMapper<IsSearching> mSearching;
@Wire Grass grass;
final private int cellSize;
private Canvas canvas;
public Graphics(int cellSize) {
super();
this.cellSize = cellSize;
}
@Override
protected void initialize() {
grasserSubs = world.getAspectSubscriptionManager()
.get(Aspect.all(Position.class).one(IsGrassing.class, IsSearching.class));
var frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
canvas = new Canvas();
var dim = new Dimension(grass.grass.width * cellSize, grass.grass.height * cellSize);
canvas.setPreferredSize( dim );
frame.add(canvas);
frame.pack();
frame.setVisible(true);
}
@Override
protected void processSystem() {
canvas.paintImmediately(0, 0, grass.grass.width * cellSize, grass.grass.height * cellSize);
}
class Canvas extends JPanel {
@Override
public void paint(java.awt.Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
Grid.Float grass = Graphics.this.grass.grass;
int s = Graphics.this.cellSize;
setBackground(Color.BLACK);
for(int y=0; y<grass.height; y++) {
for(int x=0; x<grass.width; x++) {
g2d.setPaint(new Color(0f, 0.7f * grass.get(x, y), 0f));
g2d.fillRect(x*s, y*s, s, s);
}
}
int[] x = new int[3];
int[] y = new int[3];
IntBag grassers = Graphics.this.grasserSubs.getEntities();
for (int i = 0; i < grassers.size(); i++) {
int entity = grassers.get(i);
var pos = mPosition.get(entity);
var head = mHeading.get(entity);
if(mGrassing.has(entity)) {
g2d.setPaint(Color.WHITE);
} else if(mSearching.has(entity)) {
g2d.setPaint(Color.ORANGE);
} else {
g2d.setPaint(Color.MAGENTA);
}
//g2d.fillOval((int) (pos.x*s)-1, (int) (pos.y*s)-1, 2, 2);
var f = MathUtil.heading(head.angle);
var r = MathUtil.heading(head.angle + 0.5f * (float) Math.PI);
x[0] = (int) ((pos.x + f.x) * s);
y[0] = (int) ((pos.y + f.y) * s);
x[1] = (int) ((pos.x + 0.4f * r.x) * s);
y[1] = (int) ((pos.y + 0.4f * r.y) * s);
x[2] = (int) ((pos.x - 0.4f * r.x) * s);
y[2] = (int) ((pos.y - 0.4f * r.y) * s);
g2d.fillPolygon(x, y, 3);
}
}
}
}
```
## Utilities
```java
/// file:src/main/java/grassing/util/Grid.java
package grassing.util;
public abstract class Grid {
final public int width;
final public int height;
final public int length;
protected final Object data;
protected Grid(Object data, int width, int height) {
this.data = data;
this.width = width;
this.height = height;
this.length = width * height;
}
public final int getIndex(int x, int y) {
return (y * width + x);
}
public final boolean contains(int x, int y) {
return x >= 0 && y >= 0 && x < width && y < height;
}
public static final class Float extends Grid {
public Float(int width, int height) {
super((Object) (new float[width * height]), width, height);
}
private float[] getData() {
return (float[]) data;
}
public final float get(int idx) {
return getData()[idx];
}
public final float get(int x, int y) {
return getData()[getIndex(x, y)];
}
public final void set(int idx, float value) {
getData()[idx] = value;
}
public final void set(int x, int y, float value) {
getData()[getIndex(x, y)] = value;
}
}
}
```
```java
/// file:src/main/java/grassing/util/MathUtil.java
package grassing.util;
public abstract class MathUtil {
public static Point heading(float angle) {
return new Point((float) Math.cos(angle), (float) Math.sin(angle));
}
public static float deg2rad(float deg) {
return (float) (deg * Math.PI / 180f);
}
}
```
```java
/// file:src/main/java/grassing/util/Point.java
package grassing.util;
public class Point {
public float x;
public float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
}
```
## Gradle files
[Gradle](https://gradle.org/) is used as build tool and for dependency management.
```groovy
/// file:build.gradle
apply plugin: 'application'
sourceCompatibility = 11
targetCompatibility = 11
repositories {
mavenCentral()
}
dependencies {
implementation group: 'net.onedaybeard.artemis', name: 'artemis-odb', version: '2.3.0'
}
application {
getMainClass().set('grassing.Main')
}
```
```groovy
/// file:settings.gradle
rootProject.name = 'Grassing'
```
## Git ignore file
Regarding VCS/Git, we ignore files generated by Gradle, as well as build output.
```
/// file:.gitignore
.gradle
build
```
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment