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

feat: project is now written in TS

parent 2b3c6308
No related branches found
No related tags found
No related merge requests found
# computergrafik-03
# Computergrafik 3
```bash
npm install
npm i
npm run dev
```
......@@ -4,11 +4,11 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<title>Computergrafik 3</title>
</head>
<body>
<div id="app"></div>
<canvas id="canvas"></canvas>
<script type="module" src="/main.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
'use strict'
import * as THREE from 'three';
import * as CG from './src/rendering';
import * as Interpolation from './src/interpolation';
import * as UI from './src/userInterface';
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const camera = CG.createCamera({ type: CG.cameraTypes.ORTHOGRAPHIC });
const renderer = CG.createRenderer('#canvas');
const controls = CG.createControls(camera, renderer);
const scene = new THREE.Scene()
const animator = new CG.Animate(renderer, scene, camera, controls);
const ui = new UI.UserInterface();
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// create point start and end point and control points
const startPoint = new THREE.Vector2(-1, -1);
const endPoint = new THREE.Vector2(1, 1);
const startControlPoint = new THREE.Vector2(-1, 1);
const endControlPoint = new THREE.Vector2(1, -1);
// create curve
const curve = new Interpolation.BezierCurve(
startPoint,
endPoint,
startControlPoint,
endControlPoint
)
// add curve to scene
const line = curve.createLine();
scene.add(line)
// register curve to the ui
const ranges = [-10, 10, .01];
const update = () => { curve.update() }
ui.registerObject(curve._startPoint, "x", "Start Point", ranges[0], ranges[1], ranges[2], "x", update);
ui.registerObject(curve._startPoint, "y", "Start Point", ranges[0], ranges[1], ranges[2], "y", update);
ui.registerObject(curve._startControlPoint, "x", "Start Control Point", ranges[0], ranges[1], ranges[2], "x", update);
ui.registerObject(curve._startControlPoint, "y", "Start Control Point", ranges[0], ranges[1], ranges[2], "y", update);
ui.registerObject(curve._endPoint, "x", "End Point", ranges[0], ranges[1], ranges[2], "x", update);
ui.registerObject(curve._endPoint, "y", "End Point", ranges[0], ranges[1], ranges[2], "y", update);
ui.registerObject(curve._endControlPoint, "x", "End Control Point", ranges[0], ranges[1], ranges[2], "x", update);
ui.registerObject(curve._endControlPoint, "y", "End Control Point", ranges[0], ranges[1], ranges[2], "y", update);
ui.registerObject(curve, "_numPoints", undefined, 5, 100, 1, "numPoints", update);
animator.run();
This diff is collapsed.
{
"type": "commonjs",
"name": "computergrafik-03",
"type": "module",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^2.9.0"
"@types/dat.gui": "^0.7.7",
"@types/three": "^0.139.0",
"typescript": "^4.5.4",
"vite": "^2.9.5"
},
"dependencies": {
"dat.gui": "^0.7.9",
......
'use strict'
import { Animated } from "./rendering";
import {
Line,
Vector2,
BufferGeometry,
LineBasicMaterial,
CubicBezierCurve3
} from "three";
import { Vector3 } from "three";
export class BezierCurve extends Animated {
/**
* @param {Vector2} startPoint
* @param {Vector2} endPoint
* @param {Vector2} startControlPoint
* @param {Vector2} endControlPoint
*/
constructor(startPoint, endPoint, startControlPoint, endControlPoint) {
super();
this._startPoint = startPoint;
this._endPoint = endPoint;
this._startControlPoint = startControlPoint;
this._endControlPoint = endControlPoint;
}
/**
* Called by the animator to update the curve in the scene.
*/
update() {
console.log("Updating curve");
this.generateCurvePoints(this._numPoints);
this.updateGeomBuffer();
if (this.lineObject !== undefined) this.lineObject.geometry = this.geomBuffer;
}
/**
* Creates a buffer geometry for the curve.
*/
updateGeomBuffer() {
// Case 1: No buffer geometry exists yet.
if (this.geomBuffer === undefined) {
this.geomBuffer = new BufferGeometry();
// Case 2: Buffer geometry exists, but needs to be updated with new amount of points.
} else if (this.geomBuffer.attributes.position.count !== this._numPoints) {
this.geomBuffer.dispose();
this.geomBuffer = new BufferGeometry();
}
this.geomBuffer.setFromPoints(this.curvePoints);
}
/**
* Create an object which can be registered by the scene.
* @returns {Line}
*/
createLine() {
this.update();
this.lineObject = new Line(this.geomBuffer, new LineBasicMaterial({ color: 0xffffff }));
this.lineObject.name = 'bezierCurve';
return this.lineObject;
}
/**
* Generates the points for the curve.
*/
generateCurvePoints() {
this.curvePoints = [];
const curve = new CubicBezierCurve3(
new Vector3(this._startPoint.x, this._startPoint.y, 0),
new Vector3(this._startControlPoint.x, this._startControlPoint.y, 0),
new Vector3(this._endPoint.x, this._endPoint.y, 0),
new Vector3(this._endControlPoint.x, this._endControlPoint.y, 0)
);
curve.getPoints(this._numPoints).forEach(point => {
this.curvePoints.push(new Vector2(point.x, point.y));
});
}
// start and end point / control points
_startPoint = new Vector2(0, 0);
_startControlPoint = new Vector2(0, 0);
_endPoint = new Vector2(0, 0);
_endControlPoint = new Vector2(0, 0);
// curve points && number of points to generate
curvePoints = [new Vector2(0, 0)];
_numPoints = 10;
// threejs stuff
geomBuffer = undefined;
lineObject = undefined;
}
export class BezierCurveDebug extends Animated {
/**
* @param {BezierCurve} curve
*/
constructor(curve) {
super();
this._curve = curve;
}
// craete a line object
createLine() {
this.update();
this.lineObject = new Line(this.geomBuffer, new LineBasicMaterial({ color: 0xffffff }));
this.lineObject.name = 'bezierCurveDebug';
return this.lineObject;
}
}
import * as CG from "./uitls/rendering";
import * as THREE from "three";
import { UI } from "./uitls/ui";
import { Vector3 } from "three";
import * as Interpolation from "./uitls/bezierCurve";
const render = new CG.RenderManager('#canvas', { near: 0.1, far: 1000, fov: 45, height: 1 });
const ui = new UI();
// create a cube
let geometry = new THREE.BoxGeometry(1, 1, 1);
let material = new THREE.MeshBasicMaterial({ color: 0xfffffff });
let cube = new THREE.Mesh(geometry, material);
// render.add(cube);
const startPoint = new THREE.Vector2(-1, -1);
const endPoint = new THREE.Vector2(1, 1);
const startControlPoint = new THREE.Vector2(-1, 1);
const endControlPoint = new THREE.Vector2(1, -1);
const curve = new Interpolation.BezierCurveTest(
startPoint,
endPoint,
startControlPoint,
endControlPoint
)
const line = curve.createLine();
render.add(line);
render.render();
ui.addModifiable(render);
ui.addModifiable(curve);
'use strict';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import {
PerspectiveCamera,
OrthographicCamera,
WebGLRenderer
} from 'three';
export const cameraTypes = {
PERSPECTIVE: 'PerspectiveCamera',
ORTHOGRAPHIC: 'OrthographicCamera',
};
/**
* Creates a camera with the given paramter
* Not all parameters are required.
*
* @param {object} settings
* @returns PerspectiveCamera or OrthographicCamera
*/
export function createCamera(settings) {
// EXAMPLE perspective camera
// {
// type: CAMERA_TYPE.PERSPECTIVE,
// fov: 70, near: 1e-3, far: 1e5,
// }
//
// EXAMPLE orthographic camera
// {
// type: CAMERA_TYPE.ORTHOGRAPHIC,
// height: 70, near: 1e-3, far: 1e5,
// }
const aspect = window.innerWidth / window.innerHeight;
if (!'type' in settings)
throw new Error('Camera type not specified');
if (settings.type === cameraTypes.PERSPECTIVE && !('fov' in settings))
settings['fov'] = 70;
if (settings.type === cameraTypes.ORTHOGRAPHIC && !('height' in settings))
settings['height'] = 1;
if (!('near' in settings))
settings['near'] = 1e-3;
if (!('far' in settings))
settings['far'] = 1e5;
if (settings.type === cameraTypes.PERSPECTIVE) {
return new PerspectiveCamera(
settings.fov, aspect,
settings.near, settings.far
);
}
else if (settings.type === cameraTypes.ORTHOGRAPHIC) {
return new OrthographicCamera(
aspect * -settings.height, aspect * settings.height,
settings.height, -settings.height,
settings.near, settings.far
);
}
}
/**
* Create a renderer attached to a HTML element.
* @param {string} tag id of the html element in dom
* @returns THREE.WebGLRenderer
*/
export function createRenderer(tag) {
let renderer = new WebGLRenderer({
canvas: document.querySelector(tag),
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
return renderer;
}
/**
*
* @param {THREE.Camera} camera
* @param {THREE.WebGLRenderer} renderer
* @returns OrbitControls
*/
export function createControls(camera, renderer) {
const controls = new OrbitControls(camera, renderer.domElement);
if (camera.type === cameraTypes.PERSPECTIVE) {
controls.minDistance = 1;
controls.maxDistance = 10;
} else if (camera.type === cameraTypes.ORTHOGRAPHIC) {
controls.enableRotate = false;
}
return controls;
}
/**
* Interface for animation
*/
export class Animated {
update() {}
}
/**
* Class to animate the scene. It uses requestAnimationFrame to update the scene.
* @param {THREE.WebGLRenderer} renderer
* @param {THREE.Scene} scene
* @param {THREE.Camera} camera
* @param {THREE.OrbitControls} controls
*/
export class Animate {
functions = [];
constructor(renderer, scene, camera, controls) {
this.renderer = renderer;
this.scene = scene;
this.camera = camera;
this.controls = controls;
}
// regist function whic are called during animation
register(object) {
if (object instanceof Animated)
this.functions.push(object);
}
// unregister function
unregister(object) {
if (object instanceof Animated)
this.functions = this.functions.filter(f => f !== object);
}
// update all registered functions
updateRegistered() {
this.functions.forEach(function (object) {
object.update();
});
}
run() {
this.renderer.render(this.scene, this.camera);
this.controls.update();
this.updateRegistered();
requestAnimationFrame(() => this.run());
}
}
\ No newline at end of file
import { Modifiable, UI, Updatable } from "./ui";
import {
Line,
Vector2,
Vector3,
BufferGeometry,
LineBasicMaterial,
CubicBezierCurve3
} from "three";
import { GUI } from "dat.gui";
export abstract class AbstractBezierCurve implements Modifiable, Updatable {
constructor(startPoint: Vector2, endPoint: Vector2, startControlPoint: Vector2, endControlPoint: Vector2) {
this._startPoint = startPoint;
this._endPoint = endPoint;
this._startControlPoint = startControlPoint;
this._endControlPoint = endControlPoint;
this._geomBuffer = new BufferGeometry();
}
abstract generateCurvePoints(): Vector2[];
updateGeomBuffer() {
this._geomBuffer.dispose();
this._geomBuffer = new BufferGeometry();
this._geomBuffer.setFromPoints(this._curvePoints);
}
createLine() {
this.update();
this._line = new Line(this._geomBuffer, new LineBasicMaterial({ color: 0xffffff }));
this._line.name = 'bezierCurve';
return this._line;
}
update() {
this._curvePoints = this.generateCurvePoints();
this.updateGeomBuffer();
if (this._line)
this._line.geometry = this._geomBuffer;
}
createElement(gui: GUI): void {
const folder = gui.addFolder('Bezier Curve');
UI.addVector(folder, this, this._startPoint, 'Start Point');
UI.addVector(folder, this, this._endPoint, 'End Point');
UI.addVector(folder, this, this._startControlPoint, 'Start Control Point');
UI.addVector(folder, this, this._endControlPoint, 'End Control Point');
folder.add(this, "_numPoints", 10, 100).step(1).name("Points").onChange(() => { this.update(); });
}
public _startPoint: Vector2;
public _endPoint: Vector2;
public _startControlPoint: Vector2;
public _endControlPoint: Vector2;
public _numPoints: number = 100;
public _line?: Line;
public _curvePoints: Vector2[] = [];
public _geomBuffer: BufferGeometry;
}
// TODO: Handles for the control points
export class Debug implements Updatable, Modifiable {
constructor(private _curve: AbstractBezierCurve) { }
update() {
this._curve.update();
}
createElement(gui: GUI): void {
const folder = gui.addFolder('Debug');
folder.add(this, "update").name("Update");
}
}
export class BezierCurveTest extends AbstractBezierCurve {
generateCurvePoints(): Vector2[] {
const curve = new CubicBezierCurve3(
new Vector3(this._startPoint.x, this._startPoint.y, 0),
new Vector3(this._startControlPoint.x, this._startControlPoint.y, 0),
new Vector3(this._endPoint.x, this._endPoint.y, 0),
new Vector3(this._endControlPoint.x, this._endControlPoint.y, 0)
);
let curvePoints: Vector2[] = [];
curve.getPoints(this._numPoints).forEach(point => {
curvePoints.push(new Vector2(point.x, point.y));
});
return curvePoints;
}
}
export class BezierCurve extends AbstractBezierCurve {
generateCurvePoints(): Vector2[] {
let curvePoints: Vector2[] = [];
// TODO: implement bezier curves
return curvePoints;
}
}
\ No newline at end of file
import {
Camera,
OrthographicCamera,
PerspectiveCamera,
WebGLRenderer,
Object3D,
Scene
} from 'three';
import { Modifiable } from './ui';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GUI } from 'dat.gui';
export interface CameraSettings {
near: number;
far: number;
fov: number;
height: number;
}
class CameraManager {
active: Camera;
cameras: Array<Camera>;
constructor(settings: CameraSettings) {
const aspect = window.innerWidth / window.innerHeight;
this.cameras = [
new PerspectiveCamera(settings.fov, aspect, settings.near, settings.far),
new OrthographicCamera(-aspect, aspect, 1, -1, settings.near, settings.far)
];
this.active = this.cameras[0];
this.active.position.set(0, 0, 5);
}
switchCamera(): void {
if (this.active == this.cameras[0]) {
this.active = this.cameras[1];
this.active.position.set(0, 0, 5);
}
else {
this.active = this.cameras[0];
}
}
camera(): Camera {
return this.active;
}
}
/**
* @class Rendering
* @description Class for rendering the scene.
*/
export class RenderManager implements Modifiable {
/**
* Create a rendere
* @param tag - the tag of the canvas element
* @param settings - the settings of the cameras
*/
constructor(tag: string, settings: CameraSettings) {
let element = document.querySelector(tag);
if (!element)
throw new Error('Canvas element not found');
this._renderer = new WebGLRenderer({
canvas: element,
antialias: true
});
this._renderer.setSize(window.innerWidth, window.innerHeight);
this._renderer.setPixelRatio(window.devicePixelRatio);
this._renderer.setClearColor(1);
this._cameraManager = new CameraManager(settings);
this._controls = new OrbitControls(this._cameraManager.camera(), this._renderer.domElement);
this._scene = new Scene();
}
createElement(gui: GUI): void {
const rendering = gui.addFolder('Rendering');
rendering.add(this, '_fps', 10, 60).step(1).name('FPS');
rendering.add(this._cameraManager, 'switchCamera').name('Switch camera').onFinishChange(() => {
if (this._cameraManager.camera().type == 'PerspectiveCamera' ? true : false)
this._controls.enableRotate = true;
else
this._controls.enableRotate = false;
this._controls.object = this._cameraManager.camera();
this._controls.reset();
});
}
/**
* Render routine (called by the main loop)
*/
render(): void {
// request frame
requestAnimationFrame(() => this.render());
let now = Date.now();
let delta = now - this._past;
let interval = 1000 / this._fps;
// update if delta is greater than 1/60
if (delta > interval) {
this._past = now - (delta % interval);
this._renderer.render(this._scene, this._cameraManager.camera());
this._controls.update();
}
}
/**
* Extends the scene with a new object from THREE.js
* @param object - the object to add to the scene
*/
add(object: Object3D): void {
this._scene.add(object);
}
/**
* Removes an object from the scene
* @param object - the object to remove from the scene
*/
remove(object: Object3D): void {
this._scene.remove(object);
}
/**
* Removes all objects from the scene
*/
reset(): void {
this._scene.children.forEach(child => {
this._scene.remove(child);
});
}
private _fps: number = 60;
private _controls: OrbitControls;
private _cameraManager: CameraManager;
private _renderer: WebGLRenderer;
private _scene: Scene;
private _past: number = Date.now();
}
import { GUI } from 'dat.gui';
/**
* Class which implement this interface do create a GUI element for the object.
* The element will be added to the GUI which is passed to the constructor.
* @interface Modifiable
*/
export interface Modifiable {
createElement(gui: GUI): void;
}
/**
* Class which implement this interface can be updated. It is important for
* the GUI element to be updated when the object is changed.
* @interface Updatable
*/
export interface Updatable {
update(): void;
}
export class UI {
private _gui: GUI;
constructor() {
this._gui = new GUI();
}
addModifiable(modifiable: Modifiable) {
modifiable.createElement(this._gui);
}
/**
* Generates a folder for any vector type.
* @param gui
* @param parent
* @param vector
* @param name
*/
static addVector<Vector>(gui: GUI, parent: Updatable, vector: Vector, name: string) : void {
const folder = gui.addFolder(name);
for (let c in vector)
if (c == 'x' || c == 'y' || c == 'z' || c == 'w')
folder.add(vector, c, -10, 10, 0.01).name(c).onChange(() => { parent.update(); });
}
}
\ No newline at end of file
import { GUI } from 'dat.gui';
// create a GUI class to load different uis
export class UserInterface {
constructor() {
}
/**
*
* @param {object} object
* @param {string} folder
* @param {Animated} observer
* @param {object} settings
*/
registerObject(object, attribute, folder, min = -1, max = 1, step = 0.1, label = "default", update) {
let f;
if (folder !== undefined) {
if (!(folder in this.folders)) this.folders[folder] = this.gui.addFolder(folder);
f = this.folders[folder];
}
else {
f = this.gui;
}
// create a new dat.gui controller for the object
const controller = f.add(object, attribute, min, max, step);
controller.onChange(update);
controller.name(label);
}
folders = {};
gui = new GUI();
}
/// <reference types="vite/client" />
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src"]
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment