jsketcher/web/app/sketcher/constr/ANConstraints.js
Val Erastov (xibyte) 2fafec904c polynomial analysis
2020-01-07 00:17:20 -08:00

238 lines
5.9 KiB
JavaScript

import {R_DistancePP, R_Equal, R_PointOnLine} from "./residuals";
import {indexById} from "../../../../modules/gems/iterables";
import {DEG_RAD, distanceAB} from "../../math/math";
import {COS_FN, Polynomial, POW_1_FN, POW_2_FN, SIN_FN} from "./polynomial";
import {Types} from "../io";
export const ConstraintDefinitions = indexById([
{
id: 'PCoincident',
name: 'Two Points Coincidence',
defineParamsScope: ([p1, p2], callback) => {
p1.visitParams(callback);
p2.visitParams(callback);
},
collectPolynomials: (polynomials, [x1, y1, x2, y2]) => {
polynomials.push(new Polynomial(0)
.monomial(1)
.term(x1, POW_1_FN)
.monomial(-1)
.term(x2, POW_1_FN)
);
polynomials.push(new Polynomial(0)
.monomial(1)
.term(y1, POW_1_FN)
.monomial(-1)
.term(y2, POW_1_FN)
);
},
},
{
id: 'TangentLC',
name: 'Line & Circle Tangency',
constants: {
inverted: {
type: 'boolean',
description: 'whether the circle attached from the opposite side',
initialValue: () => false
}
},
defineParamsScope: ([segment, circle], callback) => {
callback(segment.params.ang);
callback(segment.params.w);
circle.c.visitParams(callback);
callback(circle.r);
},
collectPolynomials: (polynomials, [ang, w, cx, cy, r], {inverted}) => {
polynomials.push(new Polynomial(0)
.monomial(1)
.term(cx, POW_1_FN)
.term(ang, COS_FN)
.monomial(1)
.term(cy, POW_1_FN)
.term(ang, SIN_FN)
.monomial(-1)
.term(w, POW_1_FN)
.monomial(- (inverted ? -1 : 1))
.term(r, POW_1_FN)
);
},
},
{
id: 'PointOnLine',
name: 'Point On Line',
defineParamsScope: ([pt, segment], callback) => {
pt.visitParams(callback);
callback(segment.params.ang);
callback(segment.params.w);
},
collectResiduals: (residuals, params) => {
residuals.push([R_PointOnLine, params, []]);
},
collectPolynomials: (polynomials, [x, y, ang, w]) => {
polynomials.push(new Polynomial(0)
.monomial(1)
.term(x, POW_1_FN)
.term(ang, COS_FN)
.monomial(1)
.term(y, POW_1_FN)
.term(ang, SIN_FN)
.monomial(-1)
.term(w, POW_1_FN)
);
},
},
{
id: 'DistancePP',
name: 'Distance Between Two Point',
constants: {
distance: {
type: 'number',
description: 'the distance between two points',
initialValue: (constraint) => {
const [a, b] = constraint.object;
return distanceAB(a, b).toFixed(2) + '';
}
}
},
defineParamsScope: ([pt, segment], callback) => {
pt.visitParams(callback);
callback(segment.params.ang);
callback(segment.params.w);
},
collectResiduals: (residuals, params, {distance}) => {
residuals.push([R_DistancePP, params, [distance]]);
},
collectPolynomials: (polynomials, [x1, y1, x2, y2], {distance}) => {
polynomials.push(new Polynomial( - distance * distance)
.monomial(1)
.term(x1, POW_2_FN)
.monomial(1)
.term(x2, POW_2_FN)
.monomial(-2)
.term(x1, POW_1_FN)
.term(x2, POW_1_FN)
.monomial(1)
.term(y1, POW_2_FN)
.monomial(1)
.term(y2, POW_2_FN)
.monomial(-2)
.term(y1, POW_1_FN)
.term(y2, POW_1_FN)
);
},
},
{
id: 'Angle',
name: 'Absolute Line Angle',
constants: {
angle: {
type: 'number',
description: 'line angle',
initialValue: (constraint) => {
let degrees = constraint.objects[0].params.ang.get() / DEG_RAD;
degrees = (degrees + 360 - 90) % 360;
return degrees.toFixed(2) + '';
},
transform: degree => ( (degree + 90) % 360 ) * DEG_RAD
}
},
defineParamsScope: ([segment], callback) => {
callback(segment.params.ang);
},
collectPolynomials: (polynomials, [x], {angle}) => {
polynomials.push(new Polynomial( - angle).monomial(1).term(x, POW_1_FN));
},
},
]);
export class AlgNumConstraint {
static Counter = 0;
constructor(schema, objects, constants) {
this.id = schema.id + ':' + (AlgNumConstraint.Counter ++); // only for debug purposes - not persisted
this.objects = objects;
this.constants = constants;
this.resolvedConstants = undefined;
this.internal = false;
this.schema = schema;
this.params = [];
this.schema.defineParamsScope(this.objects, p => this.params.push(p));
// this.paramSet = new Set(this.params);
}
collectPolynomials(polynomials) {
this.resolveConstants();
this.schema.collectPolynomials(polynomials, this.params, this.resolvedConstants);
}
resolveConstants() {
if (this.constants) {
if (!this.resolvedConstants) {
this.resolvedConstants = {};
}
Object.keys(this.constants).map(name => {
let def = this.schema.constants[name];
if (def.type === 'number') {
let val = parseFloat(this.constants[name]);
if (def.transform) {
val = def.transform(val);
}
this.resolvedConstants[name] = val;
}
});
}
}
write() {
return {
typeId: this.schema.id,
objects: this.objects.map(o => o.id),
constants: this.constants
}
}
static read({typeId, objects, constants}, index) {
const schema = ConstraintDefinitions[typeId];
if (!schema) {
throw "constraint schema ' + typeId + ' doesn't exist";
}
return new AlgNumConstraint(schema, objects.map(oId => index[oId]), constants);
}
initConstants() {
if (this.schema.constants) {
this.constants = {};
Object.keys(this.schema.constants).map(name => {
this.constants[name] = this.schema.constants[name].initialValue(this);
});
}
}
}