diff --git a/src/03-simulation/DemoSimulation.ts b/src/03-simulation/DemoSimulation.ts
index 94def8985f907c323a5fa87d86078135b8671c37..bc7d53f765be8589105db808b16082452bc59ab7 100644
--- a/src/03-simulation/DemoSimulation.ts
+++ b/src/03-simulation/DemoSimulation.ts
@@ -1,12 +1,12 @@
 import { PipelineData } from "../core/Pipeline";
 import { Simulation } from "./Simulation";
-import { SimulationData } from './SimulationData';
+import { SimData } from './SimData';
 
 class SimulationDemo extends PipelineData {
     public constructor() { super(); }
 
     data(): void {
-        const data = new SimulationData();
+        const data = new SimData();
         this.addGUIElement(data);
         this.addRenderable(data);
 
diff --git a/src/03-simulation/SimData.ts b/src/03-simulation/SimData.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e2989ba2e3d6a6aec1d94f6e2fb31a020018f091
--- /dev/null
+++ b/src/03-simulation/SimData.ts
@@ -0,0 +1,546 @@
+import * as THREE from 'three';
+import { GUI } from 'dat.gui';
+import { picked, PipelineGUI, PipelineRenderable } from '../core/Pipeline';
+import { BendingSpring, ShearSpring, Spring, SpringConstants, StructuralSpring, } from './physics/Springs';
+import { Particle } from './physics/particle';
+import { EulerIntegrator, Integrator, MidpointIntegrator, RungeKuttaIntegrator } from './physics/Integrators';
+
+document.addEventListener('keydown', (event: KeyboardEvent) => {
+
+    if (event.key === 'e') {
+        SimData.switchParticlePhysics();
+    } else if (event.key === 'w') {
+        SimData.paused = !SimData.paused;
+    }
+});
+
+class SimData implements PipelineGUI, PipelineRenderable {
+
+    public static container: THREE.Group = new THREE.Group();
+    public static paused: boolean = false;
+
+    // constants
+    public static readonly MAX_TOTAL_MASS = 10;
+    public static readonly MIN_TOTAL_MASS = 0.1;
+
+    public static readonly MAX_WIDTH = 5;
+    public static readonly MIN_WIDTH = 0.01;
+
+    public static readonly MAX_HEIGHT = 2;
+    public static readonly MIN_HEIGHT = 0.01;
+
+    public static readonly MAX_RESOLUTION = 20;
+    public static readonly MIN_RESOLUTION = 2;
+
+    public static readonly MAX_SPRING_CONSTANT = 10;
+    public static readonly MIN_SPRING_CONSTANT = 0.1;
+
+    public static readonly INTEGRATOR_OPTIONS = { 'Euler': 0, 'Midpoint': 1, 'RungeKutta': 2 }
+    public static readonly STEP_SIZE_OPTIONS = { 'Fixed': 0, 'Adaptive': 1, }
+
+    public static readonly MAX_EXPONENT = 10;
+    public static readonly MIN_EXPONENT = -10;
+
+    // general
+    public static totalMass: number = 1;
+    public static width: number = .2;
+    public static height: number = .2;
+    public static resolution: number = 5;
+
+    // integrator
+    public static readonly INTEGRATORS: Integrator[] = [
+        new EulerIntegrator(),
+        new MidpointIntegrator(),
+        new RungeKuttaIntegrator()
+    ]
+
+    public static integrationMethod: number = 1;
+    public static stepSizeMethod: number = 0;
+
+    public static stepSize: number = .001;
+
+    public static hMax: number = .05;
+    public static hMin: number = .00005;
+
+    public static hFactor: number = 5;
+    public static hMaxExponent: number = -2;
+    public static hMinExponent: number = -5;
+
+    public static eOrder: number = 3;
+    public static eFactor: number = 1;
+    public static eMaxExponent: number = -14;
+
+    public static damping: number = .1;
+
+
+    // Force Field
+    public static forceFieldStrength: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
+    public static forceFieldPosition: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
+
+    // particls
+    public static pList?= new Array<Array<Particle>>();
+    public static pColorDefault: number = 0xffffff;
+    public static pColorNoPhysix: number = 0xff00ff;
+
+    // springs
+    public static sList?= new Array<Spring>();
+    public static sConstants: SpringConstants = { structural: 5, shear: 2, bend: 4 };
+    public static sColorStructural: number = 0xff0000;
+    public static sColorShear: number = 0x00ff00;
+    public static sColorBend: number = 0x0000ff;
+
+    public static resetForceField(): void {
+        SimData.forceFieldStrength.x = 0;
+        SimData.forceFieldStrength.y = 0;
+        SimData.forceFieldStrength.z = 0;
+    }
+
+    public static generateTextile(): void {
+        // 1. reset pList
+        delete SimData.pList;
+        SimData.pList = new Array<Array<Particle>>();
+
+        // 2. generate particles
+        for (let i = 0; i < SimData.resolution; i++) {
+            SimData.pList[i] = new Array<Particle>();
+            for (let j = 0; j < SimData.resolution; j++) {
+                const p = new Particle({
+                    position: new THREE.Vector3(
+                        SimData.width * (i / SimData.resolution) - SimData.width / 2,
+                        SimData.height * (j / SimData.resolution) - SimData.height / 2,
+                        0),
+                    velocity: new THREE.Vector3(0, 0, 0),
+                    mass: SimData.totalMass / (SimData.resolution * SimData.resolution),
+                    usePhysics: true,
+                    references: [],
+                    color: SimData.pColorDefault,
+                    wireframe: true
+                });
+                SimData.pList[i].push(p);
+            }
+        }
+
+        for (let i = 0; i < SimData.resolution; i++) {
+            SimData.pList[i][SimData.resolution - 1].usePhysics = false;
+        }
+
+        // 3. set particle mass and color
+        SimData.changedParticleSize();
+        SimData.changedParticleColor();
+
+        // 4. add particles to container
+        const pLinList = new Array<THREE.Mesh>();
+        SimData.pList.forEach(pSub => {
+            pSub.forEach(p => {
+                pLinList.push(p.mesh);
+            });
+        });
+
+        // 5. reset sList
+        delete SimData.sList;
+        SimData.sList = new Array<Spring>();
+
+        // 6. generate springs
+        for (let i = 0; i < SimData.resolution; i++) {
+            for (let j = 0; j < SimData.resolution; j++) {
+
+                // structural springs (horizontal)
+                if (i < SimData.resolution - 1) {
+                    const pA = SimData.pList[i][j];
+                    const pB = SimData.pList[i + 1][j];
+
+                    const s = new StructuralSpring({
+                        positionA: pA.position,
+                        positionB: pB.position,
+                        constants: SimData.sConstants,
+                        color: SimData.sColorStructural,
+                    });
+                    SimData.sList.push(s);
+                    s.line.renderOrder = 2;
+
+                    pA.references.push(s);
+                    pB.references.push(s);
+                }
+
+                // structural springs (vertical)
+                if (j < SimData.resolution - 1) {
+                    const pA = SimData.pList[i][j];
+                    const pB = SimData.pList[i][j + 1];
+
+                    const s = new StructuralSpring({
+                        positionA: pA.position,
+                        positionB: pB.position,
+                        constants: SimData.sConstants,
+                        color: SimData.sColorStructural,
+                    });
+                    SimData.sList.push(s);
+                    s.line.renderOrder = 2;
+
+                    pA.references.push(s);
+                    pB.references.push(s);
+                }
+
+                // shear springs (bottom-left to top-right)
+                if (i < SimData.resolution - 1 && j < SimData.resolution - 1) {
+                    const pA = SimData.pList[i][j];
+                    const pB = SimData.pList[i + 1][j + 1];
+
+                    const s = new ShearSpring({
+                        positionA: pA.position,
+                        positionB: pB.position,
+                        constants: SimData.sConstants,
+                        color: SimData.sColorShear,
+                    });
+                    SimData.sList.push(s);
+                    s.line.renderOrder = 1;
+
+                    pA.references.push(s);
+                    pB.references.push(s);
+                }
+
+                // shear springs (top-left to bottom-right)
+                if (i < SimData.resolution - 1 && j > 0) {
+                    const pA = SimData.pList[i][j];
+                    const pB = SimData.pList[i + 1][j - 1];
+
+                    const s = new ShearSpring({
+                        positionA: pA.position,
+                        positionB: pB.position,
+                        constants: SimData.sConstants,
+                        color: SimData.sColorShear,
+                    });
+                    SimData.sList.push(s);
+                    s.line.renderOrder = 1;
+
+                    pA.references.push(s);
+                    pB.references.push(s);
+                }
+
+                // bend springs (horizontal)
+                if (i < SimData.resolution - 2) {
+                    const pA = SimData.pList[i][j];
+                    const pB = SimData.pList[i + 2][j];
+
+                    const s = new BendingSpring({
+                        positionA: pA.position,
+                        positionB: pB.position,
+                        constants: SimData.sConstants,
+                        color: SimData.sColorBend,
+                    });
+                    SimData.sList.push(s);
+                    s.line.renderOrder = 0;
+
+                    pA.references.push(s);
+                    pB.references.push(s);
+                }
+
+                // bend springs (vertical)
+                if (j < SimData.resolution - 2) {
+                    const pA = SimData.pList[i][j];
+                    const pB = SimData.pList[i][j + 2];
+
+                    const s = new BendingSpring({
+                        positionA: pA.position,
+                        positionB: pB.position,
+                        constants: SimData.sConstants,
+                        color: SimData.sColorBend,
+                    });
+                    SimData.sList.push(s);
+                    s.line.renderOrder = 0;
+
+                    pA.references.push(s);
+                    pB.references.push(s);
+                }
+            }
+        }
+
+        // 7. add springs to container
+        const sLinList = new Array<THREE.Line>();
+        SimData.sList.forEach(s => {
+            sLinList.push(s.line);
+        });
+
+        // 8. add particles and springs to container
+        while (SimData.container.children.length > 0) {
+            let rm = SimData.container.children.pop();
+            if (rm instanceof THREE.Line) {
+                rm.geometry.dispose();
+                rm.material.dispose();
+            } else if (rm instanceof THREE.Mesh) {
+                rm.geometry.dispose();
+                rm.material.dispose();
+            }
+        }
+
+        SimData.container.add(...pLinList, ...sLinList);
+    }
+
+    public static changedParticleSize(): void {
+        // 1. calculate current mass
+        const pCount = SimData.resolution * SimData.resolution;
+        const pMass = SimData.totalMass / pCount;
+
+        // 1.1 update particle mass
+        SimData.pList?.forEach(pSub => {
+            pSub.forEach(p => {
+                p.mass = pMass;
+            });
+        });
+
+        // 2. calculate max pSize
+        const maxSize = Math.min(SimData.width, SimData.height);
+
+        // 3. normalize pMass
+        const pMassNorm = (pMass - SimData.MIN_TOTAL_MASS) /
+            (SimData.MAX_TOTAL_MASS + SimData.MIN_TOTAL_MASS);
+
+        // 4. calculate pSize
+        const pSize = maxSize * pMassNorm;
+
+        // 5. set pSize
+        SimData.pList?.forEach(pSub => { pSub.forEach(p => p.setRadius(pSize)) });
+    }
+
+    public static changedDimensions(): void {
+        if (!SimData.pList)
+            return;
+
+        // 1. reset positions of particles
+        for (let i = 0; i < SimData.resolution; i++) {
+            for (let j = 0; j < SimData.resolution; j++) {
+                SimData.pList[i][j].setPosition(
+                    SimData.width * (i / SimData.resolution) - SimData.width / 2,
+                    SimData.height * (j / SimData.resolution) - SimData.height / 2,
+                    0);
+            }
+        }
+
+        // 2. set size based on particle mass
+        SimData.changedParticleSize();
+    }
+
+    public static changedParticleColor(): void {
+        // iterate over all particles and set color based on current internal state
+        SimData.pList?.forEach(pSub => {
+            pSub.forEach(p => {
+                if (p.usePhysics) {
+                    p.setColor(SimData.pColorDefault);
+                } else {
+                    p.setColor(SimData.pColorNoPhysix);
+                }
+            })
+        });
+    }
+
+    public static changedSpringColor(): void {
+        // iterate over all particles and set color based on current internal state
+        SimData.sList?.forEach(s => {
+            if (s instanceof StructuralSpring) {
+                s.setColor(SimData.sColorStructural);
+            } else if (s instanceof ShearSpring) {
+                s.setColor(SimData.sColorShear);
+            } else if (s instanceof BendingSpring) {
+                s.setColor(SimData.sColorBend);
+            }
+        });
+    }
+
+    public static switchParticlePhysics(): void {
+        // 1. is picked object not null
+        if (!picked) {
+            return;
+        }
+
+        // 2. compare mesh to all particles => find particle and change usePhysics
+        SimData.pList?.forEach(pSub => {
+            pSub.forEach(p => {
+                if (p.mesh === picked) {
+                    p.usePhysics = !p.usePhysics;
+                    p.setColor(p.usePhysics ? SimData.pColorDefault
+                        : SimData.pColorNoPhysix);
+                }
+            })
+        });
+    }
+
+    gui(gui: GUI): void {
+
+        // GENERAL SETTINGS        
+        const general = gui.addFolder('General');
+        general.open();
+
+        general.add(SimData, 'paused')
+            .name("Pause Simulation")
+            .listen();
+
+        general.add(SimData, 'switchParticlePhysics')
+            .name('Switch Particle Physics');
+
+        general.add(SimData, 'totalMass')
+            .name("Total Mass")
+            .max(SimData.MAX_TOTAL_MASS)
+            .min(SimData.MIN_TOTAL_MASS)
+            .step(0.1)
+            .onChange(SimData.changedParticleSize);
+
+        general.add(SimData, 'width')
+            .name("Width")
+            .max(SimData.MAX_WIDTH)
+            .min(SimData.MIN_WIDTH)
+            .step(0.1)
+            .onChange(SimData.changedDimensions);
+
+        general.add(SimData, 'height')
+            .name("Height")
+            .max(SimData.MAX_HEIGHT)
+            .min(SimData.MIN_HEIGHT)
+            .step(0.1)
+            .onChange(SimData.changedDimensions);
+
+        general.add(SimData, 'resolution')
+            .name("Resolution")
+            .max(SimData.MAX_RESOLUTION)
+            .min(SimData.MIN_RESOLUTION)
+            .step(1)
+            .onFinishChange(SimData.generateTextile);
+
+        // INTEGRATOR SETTINGS
+        const integrator = general.addFolder('Integrator Settings');
+        integrator.open();
+
+        integrator.add(SimData, 'integrationMethod')
+            .options(SimData.INTEGRATOR_OPTIONS)
+            .name("Method")
+            .onChange((e: number) => {
+                if (e === 0) {
+                    SimData.eOrder = 2;
+                } else if (e === 1) {
+                    SimData.eOrder = 3;
+                } else if (e === 2) {
+                    SimData.eOrder = 5;
+                }
+            });
+
+        integrator.add(SimData, 'stepSizeMethod')
+            .options(SimData.STEP_SIZE_OPTIONS)
+            .name("Step Size Method")
+
+        integrator.add(SimData, 'stepSize')
+            .name("Fixed Size")
+            .step(0.00001)
+
+
+        const adaptiveSettings = integrator.addFolder('Adaptive Settings');
+        adaptiveSettings.open();
+
+        const stepSizeSettings = adaptiveSettings.addFolder('Step Size');
+
+        stepSizeSettings.add(SimData, 'hFactor')
+            .name("h Factor")
+            .step(1)
+            .max(9)
+            .min(1)
+
+        stepSizeSettings.add(SimData, 'hMaxExponent')
+            .name("h Max Exponent")
+            .step(1)
+            .max(SimData.MAX_EXPONENT)
+            .min(SimData.MIN_EXPONENT)
+
+        stepSizeSettings.add(SimData, 'hMinExponent')
+            .name("h Min Exponent")
+            .step(1)
+            .max(SimData.MAX_EXPONENT)
+            .min(SimData.MIN_EXPONENT)
+
+        const errorSettings = adaptiveSettings.addFolder('Error');
+
+        errorSettings.add(SimData, 'eFactor')
+            .name("e Factor")
+            .step(1)
+            .max(9)
+            .min(1)
+
+        errorSettings.add(SimData, 'eMaxExponent')
+            .name("e Max Exponent")
+            .step(1)
+            .max(-10)
+            .min(-20)
+
+        // SPRING SETTINGS
+        const springs = general.addFolder('Spring Constants');
+        springs.open();
+
+        springs.add(SimData.sConstants, 'structural')
+            .name("Structural")
+            .step(0.1)
+            .max(SimData.MAX_SPRING_CONSTANT)
+            .min(SimData.MIN_SPRING_CONSTANT);
+
+        springs.add(SimData.sConstants, 'shear')
+            .name("Shear")
+            .step(0.1)
+            .max(SimData.MAX_SPRING_CONSTANT)
+            .min(SimData.MIN_SPRING_CONSTANT);
+
+        springs.add(SimData.sConstants, 'bend')
+            .name("Bend")
+            .step(0.1)
+            .max(SimData.MAX_SPRING_CONSTANT)
+            .min(SimData.MIN_SPRING_CONSTANT);
+
+        springs.add(SimData, 'damping')
+            .name("Damping Factor")
+            .step(0.001)
+
+        // APPEARANCE SETTINGS
+        const appearance = general.addFolder('Appearance');
+
+        appearance.addColor(SimData, 'pColorDefault')
+            .name('Particle Default')
+            .onChange(SimData.changedParticleColor);
+
+        appearance.addColor(SimData, 'pColorNoPhysix')
+            .name('Particle Pinned')
+            .onChange(SimData.changedParticleColor);
+
+        appearance.addColor(SimData, 'sColorStructural')
+            .name('Structural')
+            .onChange(SimData.changedSpringColor);
+
+        appearance.addColor(SimData, 'sColorShear')
+            .name('Shear')
+            .onChange(SimData.changedSpringColor);
+
+        appearance.addColor(SimData, 'sColorBend')
+            .name('Bend')
+            .onChange(SimData.changedSpringColor);
+
+        const forceField = general.addFolder('Point Force Field');
+
+        const ffStrength = forceField.addFolder('Strength');
+        ffStrength.open();
+
+        ffStrength.add(SimData, 'resetForceField')
+            .name('Reset Strength')
+            .onFinishChange(() => ffStrength.__controllers.forEach(c => c.updateDisplay()));
+        ffStrength.add(SimData.forceFieldStrength, 'x',).step(0.01);
+        ffStrength.add(SimData.forceFieldStrength, 'y',).step(0.01);
+        ffStrength.add(SimData.forceFieldStrength, 'z',).step(0.01);
+
+        const ffPosition = forceField.addFolder('Position');
+        ffPosition.open();
+
+        ffPosition.add(SimData.forceFieldPosition, 'x',).step(0.01);
+        ffPosition.add(SimData.forceFieldPosition, 'y',).step(0.01);
+        ffPosition.add(SimData.forceFieldPosition, 'z',).step(0.01);
+    }
+
+    object(): THREE.Object3D<THREE.Event> {
+        return SimData.container;
+    }
+}
+
+export { SimData };
+
+
diff --git a/src/03-simulation/Simulation.ts b/src/03-simulation/Simulation.ts
index 340ece80290ba376ac9781fc762ce579ac19669b..c15d5cf6161a417ac3510e497934b8de3030ab44 100644
--- a/src/03-simulation/Simulation.ts
+++ b/src/03-simulation/Simulation.ts
@@ -1,90 +1,73 @@
 import * as THREE from 'three';
 import { PipelineObserver } from "../core/Pipeline";
 import { Particle } from './physics/particle';
-import { SimulationData } from "./SimulationData";
-
-
-function adaptive(x: Array<number>, h: number, eMax = 1e-10, n: number = 5): number {
-    const rk4 = SimulationData.INTEGRATORS[2];
-
-    // 1. calculate approximate
-    let xFull = rk4.step([...x], h, Simulation.derivation);
-    let xHalf = rk4.step([...x], h/2, Simulation.derivation);
-    xHalf = rk4.step(xHalf, h/2, Simulation.derivation);
-
-    // 2. calculate error
-    let eList = [];
-    for (let i = 0; i < x.length; i++) {
-        eList.push(Math.abs(xFull[i] - xHalf[i]));
-    }
-    let e = 0;
-    for (let i = 0; i < eList.length; i++) {
-        e += Math.pow(eList[i], 2);
-    }
-    
-    if (e < 1e-8) {
-        return h;
-    }
-    
-    e = Math.sqrt(e); // total error
-
-    // 3. e = k * h^n
-    //  e.g. h = 0.001 mit n = 5 => e = k * 0.001^5
-    //   => k = e / h^n
-    //   => k * h^n = eMax
-    //  <=> h = pow(eMax / k, 1/n)
-    //   => h = pow(eMax / (e / h^n), 1/n)
-
-    let newH = Math.pow(eMax / (e / Math.pow(h, n)), 1 / n);
-    if (isNaN(newH) || !isFinite(newH)) {
-        newH = h;
-    }
-    return newH;
-}
+import { SimData } from "./SimData";
 
 export class Simulation implements PipelineObserver {
 
     public static readonly G = new THREE.Vector3(0, -9.807, 0);
     public static current: Particle;
-    public static h = .001;
+
+    public static timeAccumulator = 0;
 
     constructor() { 
-        SimulationData.generateTextile();
+        SimData.generateTextile();
     }
 
-
     update(_deltaTime: number): void {
 
-        if (SimulationData.paused) {
+        if (SimData.paused) {
             return;
         }
-        
-        for (let t = 0; t < _deltaTime; t += Simulation.h) {
-            SimulationData.pList?.forEach(pSub => {
+
+        const integrator = SimData.INTEGRATORS[SimData.integrationMethod];
+        Simulation.timeAccumulator += _deltaTime;
+
+        // 1. find smallest step siz
+        let h = SimData.stepSize;
+        if (SimData.stepSizeMethod == 1) {
+            let hMin = Number.MAX_VALUE;
+            SimData.pList?.forEach(pSub => {
+                pSub.forEach(p => {
+                    let x = [p.position.x, p.position.y, p.position.z,
+                        p.velocity.x, p.velocity.y, p.velocity.z];
+
+                    let hTmp = integrator.adaptive(x, h, Simulation.derivation, SimData.eOrder,
+                        SimData.eFactor * Math.pow(10, SimData.eMaxExponent),
+                        SimData.hFactor * Math.pow(10, SimData.hMaxExponent),
+                        SimData.hFactor * Math.pow(10, SimData.hMinExponent));
+                    if (hTmp < hMin) {
+                        hMin = hTmp;
+                    }
+                });
+            })
+
+            h = hMin;
+
+            if (Simulation.timeAccumulator > 1) {
+                Simulation.timeAccumulator = 0;
+                console.log(h);
+            }
+        }
+    
+        // 2. apply simualtion
+        for (let t = 0, i = 0; t < _deltaTime && i < 10000; t += h, i++) {
+            SimData.pList?.forEach(pSub => {
                 pSub.forEach(p => {
                     if (p.usePhysics) {
                         Simulation.current = p;
                         let x = [p.position.x, p.position.y, p.position.z,
                             p.velocity.x, p.velocity.y, p.velocity.z];
 
-                        if (SimulationData.stepSizeMethod == 1) {
-                            Simulation.h = adaptive(x, Simulation.h);
-                        }
-
-                        x = SimulationData.INTEGRATORS[SimulationData.integrationMethod]
-                            .step(x, Simulation.h, Simulation.derivation);
+                        x = integrator.step(x, h, Simulation.derivation);
 
                         p.setState(x[0], x[1], x[2], x[3], x[4], x[5]);
                     }
                 });
             })
         }
-
-        console.log(Simulation.h);
-
     }
 
-
     public static derivation(x: Array<number>): Array<number> {
 
         // 1. create new array to store derivatives
@@ -99,10 +82,13 @@ export class Simulation implements PipelineObserver {
 
         // 3.1 accumulate forces
         let totalForce = new THREE.Vector3(0, 0, 0);
-        totalForce.add(SimulationData.externalForce);
-        totalForce.divideScalar(Math.pow(SimulationData.resolution, 2));
-        totalForce.divideScalar(Math.sqrt(Math.pow(x[0], 2) + Math.pow(x[1], 2) + Math.pow(x[2], 2)));
-        // totalForce.multiplyScalar(Math.sin(2 * Math.PI * Math.random()) + .5);
+        totalForce.add(SimData.forceFieldStrength);
+        totalForce.divideScalar(Math.pow(SimData.resolution, 2));
+
+        const dist = SimData.forceFieldPosition.distanceTo(new THREE.Vector3(x[0], x[1], x[2]));
+        if (dist > 0) {
+            totalForce.divideScalar(dist);
+        }
 
         // 3.2 accumulate spring forces
         Simulation.current.references.forEach(spring => {
@@ -111,7 +97,7 @@ export class Simulation implements PipelineObserver {
         });
 
         // 3.3 damping force
-        const dampingForce = Simulation.current.velocity.clone().multiplyScalar(-SimulationData.damping);
+        const dampingForce = Simulation.current.velocity.clone().multiplyScalar(-SimData.damping);
         totalForce.add(dampingForce);
 
         // 3.3 acceleration = force / mass
diff --git a/src/03-simulation/SimulationData.ts b/src/03-simulation/SimulationData.ts
deleted file mode 100644
index 03a81b9ce1315a81b392c325cc70d96e251dc70e..0000000000000000000000000000000000000000
--- a/src/03-simulation/SimulationData.ts
+++ /dev/null
@@ -1,400 +0,0 @@
-import * as THREE from 'three';
-import { GUI } from 'dat.gui';
-import { picked, PipelineGUI, PipelineRenderable } from '../core/Pipeline';
-import { BendingSpring, ShearSpring, Spring, SpringConstants, StructuralSpring,  } from './physics/Springs';
-import { Particle } from './physics/particle';
-import { EulerIntegrator, Integrator, MidpointIntegrator, RungeKuttaIntegrator } from './physics/Integrators';
-
-document.addEventListener('keydown', (event: KeyboardEvent) => {
-
-    if (event.key === 'e') {
-        SimulationData.switchParticlePhysics();
-    } else if (event.key === 'w') {
-        SimulationData.paused = !SimulationData.paused;
-    }
-});
-
-class SimulationData implements PipelineGUI, PipelineRenderable {
-
-    public static container: THREE.Group = new THREE.Group();
-    public static paused: boolean = false;
-
-    // cloth settings
-    public static readonly MAX_TOTAL_MASS = 10;
-    public static readonly MIN_TOTAL_MASS = 0.1;
-
-    public static readonly MAX_WIDTH = 5;
-    public static readonly MIN_WIDTH = 0.01;
-
-    public static readonly MAX_HEIGHT = 2;
-    public static readonly MIN_HEIGHT = 0.01;
-
-    public static readonly MAX_RESOLUTION = 20;
-    public static readonly MIN_RESOLUTION = 2;
-
-    public static totalMass: number = 1;
-    public static width: number = .2;
-    public static height: number = .2;
-    public static resolution: number = 5;
-    public static damping: number = .1;
-    public static externalForce: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
-
-    // particls
-    public static pList? = new Array<Array<Particle>>();
-    public static pColorDefault: number = 0xffffff;
-    public static pColorNoPhysix: number = 0xff00ff;
-
-    // springs
-    public static readonly MAX_CONSTANT = 10;
-    public static readonly MIN_CONSTANT = 0.1;
-
-    public static sList? = new Array<Spring>();
-    public static sConstants: SpringConstants = { structural: 10, shear: 2, bend: 4 };
-    public static sColorStructural: number = 0xff0000;
-    public static sColorShear: number = 0x00ff00;
-    public static sColorBend: number = 0x0000ff;
-
-    // integrator
-    public static readonly INTEGRATORS: Integrator[] = [
-        new EulerIntegrator(),
-        new MidpointIntegrator(),
-        new RungeKuttaIntegrator()
-    ]
-
-    public static integrationMethod: number = 2;
-    public static stepSizeMethod: number = 0;
-    public static delta: number = 1e-3;
-
-    public static resetExternalForce(): void {
-        SimulationData.externalForce.x = 0;
-        SimulationData.externalForce.y = 0;
-        SimulationData.externalForce.z = 0;
-    }
-
-    public static generateTextile(): void {
-        // 1. reset pList
-        delete SimulationData.pList;
-        SimulationData.pList = new Array<Array<Particle>>();
-
-        // 2. generate particles
-        for (let i = 0; i < SimulationData.resolution; i++) {
-            SimulationData.pList[i] = new Array<Particle>();
-            for (let j = 0; j < SimulationData.resolution; j++) {
-
-                const p = new Particle({
-                    position: new THREE.Vector3(
-                        SimulationData.width * (i / SimulationData.resolution) - SimulationData.width / 2,
-                        SimulationData.height * (j / SimulationData.resolution) - SimulationData.height / 2,
-                        0),
-                    velocity: new THREE.Vector3(0, 0, 0),
-                    mass: SimulationData.totalMass / (SimulationData.resolution * SimulationData.resolution),
-                    usePhysics: true,
-                    references: [],
-                    radius: 0,
-                    color: SimulationData.pColorDefault,
-                    wireframe: true
-                });
-                SimulationData.pList[i][j] = p;
-            }
-        }
-
-        for (let i = 0; i < SimulationData.resolution; i++) {
-            SimulationData.pList[i][SimulationData.resolution - 1].usePhysics = false;
-        }
-
-        // 3. set particle mass and color
-        SimulationData.changedParticleSize();
-        SimulationData.changedParticleColor();
-
-        // 4. add particles to container
-        const pLinList = new Array<THREE.Mesh>();
-        SimulationData.pList.forEach(pSub => {
-            pSub.forEach(p => {
-                pLinList.push(p.mesh);
-            });
-        });
-
-        // 5. reset sList
-        delete SimulationData.sList;
-        SimulationData.sList = new Array<Spring>();
-
-        // 6. generate springs
-        for (let i = 0; i < SimulationData.resolution; i++) {
-            for (let j = 0; j < SimulationData.resolution; j++) {
-
-                // structural springs (horizontal)
-                if (i < SimulationData.resolution - 1) {
-                    const pA = SimulationData.pList[i][j];
-                    const pB = SimulationData.pList[i + 1][j];
-
-                    const s = new StructuralSpring({
-                        positionA: pA.position,
-                        positionB: pB.position,
-                        constants: SimulationData.sConstants,
-                        color: SimulationData.sColorStructural,
-                    });
-                    SimulationData.sList.push(s);
-                    s.line.renderOrder = 2;
-
-                    pA.references.push(s);
-                    pB.references.push(s);
-                }
-
-                // structural springs (vertical)
-                if (j < SimulationData.resolution - 1) {
-                    const pA = SimulationData.pList[i][j];
-                    const pB = SimulationData.pList[i][j + 1];
-
-                    const s = new StructuralSpring({
-                        positionA: pA.position,
-                        positionB: pB.position,
-                        constants: SimulationData.sConstants,
-                        color: SimulationData.sColorStructural,
-                    });
-                    SimulationData.sList.push(s);
-                    s.line.renderOrder = 2;
-
-                    pA.references.push(s);
-                    pB.references.push(s);
-                }
-
-
-                // shear springs (bottom-left to top-right)
-                if (i < SimulationData.resolution - 1 && j < SimulationData.resolution - 1) {
-                    const pA = SimulationData.pList[i][j];
-                    const pB = SimulationData.pList[i + 1][j + 1];
-
-                    const s = new ShearSpring({
-                        positionA: pA.position,
-                        positionB: pB.position,
-                        constants: SimulationData.sConstants,
-                        color: SimulationData.sColorShear,
-                    });
-                    SimulationData.sList.push(s);
-                    s.line.renderOrder = 1;
-
-                    pA.references.push(s);
-                    pB.references.push(s);
-                }
-
-                // shear springs (top-left to bottom-right)
-                if (i < SimulationData.resolution - 1 && j > 0) {
-                    const pA = SimulationData.pList[i][j];
-                    const pB = SimulationData.pList[i + 1][j - 1];
-
-                    const s = new ShearSpring({
-                        positionA: pA.position,
-                        positionB: pB.position,
-                        constants: SimulationData.sConstants,
-                        color: SimulationData.sColorShear,
-                    });
-                    SimulationData.sList.push(s);
-                    s.line.renderOrder = 1;
-
-                    pA.references.push(s);
-                    pB.references.push(s);
-                }
-
-                // bend springs (horizontal)
-                if (i < SimulationData.resolution - 2) {
-                    const pA = SimulationData.pList[i][j];
-                    const pB = SimulationData.pList[i + 2][j];
-
-                    const s = new BendingSpring({
-                        positionA: pA.position,
-                        positionB: pB.position,
-                        constants: SimulationData.sConstants,
-                        color: SimulationData.sColorBend,
-                    });
-                    SimulationData.sList.push(s);
-                    s.line.renderOrder = 0;
-
-                    pA.references.push(s);
-                    pB.references.push(s);
-                }
-
-                // bend springs (vertical)
-                if (j < SimulationData.resolution - 2) {
-                    const pA = SimulationData.pList[i][j];
-                    const pB = SimulationData.pList[i][j + 2];
-
-                    const s = new BendingSpring({
-                        positionA: pA.position,
-                        positionB: pB.position,
-                        constants: SimulationData.sConstants,
-                        color: SimulationData.sColorBend,
-                    });
-                    SimulationData.sList.push(s);
-                    s.line.renderOrder = 0;
-
-                    pA.references.push(s);
-                    pB.references.push(s);
-                }
-            }
-        }
-
-        // 7. add springs to container
-        const sLinList = new Array<THREE.Line>();
-        SimulationData.sList.forEach(s => {
-            sLinList.push(s.line);
-        });
-
-        // 8. add particles and springs to container
-        while (SimulationData.container.children.length > 0) {
-            let rm = SimulationData.container.children.pop();
-            if (rm instanceof THREE.Line) {
-                rm.geometry.dispose();
-                rm.material.dispose();
-            } else if (rm instanceof THREE.Mesh) {
-                rm.geometry.dispose();
-                rm.material.dispose();
-            }
-        }
-
-        SimulationData.container.add(...pLinList, ...sLinList);
-    }
-
-    public static changedParticleSize(): void {
-        // 1. calculate current mass
-        const pCount = SimulationData.resolution * SimulationData.resolution;
-        const pMass = SimulationData.totalMass / pCount;
-
-        // 1.1 update particle mass
-        SimulationData.pList?.forEach(pSub => {
-            pSub.forEach(p => {
-                p.mass = pMass;
-            });
-        });
-
-        // 2. calculate max pSize
-        const maxSize = Math.min(SimulationData.width, SimulationData.height);
-
-        // 3. normalize pMass
-        const pMassNorm = (pMass + SimulationData.MIN_TOTAL_MASS) /
-            (SimulationData.MAX_TOTAL_MASS + SimulationData.MIN_TOTAL_MASS);
-
-        // 4. calculate pSize
-        const pSize = maxSize * pMassNorm;
-
-        // 5. set pSize
-        SimulationData.pList?.forEach(pSub => { pSub.forEach(p => p.setRadius(pSize)) });
-    }
-
-    public static changedDimensions(): void {
-        if (!SimulationData.pList)
-            return;
-
-        // 1. reset positions of particles
-        for (let i = 0; i < SimulationData.resolution; i++) {
-            for (let j = 0; j < SimulationData.resolution; j++) {
-                SimulationData.pList[i][j].setPosition(
-                    SimulationData.width * (i / SimulationData.resolution) - SimulationData.width / 2,
-                    SimulationData.height * (j / SimulationData.resolution) - SimulationData.height / 2,
-                    0);
-            }
-        }
-
-        // 2. set size based on particle mass
-        SimulationData.changedParticleSize();
-    }
-
-    public static changedParticleColor(): void {
-        // iterate over all particles and set color based on current internal state
-        SimulationData.pList?.forEach(pSub => {
-            pSub.forEach(p => {
-                if (p.usePhysics) {
-                    p.setColor(SimulationData.pColorDefault);
-                } else {
-                    p.setColor(SimulationData.pColorNoPhysix);
-                }
-            })
-        });
-    }
-
-    public static changedSpringColor(): void {
-        // iterate over all particles and set color based on current internal state
-        SimulationData.sList?.forEach(s => {
-            if (s instanceof StructuralSpring) {
-                s.setColor(SimulationData.sColorStructural);
-            } else if (s instanceof ShearSpring) {
-                s.setColor(SimulationData.sColorShear);
-            } else if (s instanceof BendingSpring) {
-                s.setColor(SimulationData.sColorBend);
-            }
-        });
-    }
-
-    public static switchParticlePhysics(): void {
-        // 1. is picked object not null
-        if (!picked) {
-            return;
-        }
-
-        // 2. compare mesh to all particles => find particle and change usePhysics
-        SimulationData.pList?.forEach(pSub => {
-            pSub.forEach(p => {
-                if (p.mesh === picked) {
-                    p.usePhysics = !p.usePhysics;
-                    p.setColor(p.usePhysics ? SimulationData.pColorDefault 
-                        : SimulationData.pColorNoPhysix);
-                }
-            })
-        });
-    }
-
-    gui(gui: GUI): void {
-
-
-        const general = gui.addFolder('General');
-        general.add(SimulationData, 'paused').listen().name("Pause Simulation");
-        general.add(SimulationData, 'totalMass', SimulationData.MIN_TOTAL_MASS, SimulationData.MAX_TOTAL_MASS)
-            .step(0.1).name('Total Mass').onChange(SimulationData.changedParticleSize);
-        general.add(SimulationData, 'width', SimulationData.MIN_WIDTH, SimulationData.MAX_WIDTH)
-            .step(0.1).name('Width').onChange(SimulationData.changedDimensions);
-        general.add(SimulationData, 'height', SimulationData.MIN_HEIGHT, SimulationData.MAX_HEIGHT)
-            .step(0.1).name('Height').onChange(SimulationData.changedDimensions);
-        general.add(SimulationData, 'resolution', SimulationData.MIN_RESOLUTION, SimulationData.MAX_RESOLUTION)
-            .step(1).name('Resolution').onChange(SimulationData.generateTextile);
-        general.open();
-        
-
-        const integration = gui.addFolder('Integrator');
-        integration.add(SimulationData, 'integrationMethod', { 'Euler': 0, 'Midpoint': 1, 'Runge-Kutta': 2 }).name('Method');
-        integration.add(SimulationData, 'stepSizeMethod', { 'Fixed': 0, 'Adaptive': 1 }).name('Step Size').onChange((e) => { SimulationData.stepSizeMethod = e; console.log(e);});
-        integration.add(SimulationData, 'delta', 1e-10, 1e-6, 1e-10).name('Error Max');
-        integration.open();
-
-        const extForce = general.addFolder('External Force');
-        extForce.add(SimulationData.externalForce, 'x',).step(0.01);
-        extForce.add(SimulationData.externalForce, 'y',).step(0.01);
-        extForce.add(SimulationData.externalForce, 'z',).step(0.01);
-        extForce.open();
-
-        const particles = gui.addFolder('Particles');
-        particles.add(SimulationData, 'switchParticlePhysics').name('Switch Particle Physics');
-        particles.addColor(SimulationData, 'pColorDefault').name('Default').onChange(SimulationData.changedParticleColor);
-        particles.addColor(SimulationData, 'pColorNoPhysix').name('No Phyisx').onChange(SimulationData.changedParticleColor);
-        particles.open();
-
-        const springs = gui.addFolder('Springs');
-        springs.add(SimulationData, 'damping', 0, 0.2, 0.0001).name('Damping Factor');
-        springs.add(SimulationData.sConstants, 'structural', SimulationData.MIN_CONSTANT, SimulationData.MAX_CONSTANT)
-            .step(0.1).name('Structural');
-        springs.add(SimulationData.sConstants, 'shear', SimulationData.MIN_CONSTANT, SimulationData.MAX_CONSTANT)
-            .step(0.1).name('Shear');
-        springs.add(SimulationData.sConstants, 'bend', SimulationData.MIN_CONSTANT, SimulationData.MAX_CONSTANT)
-            .step(0.1).name('Bend');
-        springs.addColor(SimulationData, 'sColorStructural').name('Structural').onChange(SimulationData.changedSpringColor);
-        springs.addColor(SimulationData, 'sColorShear').name('Shear').onChange(SimulationData.changedSpringColor);
-        springs.addColor(SimulationData, 'sColorBend').name('Bend').onChange(SimulationData.changedSpringColor);
-        springs.open();
-    }
-
-    object(): THREE.Object3D<THREE.Event> {
-        return SimulationData.container;
-    }
-}
-
-export { SimulationData };
-
-
diff --git a/src/03-simulation/physics/Integrators.ts b/src/03-simulation/physics/Integrators.ts
index f47ee3cda63caf5337b070b86aa24579cd34e37c..8ecdcee6471fe7c09f971b329bb2bae6b7be6bd6 100644
--- a/src/03-simulation/physics/Integrators.ts
+++ b/src/03-simulation/physics/Integrators.ts
@@ -1,12 +1,57 @@
 
 abstract class Integrator {
     abstract step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>;
+
+    /**
+     * 
+     * @param x current state
+     * @param h current step size
+     * @param f how to deriviate the state
+     * @param eMax tolerance for error
+     * @param eOrder error class of the used method
+     * @param hMax maximum step size
+     * @param hMin minimum step size
+     * @returns the next step size between hMax and hMin
+     */
+    public adaptive(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>,
+        eOrder: number, eMax: number, hMax: number = 0.05, hMin: number = 0.00005): number {
+        // 1. calculate approximate
+        let xFull = this.step([...x], h, f);
+        let xHalf = this.step([...x], h / 2, f);
+        xHalf = this.step(xHalf, h / 2, f);
+
+        // 2. calculate error
+        let eList = [];
+        for (let i = 0; i < x.length; i++) {
+            eList.push(Math.abs(xFull[i] - xHalf[i]));
+        }
+        let e = 0;
+        for (let i = 0; i < eList.length; i++) {
+            e += Math.pow(eList[i], 2);
+        }
+
+        e = Math.sqrt(e); // total error
+
+        // 3. e = k * h^n
+        //  e.g. h = 0.001 mit n = 5 => e = k * 0.001^5
+        //   => k = e / h^n
+        //   => k * h^n = eMax
+        //   => h = pow(eMax / k, 1/n)
+        //   => h = pow(eMax / (e / h^n), 1/n)
+
+        let newH = Math.pow(eMax / (e / Math.pow(h, eOrder)), 1 / eOrder);
+        if (isNaN(newH) || !isFinite(newH)) {
+            newH = h;
+        }
+
+        let clampedH = Math.min(hMax, Math.max(newH, hMin));
+        return clampedH;
+    }
 }
 
 class EulerIntegrator extends Integrator {
-    step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>
-    {
-        let y  = f(x);
+    step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number> {
+        let y = f(x);
         let x_new = x.map((_, i) => x[i] + h * y[i]);
         return x_new;
     }
@@ -21,8 +66,7 @@ class MidpointIntegrator extends Integrator {
         this.euler = new EulerIntegrator();
     }
 
-    step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>
-    {
+    step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number> {
         let euler_step = this.euler.step(x, h / 2, f);
         let y = f(euler_step);
         let x_new = x.map((_, i) => x[i] + h * y[i]);
@@ -31,8 +75,7 @@ class MidpointIntegrator extends Integrator {
 }
 
 class RungeKuttaIntegrator extends Integrator {
-    step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>
-    {
+    step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number> {
         let k1 = f(x);
         let k2 = f(x.map((_, i) => x[i] + h / 2 * k1[i]));
         let k3 = f(x.map((_, i) => x[i] + h / 2 * k2[i]));
diff --git a/src/03-simulation/physics/Particle.ts b/src/03-simulation/physics/Particle.ts
index 8237d597c549f126e85ca2f2d0341636427d700a..2fd28b99468188cdb5b9e29f752497d70c66f72b 100644
--- a/src/03-simulation/physics/Particle.ts
+++ b/src/03-simulation/physics/Particle.ts
@@ -14,12 +14,13 @@ export interface ParticleParameters {
     references: Spring[];
 
     // visual parameters
-    radius: number;
     color: number;
     wireframe: boolean;
 }
 
 export class Particle implements PipelineRenderable {
+    private static counter = 0;
+
     public position: THREE.Vector3;
     public velocity: THREE.Vector3;
 
@@ -37,10 +38,12 @@ export class Particle implements PipelineRenderable {
         this.references = params.references;
 
         this.mesh = new THREE.Mesh(
-            new THREE.SphereGeometry(params.radius, 4, 2),
+            new THREE.SphereGeometry(),
             new THREE.MeshBasicMaterial({ color: params.color, wireframe: params.wireframe })
         );
         this.mesh.position.copy(this.position);
+        this.mesh.name = `Particle ${Particle.counter++}`;
+        this.mesh.geometry.name = this.mesh.name;
     }
 
     public setColor(color: number): void {
@@ -60,7 +63,8 @@ export class Particle implements PipelineRenderable {
 
     public setRadius(radius: number): void {
         this.mesh.geometry.dispose();
-        this.mesh.geometry = new THREE.SphereGeometry(radius, 4, 2);
+        this.mesh.geometry = new THREE.SphereGeometry(Math.abs(radius), 4, 2);
+        this.mesh.geometry.name = this.mesh.name;
     }
 
     public object(): THREE.Object3D<THREE.Event> {
diff --git a/src/03-simulation/physics/Springs.ts b/src/03-simulation/physics/Springs.ts
index a43c7cf7220678ca45c981dd4d49649eec76ff37..5746ec187ef65c192b1d44c0b5824b62ef5b3604 100644
--- a/src/03-simulation/physics/Springs.ts
+++ b/src/03-simulation/physics/Springs.ts
@@ -13,6 +13,7 @@ export interface SpringParameters {
 }
 
 export abstract class Spring {
+    private static counter = 0;
 
     public positionA: THREE.Vector3;
     public positionB: THREE.Vector3;
@@ -25,10 +26,19 @@ export abstract class Spring {
         this.positionB = params.positionB;
         this.restingLength = this.positionA.distanceTo(this.positionB);
 
+        const bufferGeometry = new THREE.BufferGeometry();
+        bufferGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(6), 3));
+        bufferGeometry.attributes.position.setXYZ(0, this.positionA.x, this.positionA.y, this.positionA.z);
+        bufferGeometry.attributes.position.setXYZ(1, this.positionB.x, this.positionB.y, this.positionB.z);
+        bufferGeometry.attributes.position.needsUpdate = true;
+
         this.line = new THREE.Line(
-            new THREE.BufferGeometry().setFromPoints([this.positionA, this.positionB]),
+            bufferGeometry,
             new THREE.LineBasicMaterial({ color: params.color })
         );
+
+        this.line.name = `Spring ${Spring.counter++}`;
+        this.line.geometry.name = this.line.name;
     }
 
     abstract get springConstant(): number;
@@ -55,6 +65,7 @@ export abstract class Spring {
         this.line.geometry.attributes.position.setXYZ(0, this.positionA.x, this.positionA.y, this.positionA.z);
         this.line.geometry.attributes.position.setXYZ(1, this.positionB.x, this.positionB.y, this.positionB.z);
         this.line.geometry.attributes.position.needsUpdate = true;
+        this.line.geometry.name = this.line.name;
     }
 }