initial commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dist/**/*
|
||||||
|
node_modules
|
||||||
|
pnpm-lock.yaml
|
||||||
30
package.json
Normal file
30
package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
3
src/main.ts
Normal file
3
src/main.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./multi-pid";
|
||||||
|
export * from "./pid";
|
||||||
|
export * from "./vec";
|
||||||
61
src/multi-pid.ts
Normal file
61
src/multi-pid.ts
Normal file
@@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/pid.ts
Normal file
80
src/pid.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/vec.ts
Normal file
66
src/vec.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -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/**/*"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user