scripts/core/units.js
import * as util from '../path-controller/util/util.js';
import {Vector2, Vector3, Vector4, Quat, Matrix4} from '../path-controller/util/vectormath.js';
/*
all units convert to meters
*/
const FLT_EPSILONE = 1.192092895507812e-07;
function myfloor(f) {
return Math.floor(f + FLT_EPSILONE*2.0);
}
function normString(s) {
//remove all whitespace
s = s.replace(/ /g, "").replace(/\t/g, "");
return s.toLowerCase();
}
function myToFixed(f, decimals) {
if (typeof f !== "number") {
return "(error)";
}
f = f.toFixed(decimals);
while (f.endsWith("0") && f.search(/\./) >= 0) {
f = f.slice(0, f.length - 1);
}
if (f.endsWith(".")) {
f = f.slice(0, f.length - 1);
}
if (f.length === 0)
f = "0";
return f.trim();
}
export const Units = [];
export class Unit {
static getUnit(name) {
if (name === "none" || name === undefined) {
return undefined;
}
for (let cls of Units) {
if (cls.unitDefine().name === name) {
return cls;
}
}
throw new Error("Unknown unit " + name);
}
static register(cls) {
Units.push(cls);
}
//subclassed static methods start here
static unitDefine() {
return {
name : "",
uiname : "",
type : "", //e.g. distance
icon : -1,
pattern: undefined //a re literal to validate strings
}
}
static parse(string) {
}
static validate(string) {
string = normString(string);
let def = this.unitDefine();
let m = string.match(def.pattern);
if (!m)
return false;
return m[0] === string;
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
}
static fromInternal(value) {
}
static buildString(value, decimals = 2) {
}
}
export class MeterUnit extends Unit {
static unitDefine() {
return {
name : "meter",
uiname : "Meter",
type : "distance",
icon : -1,
pattern: /-?\d+(\.\d*)?m$/
}
}
static parse(string) {
string = normString(string);
if (string.endsWith("m")) {
string = string.slice(0, string.length - 1);
}
return parseFloat(string);
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
return value;
}
static fromInternal(value) {
return value;
}
static buildString(value, decimals = 2) {
return "" + myToFixed(value, decimals) + " m";
}
}
Unit.register(MeterUnit);
export class InchUnit extends Unit {
static unitDefine() {
return {
name : "inch",
uiname : "Inch",
type : "distance",
icon : -1,
pattern: /-?\d+(\.\d*)?(in|inch)$/
}
}
static parse(string) {
string = string.toLowerCase();
let i = string.indexOf("i");
if (i >= 0) {
string = string.slice(0, i);
}
return parseInt(string);
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
return value*0.0254;
}
static fromInternal(value) {
return value/0.0254;
}
static buildString(value, decimals = 2) {
return "" + myToFixed(value, decimals) + "in";
}
}
Unit.register(InchUnit);
let foot_re = /((-?\d+(\.\d*)?ft)(-?\d+(\.\d*)?(in|inch))?)|(-?\d+(\.\d*)?(in|inch))$/
export class FootUnit extends Unit {
static unitDefine() {
return {
name : "foot",
uiname : "Foot",
type : "distance",
icon : -1,
pattern: foot_re
}
}
static parse(string) {
string = normString(string);
let i = string.search("ft");
let parts = [];
let vft = 0.0, vin = 0.0;
if (i >= 0) {
parts = string.split("ft");
let j = parts[1].search("in");
if (j >= 0) {
parts = [parts[0]].concat(parts[1].split("in"));
vin = parseFloat(parts[1]);
}
vft = parseFloat(parts[0]);
} else {
string = string.replace(/in/g, "");
vin = parseFloat(string);
}
return vin/12.0 + vft;
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
return value*0.3048;
}
static fromInternal(value) {
return value/0.3048;
}
static buildString(value, decimals = 2) {
let vft = myfloor(value);
let vin = ((value + FLT_EPSILONE*2)*12)%12;
if (vft === 0.0) {
return myToFixed(vin, decimals) + " in";
}
let s = "" + vft + " ft";
if (vin !== 0.0) {
s += " " + myToFixed(vin, decimals) + " in";
}
return s;
}
}
Unit.register(FootUnit);
let square_foot_re = /((-?\d+(\.\d*)?ft(\u00b2)?)(-?\d+(\.\d*)?(in|inch)(\u00b2)?)?)|(-?\d+(\.\d*)?(in|inch)(\u00b2)?)$/
export class SquareFootUnit extends FootUnit {
static unitDefine() {
return {
name : "square_foot",
uiname : "Square Feet",
type : "area",
icon : -1,
pattern: square_foot_re
}
}
static parse(string) {
string = string.replace(/\u00b2/g, "");
return super.parse(string);
}
static buildString(value, decimals = 2) {
let vft = myfloor(value);
let vin = ((value + FLT_EPSILONE*2)*12)%12;
if (vft === 0.0) {
return myToFixed(vin, decimals) + " in\u00b2";
}
let s = "" + vft + " ft\u00b2";
if (vin !== 0.0) {
s += " " + myToFixed(vin, decimals) + " in\u00b2";
}
return s;
}
}
Unit.register(SquareFootUnit);
export class MileUnit extends Unit {
static unitDefine() {
return {
name : "mile",
uiname : "Mile",
type : "distance",
icon : -1,
pattern: /-?\d+(\.\d+)?miles$/
}
}
static parse(string) {
string = normString(string);
string = string.replace(/miles/, "");
return parseFloat(string);
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
return value*1609.34;
}
static fromInternal(value) {
return value/1609.34;
}
static buildString(value, decimals = 3) {
return "" + myToFixed(value, decimals) + " miles";
}
}
Unit.register(MileUnit);
export class DegreeUnit extends Unit {
static unitDefine() {
return {
name : "degree",
uiname : "Degrees",
type : "angle",
icon : -1,
pattern: /-?\d+(\.\d+)?(\u00B0|degree|deg|d|degree|degrees)$/
}
}
static parse(string) {
string = normString(string);
if (string.search("d") >= 0) {
string = string.slice(0, string.search("d")).trim();
} else if (string.search("\u00B0") >= 0) {
string = string.slice(0, string.search("\u00B0")).trim();
}
return parseFloat(string);
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
return value/180.0*Math.PI;
}
static fromInternal(value) {
return value*180.0/Math.PI;
}
static buildString(value, decimals = 3) {
return "" + myToFixed(value, decimals) + " \u00B0";
}
};
Unit.register(DegreeUnit);
export class RadianUnit extends Unit {
static unitDefine() {
return {
name : "radian",
uiname : "Radians",
type : "angle",
icon : -1,
pattern: /-?\d+(\.\d+)?(r|rad|radian|radians)$/
}
}
static parse(string) {
string = normString(string);
if (string.search("r") >= 0) {
string = string.slice(0, string.search("r")).trim();
}
return parseFloat(string);
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
return value;
}
static fromInternal(value) {
return value;
}
static buildString(value, decimals = 3) {
return "" + myToFixed(value, decimals) + " r";
}
};
Unit.register(RadianUnit);
export function setBaseUnit(unit) {
Unit.baseUnit = unit;
}
window._getBaseUnit = () => Unit.baseUnit;
export function setMetric(val) {
Unit.isMetric = val;
}
Unit.isMetric = true;
Unit.baseUnit = "meter";
let numre1 = /[+\-]?[0-9]+(\.[0-9]*)?$/
let numre2 = /[+\-]?[0-9]?(\.[0-9]*)+$/
let hexre1 = /[+\-]?[0-9a-fA-F]+h$/
let hexre2 = /[+\-]?0x[0-9a-fA-F]+$/
let binre = /[+\-]?0b[01]+$/
let expre = /[+\-]?[0-9]+(\.[0-9]*)?[eE]\-?[0-9]+$/
let intre = /[+\-]?[0-9]+$/
function isnumber(s) {
s = ("" + s).trim();
function test(re) {
return s.search(re) === 0;
}
return test(intre) || test(numre1) || test(numre2) || test(hexre1) || test(hexre2) || test(binre) || test(expre);
}
export function parseValueIntern(string, baseUnit = undefined) {
string = string.trim();
if (string[0] === ".") {
string = "0" + string;
}
if (typeof baseUnit === "string") {
let base = Unit.getUnit(baseUnit);
if (base === undefined && baseUnit !== "none") {
console.warn("Unknown unit " + baseUnit);
return NaN;
}
baseUnit = base;
}
//unannotated string?
if (isnumber(string)) {
//assume base unit
let f = parseFloat(string);
return f;
}
if (baseUnit === undefined) {
console.warn("No base unit in units.js:parseValueIntern");
}
for (let unit of Units) {
let def = unit.unitDefine();
if (unit.validate(string)) {
console.log(unit);
let value = unit.parse(string);
if (baseUnit) {
value = unit.toInternal(value);
return baseUnit.fromInternal(value);
} else {
return value;
}
}
}
return NaN;
}
/* if displayUnit is undefined, final value will be converted from displayUnit to baseUnit */
export function parseValue(string, baseUnit = undefined, displayUnit = undefined) {
displayUnit = Unit.getUnit(displayUnit);
baseUnit = Unit.getUnit(baseUnit);
let f = parseValueIntern(string, displayUnit || baseUnit);
if (baseUnit) {
if (displayUnit) {
f = displayUnit.toInternal(f);
}
f = baseUnit.fromInternal(f);
}
return f;
}
export function isNumber(string) {
if (isnumber(string)) {
return true;
}
for (let unit of Units) {
let def = unit.unitDefine();
if (unit.validate(string)) {
return true;
}
}
return false;
}
export class PixelUnit extends Unit {
static unitDefine() {
return {
name : "pixel",
uiname : "Pixel",
type : "distance",
icon : -1,
pattern: /-?\d+(\.\d*)?px$/
}
}
static parse(string) {
string = normString(string);
if (string.endsWith("px")) {
string = string.slice(0, string.length - 2).trim();
}
return parseFloat(string);
}
//convert to internal units,
//e.g. meters for distance
static toInternal(value) {
return value;
}
static fromInternal(value) {
return value;
}
static buildString(value, decimals = 2) {
return "" + myToFixed(value, decimals) + "px";
}
}
Unit.register(PixelUnit);
export function convert(value, unita, unitb) {
if (typeof unita === "string")
unita = Unit.getUnit(unita);
if (typeof unitb === "string")
unitb = Unit.getUnit(unitb);
return unitb.fromInternal(unita.toInternal(value));
}
/**
*
* @param value Note: is not converted to internal unit
* @param unit: Unit to use, should be a string referencing unit type, see unitDefine().name
* @returns {*}
*/
export function buildString(value, baseUnit = Unit.baseUnit, decimalPlaces = 3, displayUnit = Unit.baseUnit) {
if (typeof baseUnit === "string" && baseUnit !== "none") {
baseUnit = Unit.getUnit(baseUnit);
}
if (typeof displayUnit === "string" && displayUnit !== "none") {
displayUnit = Unit.getUnit(displayUnit);
}
if (baseUnit !== "none" && displayUnit !== baseUnit && displayUnit !== "none") {
value = convert(value, baseUnit, displayUnit);
}
if (displayUnit !== "none") {
return displayUnit.buildString(value, decimalPlaces);
} else {
return myToFixed(value, decimalPlaces);
}
}
window._parseValueTest = parseValue;
window._buildStringTest = buildString;