Skip to content
Snippets Groups Projects
Unverified Commit b4678136 authored by Jamie Temple's avatar Jamie Temple
Browse files

feat: generation of points and structure springs

parent ca206a8a
Branches
No related tags found
1 merge request!1feat: base project
import * as THREE from 'three';
import { PipelineData } from "../core/Pipeline"; import { PipelineData } from "../core/Pipeline";
import { Particle } from "./cgPhysix/particle";
import { SimulationSettings } from './SimulationSettings';
class SimulationDemo extends PipelineData { class SimulationDemo extends PipelineData {
public constructor() { super(); } public constructor() { super(); }
data(): void { data(): void {
/* INIT DATA HERE */ const sim = new SimulationSettings();
this.addGUIElement(sim);
SimulationSettings.generateTextile();
this.scene.add(SimulationSettings.container);
} }
} }
......
import * as THREE from 'three';
import { GUI } from 'dat.gui';
import { picked, PipelineGUI } from '../core/Pipeline';
import { BendingSpring, ShearSpring, Spring, SpringConstants, StructuralSpring, } from './cgPhysix/Springs';
import { Particle } from './cgPhysix/particle';
class World {
public static readonly G = 9.807;
public static derivation(x: Array<number>): Array<number> {
let xDot = new Array<number>(x.length);
for (let i = 0; i < x.length; i += 6) {
xDot[i] = x[i + 3];
xDot[i + 1] = x[i + 4];
xDot[i + 2] = x[i + 5];
xDot[i + 3] = 0;
xDot[i + 4] = -.01;
xDot[i + 5] = 0;
}
return xDot;
}
}
class SimulationSettings implements PipelineGUI {
public static container: THREE.Group = new THREE.Group();
// 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 = 1;
public static height: number = 1;
public static resolution: number = 5;
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: 1, shear: 1, bend: 1 };
public static sColorStructural: number = 0xff0000;
public static sColorShear: number = 0x00ff00;
public static sColorBend: number = 0x0000ff;
// integrator
public static readonly INTEGRATOR_TYPES = ['Euler', 'Midpoint', 'RungeKutta'];
public static readonly STEP_SIZE = ['Fixed', 'Adaptive'];
public static integratorType: string = 'Euler';
public static stepSize: string = 'Fixed';
public static generateTextile(): void {
// 1. reset pList
SimulationSettings.pList = new Array<Array<Particle>>();
// 2. generate particles
for (let i = 0; i < SimulationSettings.resolution; i++) {
SimulationSettings.pList[i] = new Array<Particle>();
for (let j = 0; j < SimulationSettings.resolution; j++) {
const p = new Particle({
position: new THREE.Vector3(
SimulationSettings.width * (i / SimulationSettings.resolution) - SimulationSettings.width / 2,
SimulationSettings.height * (j / SimulationSettings.resolution) - SimulationSettings.height / 2,
0),
velocity: new THREE.Vector3(0, 0, 0),
mass: SimulationSettings.totalMass / (SimulationSettings.resolution * SimulationSettings.resolution),
usePhysics: true,
references: [],
radius: 0,
color: SimulationSettings.pColorDefault,
wireframe: true
});
SimulationSettings.pList[i][j] = p;
}
}
for (let i = 0; i < SimulationSettings.resolution; i++) {
SimulationSettings.pList[i][SimulationSettings.resolution - 1].usePhysics = false;
}
// 3. set particle mass and color
SimulationSettings.changedParticleSize();
SimulationSettings.changedParticleColor();
// 4. add particles to container
const pLinList = new Array<THREE.Mesh>();
SimulationSettings.pList.forEach(pSub => {
pSub.forEach(p => {
pLinList.push(p.mesh);
});
});
// 5. reset sList
SimulationSettings.sList = new Array<Spring>();
// 6. generate springs
for (let i = 0; i < SimulationSettings.resolution; i++) {
for (let j = 0; j < SimulationSettings.resolution; j++) {
// structural springs (horizontal)
if (i < SimulationSettings.resolution - 1) {
const pA = SimulationSettings.pList[i][j];
const pB = SimulationSettings.pList[i + 1][j];
const s = new StructuralSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationSettings.sConstants,
color: SimulationSettings.sColorStructural,
});
SimulationSettings.sList.push(s);
pA.references.push(s);
pB.references.push(s);
}
// structural springs (vertical)
if (j < SimulationSettings.resolution - 1) {
const pA = SimulationSettings.pList[i][j];
const pB = SimulationSettings.pList[i][j + 1];
const s = new StructuralSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationSettings.sConstants,
color: SimulationSettings.sColorStructural,
});
SimulationSettings.sList.push(s);
pA.references.push(s);
pB.references.push(s);
}
// // shear springs
// if (j < SimulationSettings.resolution - 1) {
// const s = new ShearSpring(SimulationSettings.pList[i][j], SimulationSettings.pList[i][j + 1], SimulationSettings.sConstants.shear);
// SimulationSettings.sList.push(s);
// }
// // bending springs
// if (i < SimulationSettings.resolution - 1 && j < SimulationSettings.resolution - 1) {
// const s = new BendingSpring(SimulationSettings.pList[i][j], SimulationSettings.pList[i + 1][j + 1], SimulationSettings.sConstants.bend);
// SimulationSettings.sList.push(s);
// }
}
}
// 7. add springs to container
const sLinList = new Array<THREE.Line>();
SimulationSettings.sList.forEach(s => {
sLinList.push(s.line);
});
// 8. add particles and springs to container
while (SimulationSettings.container.children.length > 0) {
SimulationSettings.container.children.pop();
}
SimulationSettings.container.add(...pLinList, ...sLinList);
}
public static changedParticleSize(): void {
// 1. calculate current mass
const pCount = SimulationSettings.resolution * SimulationSettings.resolution;
const pMass = SimulationSettings.totalMass / pCount;
// 2. calculate max pSize
const maxSize = Math.min(SimulationSettings.width, SimulationSettings.height);
// 3. normalize pMass
const pMassNorm = (pMass + SimulationSettings.MIN_TOTAL_MASS) /
(SimulationSettings.MAX_TOTAL_MASS + SimulationSettings.MIN_TOTAL_MASS);
// 4. calculate pSize
const pSize = maxSize * pMassNorm;
// 5. set pSize
SimulationSettings.pList.forEach(pSub => { pSub.forEach(p => p.setRadius(pSize)) });
}
public static changedDimensions(): void {
// 1. reset positions of particles
for (let i = 0; i < SimulationSettings.resolution; i++) {
for (let j = 0; j < SimulationSettings.resolution; j++) {
SimulationSettings.pList[i][j].setPosition(
SimulationSettings.width * (i / SimulationSettings.resolution) - SimulationSettings.width / 2,
SimulationSettings.height * (j / SimulationSettings.resolution) - SimulationSettings.height / 2,
0);
}
}
// 2. set size based on particle mass
SimulationSettings.changedParticleSize();
}
public static changedParticleColor(): void {
// iterate over all particles and set color based on current internal state
SimulationSettings.pList.forEach(pSub => {
pSub.forEach(p => {
if (p.usePhysics) {
p.setColor(SimulationSettings.pColorDefault);
} else {
p.setColor(SimulationSettings.pColorNoPhysix);
}
})
});
}
public static changedSpringColor(): void {
// iterate over all particles and set color based on current internal state
SimulationSettings.sList.forEach(s => {
if (s instanceof StructuralSpring) {
s.setColor(SimulationSettings.sColorStructural);
} else if (s instanceof ShearSpring) {
s.setColor(SimulationSettings.sColorShear);
} else if (s instanceof BendingSpring) {
s.setColor(SimulationSettings.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
SimulationSettings.pList.forEach(pSub => {
pSub.forEach(p => {
if (p.mesh === picked) {
p.usePhysics = !p.usePhysics;
p.setColor(p.usePhysics ? SimulationSettings.pColorDefault
: SimulationSettings.pColorNoPhysix);
}
})
});
}
// callback overrides by other classes
public callbackSpringColorStructural(_value: number): void { }
public callbackSpringColorShear(_value: number): void { }
public callbackSpringColorBend(_value: number): void { }
gui(gui: GUI): void {
const general = gui.addFolder('General');
general.add(SimulationSettings, 'totalMass', SimulationSettings.MIN_TOTAL_MASS, SimulationSettings.MAX_TOTAL_MASS)
.step(0.1).name('Total Mass').onChange(SimulationSettings.changedParticleSize);
general.add(SimulationSettings, 'width', SimulationSettings.MIN_WIDTH, SimulationSettings.MAX_WIDTH)
.step(0.1).name('Width').onChange(SimulationSettings.changedDimensions);
general.add(SimulationSettings, 'height', SimulationSettings.MIN_HEIGHT, SimulationSettings.MAX_HEIGHT)
.step(0.1).name('Height').onChange(SimulationSettings.changedDimensions);
general.add(SimulationSettings, 'resolution', SimulationSettings.MIN_RESOLUTION, SimulationSettings.MAX_RESOLUTION)
.step(1).name('Resolution').onChange(SimulationSettings.generateTextile);
general.open();
const extForce = general.addFolder('External Force');
extForce.add(SimulationSettings.externalForce, 'x',).step(0.01);
extForce.add(SimulationSettings.externalForce, 'y',).step(0.01);
extForce.add(SimulationSettings.externalForce, 'z',).step(0.01);
extForce.open();
const particles = gui.addFolder('Particles');
particles.add(SimulationSettings, 'switchParticlePhysics').name('Switch Particle Physics');
particles.addColor(SimulationSettings, 'pColorDefault').name('Default').onChange(SimulationSettings.changedParticleColor);
particles.addColor(SimulationSettings, 'pColorNoPhysix').name('No Phyisx').onChange(SimulationSettings.changedParticleColor);
particles.open();
const springs = gui.addFolder('Springs');
springs.add(SimulationSettings.sConstants, 'structural', SimulationSettings.MIN_CONSTANT, SimulationSettings.MAX_CONSTANT)
.step(0.1).name('Structural');
springs.add(SimulationSettings.sConstants, 'shear', SimulationSettings.MIN_CONSTANT, SimulationSettings.MAX_CONSTANT)
.step(0.1).name('Shear');
springs.add(SimulationSettings.sConstants, 'bend', SimulationSettings.MIN_CONSTANT, SimulationSettings.MAX_CONSTANT)
.step(0.1).name('Bend');
springs.addColor(SimulationSettings, 'sColorStructural').name('Structural').onChange(SimulationSettings.changedSpringColor);
springs.addColor(SimulationSettings, 'sColorShear').name('Shear').onChange(SimulationSettings.changedSpringColor);
springs.addColor(SimulationSettings, 'sColorBend').name('Bend').onChange(SimulationSettings.changedSpringColor);
springs.open();
}
}
export { SimulationSettings };
abstract class Integrator {
abstract step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>;
public static adaptiveStep(integrator: Integrator, x: Array<number>, h: number,
f: (x: Array<number>) => Array<number>, errorMax: number): number {
let xFull = integrator.step(x, h, f);
let xHalf = integrator.step(x, h / 2, f);
let error: Array<number> = [];
for (let i = 0; i < xFull.length; i++) {
error.push(Math.abs(xFull[i] - xHalf[i]));
}
let errorApproxMax = error.reduce((a, b) => Math.max(a, b));
return h * Math.sqrt(errorMax / errorApproxMax);
}
}
class EulerIntegrator extends Integrator {
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;
}
}
class MidpointIntegrator extends Integrator {
private euler: EulerIntegrator;
constructor() {
super();
this.euler = new EulerIntegrator();
}
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]);
return x_new;
}
}
class RungeKuttaIntegrator extends Integrator {
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]));
let k4 = f(x.map((_, i) => x[i] + h * k3[i]));
let x_new = x.map((_, i) => x[i] + h / 6 * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]));
return x_new;
}
}
export { Integrator, EulerIntegrator, MidpointIntegrator, RungeKuttaIntegrator };
import * as THREE from 'three';
import { PipelineRenderable } from '../../core/Pipeline';
import './Springs';
import { Spring } from './Springs';
export interface ParticleParameters {
// particle traits
position: THREE.Vector3;
velocity: THREE.Vector3;
// internal parameters
usePhysics: boolean;
mass: number;
references: Spring[];
// visual parameters
radius: number;
color: number;
wireframe: boolean;
}
export class Particle implements PipelineRenderable {
public position: THREE.Vector3;
public velocity: THREE.Vector3;
public mass: number;
public usePhysics: boolean;
public references: Spring[];
public mesh: THREE.Mesh;
constructor(params: ParticleParameters) {
this.position = params.position;
this.velocity = params.velocity;
this.mass = params.mass;
this.usePhysics = params.usePhysics;
this.references = params.references;
this.mesh = new THREE.Mesh(
new THREE.SphereGeometry(params.radius, 4, 2),
new THREE.MeshBasicMaterial({ color: params.color, wireframe: params.wireframe })
);
this.mesh.position.copy(this.position);
}
public setColor(color: number): void {
(this.mesh.material as THREE.MeshBasicMaterial).color.set(color);
}
public setPosition(x: number, y: number, z: number): void {
this.position.set(x, y, z);
this.mesh.position.copy(this.position);
this.references.forEach(spring => spring.notify());
}
public setRadius(radius: number): void {
this.mesh.geometry = new THREE.SphereGeometry(radius, 4, 2);
}
public object(): THREE.Object3D<THREE.Event> {
return this.mesh;
}
}
\ No newline at end of file
import * as THREE from 'three';
export interface SpringParameters {
// spring traits
positionA: THREE.Vector3;
positionB: THREE.Vector3;
// internal parameters
constants: SpringConstants;
// visuals
color: number;
}
export abstract class Spring {
public positionA: THREE.Vector3;
public positionB: THREE.Vector3;
public restingLength: number;
public line: THREE.Line;
constructor(params: SpringParameters) {
this.positionA = params.positionA;
this.positionB = params.positionB;
this.restingLength = this.positionA.distanceTo(this.positionB);
this.line = new THREE.Line(
new THREE.BufferGeometry().setFromPoints([this.positionA, this.positionB]),
new THREE.LineBasicMaterial({ color: params.color })
);
}
abstract get springConstant(): number;
public force(x: number, y: number, z: number): THREE.Vector3 {
let direction = this.positionB.clone().sub(this.positionA);
const distance = direction.length();
direction.normalize();
const sign = distance / Math.abs(distance);
const result = direction.multiplyScalar(this.springConstant * (distance - this.restingLength) * sign);
if (this.positionA === new THREE.Vector3(x, y, z)) {
return result;
}
return result.multiplyScalar(-1);
}
public setColor(color: number): void {
(this.line.material as THREE.LineBasicMaterial).color.set(color);
}
notify(): void {
this.line.geometry.setFromPoints([this.positionA, this.positionB]);
}
}
export class SpringConstants {
public structural: number = 1;
public shear: number = 1;
public bend: number = 1;
}
export class StructuralSpring extends Spring {
public constants: SpringConstants;
constructor(params: SpringParameters) {
super(params);
this.constants = params.constants;
}
get springConstant(): number {
return this.constants.structural;
}
}
export class ShearSpring extends Spring {
public constants: SpringConstants;
constructor(params: SpringParameters) {
super(params);
this.constants = params.constants;
}
get springConstant(): number {
return this.constants.shear;
}
}
export class BendingSpring extends Spring {
public constants: SpringConstants;
constructor(params: SpringParameters) {
super(params);
this.constants = params.constants;
}
get springConstant(): number {
return this.constants.bend;
}
}
...@@ -4,6 +4,8 @@ import * as THREE from "three"; ...@@ -4,6 +4,8 @@ import * as THREE from "three";
import { DragControls } from "three/examples/jsm/controls/DragControls"; import { DragControls } from "three/examples/jsm/controls/DragControls";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
export let picked: THREE.Object3D | null;
/** /**
* PipelineObserver is an abstract class that is used to observe the renderer. * PipelineObserver is an abstract class that is used to observe the renderer.
*/ */
...@@ -27,11 +29,6 @@ interface PipelineRenderable { ...@@ -27,11 +29,6 @@ interface PipelineRenderable {
* Access the THREE.js object. * Access the THREE.js object.
*/ */
object(): THREE.Object3D; object(): THREE.Object3D;
/**
* Access the position of the THREE.js object directly.
*/
position(): THREE.Vector3;
} }
/** /**
...@@ -102,7 +99,7 @@ abstract class PipelineData { ...@@ -102,7 +99,7 @@ abstract class PipelineData {
/** /**
* Define objects that should be added to the GUI. * Define objects that should be added to the GUI.
*/ */
protected addGUI(guiElement: PipelineGUI) { protected addGUIElement(guiElement: PipelineGUI) {
this.guiElements.push(guiElement); this.guiElements.push(guiElement);
} }
...@@ -190,6 +187,25 @@ class Pipeline { ...@@ -190,6 +187,25 @@ class Pipeline {
data.draggables.forEach(draggable => { this.addDraggable(draggable); }); data.draggables.forEach(draggable => { this.addDraggable(draggable); });
data.observers.forEach(observer => { this.addPipelineObserver(observer); }); data.observers.forEach(observer => { this.addPipelineObserver(observer); });
data.guiElements.forEach(guiElement => { this.addGUI(guiElement); }); data.guiElements.forEach(guiElement => { this.addGUI(guiElement); });
document.addEventListener( 'mousedown', (event) => {
event.preventDefault();
let mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1,
-( event.clientY / window.innerHeight ) * 2 + 1,
0.5 );
let raycaster = new THREE.Raycaster();
raycaster.setFromCamera( mouse3D, this.camera );
let intersects = raycaster.intersectObjects( data.scene.children );
if (intersects.length > 0) {
intersects.forEach(intersect => {
if (intersect.object instanceof THREE.Mesh) {
picked = intersect.object;
}
});
}
console.log(picked);
});
} }
public addPipelineObserver(object: PipelineObserver): void { public addPipelineObserver(object: PipelineObserver): void {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment