commit 3555ea434ea7c03836e23d79fee26c22aa66e8a5 Author: mincomk Date: Sat Jun 7 13:10:40 2025 +0900 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4b80bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +dist/**/* +node_modules +pnpm-lock.yaml diff --git a/package.json b/package.json new file mode 100644 index 0000000..012e97c --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "computercraft-mutil", + "version": "1.0.0", + "description": "", + "license": "ISC", + "author": "", + "type": "commonjs", + "types": "./dist/main.d.ts", + "main": "./dist/main", + "files": [ + "dist/**/*.lua", + "dist/**/*.d.ts" + ], + "scripts": { + "build": "tstl" + }, + "devDependencies": { + "@eslint/js": "^9.28.0", + "@typescript-to-lua/language-extensions": "^1.19.0", + "computercraft-ts": "latest", + "eslint": "^9.28.0", + "globals": "^16.2.0", + "luamin": "^1.0.4", + "typescript": "^5.8.3", + "typescript-eslint": "^8.33.1", + "typescript-to-lua": "^1.31.1", + "typescript-tstl-plugin": "^0.3.2" + }, + "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..7f1f879 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,3 @@ +export * from "./multi-pid"; +export * from "./pid"; +export * from "./vec"; diff --git a/src/multi-pid.ts b/src/multi-pid.ts new file mode 100644 index 0000000..8f82104 --- /dev/null +++ b/src/multi-pid.ts @@ -0,0 +1,61 @@ +import { PID, PIDCallback, PIDParameters, PIDReport } from "./pid"; + +export type MultiPIDCallback = (channel: number, report: PIDReport) => any; + +export class MultiPID { + private pids: PID[]; + private channels: number; + + public constructor(channels: number, params: PIDParameters) { + this.channels = channels; + this.pids = Array(channels) + .fill(0) + .map((_) => new PID(params)); + } + + public setSetpoint(channel: number, setpoint: number) { + if (channel > this.channels - 1) + throw new Error("Channel index out of range: " + channel); + this.pids[channel].setSetpoint(setpoint); + } + + public setSetpoints(setpoints: (number | null)[]) { + if (setpoints.length != this.channels) + throw new Error("Setpoint length mismatch: " + setpoints.length); + setpoints + .filter((x) => x != null) + .forEach((x, i) => this.pids[i].setSetpoint(x)); + } + + public setParameter(channel: number, params: PIDParameters) { + if (channel > this.channels - 1) + throw new Error("Channel index out of range: " + channel); + this.pids[channel].setParameters(params); + } + + public setParameters(params: (PIDParameters | null)[]) { + if (params.length != this.channels) + throw new Error("Setpoint length mismatch: " + params.length); + params + .filter((x) => x != null) + .forEach((x, i) => this.pids[i].setParameters(x)); + } + + public updateOne(channel: number, measuredValue: number) { + if (channel > this.channels - 1) + throw new Error("Channel index out of range: " + channel); + return this.pids[channel].update(measuredValue); + } + + public updateAll(measuredValues: (number | null)[]) { + if (measuredValues.length != this.channels) + throw new Error("Values length mismatch: " + measuredValues.length); + return measuredValues + .filter((x) => x != null) + .map((x, i) => this.pids[i].update(x)); + } + + public setCallback(cb: MultiPIDCallback) { + this.pids.forEach((p, i) => p.setCallback((report) => cb(i, report))); + } +} diff --git a/src/pid.ts b/src/pid.ts new file mode 100644 index 0000000..f885bf3 --- /dev/null +++ b/src/pid.ts @@ -0,0 +1,80 @@ +export interface PIDParameters { + kP: number; + kI: number; + kD: number; +} + +export type PIDReport = { + timeMs: number; + measured: number; + setpoint: number; + output: number; + error: number; +}; + +export type PIDCallback = (report: PIDReport) => any; + +export class PID { + private params: PIDParameters; + private setpoint: number; + private prevError: number = 0; + private integral: number = 0; + private lastTime: number | null = null; + private handler: PIDCallback | null = null; + + constructor(params: PIDParameters, setpoint: number = 0) { + this.params = Object.assign({}, params); + this.setpoint = setpoint; + } + + public setParameters(params: PIDParameters) { + this.params = Object.assign({}, params); + } + + public setSetpoint(setpoint: number): void { + this.setpoint = setpoint; + this.prevError = 0; + this.integral = 0; + this.lastTime = null; + } + + public setCallback(handler: PIDCallback) { + this.handler = handler; + } + + public update(measuredValue: number): number { + const currentTime = os.epoch("utc"); + const error = this.setpoint - measuredValue; + + let deltaTime = 0; + if (this.lastTime !== null) { + deltaTime = (currentTime - this.lastTime) / 1000; // in seconds + } + + this.integral += error * deltaTime; + const derivative = deltaTime > 0 ? (error - this.prevError) / deltaTime : 0; + + const output = + this.params.kP * error + + this.params.kI * this.integral + + this.params.kD * derivative; + + this.prevError = error; + this.lastTime = currentTime; + + try { + if (this.handler != null) + this.handler({ + timeMs: currentTime, + measured: measuredValue, + setpoint: this.setpoint, + output, + error, + }); + } catch (e) { + print(e); + } + + return output; + } +} diff --git a/src/vec.ts b/src/vec.ts new file mode 100644 index 0000000..fa6b7a0 --- /dev/null +++ b/src/vec.ts @@ -0,0 +1,66 @@ +export class Vec3 { + public constructor( + public x: number, + public y: number, + public z: number, + ) { } + + public dot(b: Vec3): number { + return this.x * b.x + this.y * b.y + this.z * b.z; + } + + public cross(b: Vec3): Vec3 { + return new Vec3( + this.y * b.z - this.z * b.y, + this.z * b.x - this.x * b.z, + this.x * b.y - this.y * b.x, + ); + } + + public add(b: Vec3): Vec3 { + return new Vec3(this.x + b.x, this.y + b.y, this.z + b.z); + } + + public sub(b: Vec3): Vec3 { + return new Vec3(this.x - b.x, this.y - b.y, this.z - b.z); + } + + public mul(v: number): Vec3 { + return new Vec3(this.x * v, this.y * v, this.z * v); + } + + public len(): number { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + + public normalize(): Vec3 { + const len = this.len(); + if (len === 0) return new Vec3(0, 0, 0); + return new Vec3(this.x / len, this.y / len, this.z / len); + } + + public lerp(b: Vec3, t: number): Vec3 { + return this.add(b.sub(this).mul(t)); + } + + public slerp(b: Vec3, t: number): Vec3 { + const v0 = this.normalize(); + const v1 = b.normalize(); + + const dot = math.min(math.max(v0.dot(v1), -1), 1); + + const theta = math.acos(dot) * t; + + if (math.abs(theta) < 1e-6) return this.lerp(b, t); + + const relativeDir = b.sub(this.mul(dot)).normalize(); + + return this.mul(math.cos(theta)).add(relativeDir.mul(math.sin(theta))); + } +} + +export interface Quaternion { + x: number; + y: number; + z: number; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0c6d21c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/TypeScriptToLua/TypeScriptToLua/master/tsconfig-schema.json", + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext"], + "moduleResolution": "Node", + "types": ["computercraft-ts", "@typescript-to-lua/language-extensions"], + "strict": true, + "declaration": true, + "outDir": "dist/", + "rootDir": "src/", + "noImplicitAny": false + }, + "tstl": { + "luaTarget": "JIT", + "buildMode": "library", + "noImplicitSelf": true + }, + "include": ["src/**/*"] +}