mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 08:25:19 +01:00
polynomial analysis
This commit is contained in:
parent
58f3ac1846
commit
da613a082e
14 changed files with 434 additions and 97 deletions
|
|
@ -34,5 +34,6 @@ export function compositeFn() {
|
|||
funcs.push(fn);
|
||||
}
|
||||
}
|
||||
fn.push = push;
|
||||
return fn
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import numeric from 'numeric';
|
||||
import {_vec, _matrix} from './math'
|
||||
|
||||
const SUCCESS = 1, ITER_LIMIT = 2, SMALL_DELTA = 3, SMALL_STEP = 4, DIVERGENCE = 5, INVALID_STATE = 6;
|
||||
|
||||
|
||||
//Added strong wolfe condition to numeric's uncmin
|
||||
export function fmin_bfgs(f,x0,tol,gradient,maxit,callback,options) {
|
||||
var grad = numeric.gradient;
|
||||
|
|
@ -256,9 +259,10 @@ var inv = function inv(A) {
|
|||
};
|
||||
|
||||
var _result = function(evalCount, error, returnCode) {
|
||||
this.evalCount = evalCount;
|
||||
this.error = error;
|
||||
this.returnCode = returnCode;
|
||||
return {
|
||||
evalCount, error, returnCode,
|
||||
success: returnCode === SUCCESS
|
||||
};
|
||||
};
|
||||
|
||||
var dog_leg = function (subsys, rough) {
|
||||
|
|
@ -279,7 +283,7 @@ var dog_leg = function (subsys, rough) {
|
|||
var csize = subsys.constraints.length;
|
||||
|
||||
if (xsize == 0) {
|
||||
return new _result(0, 0, 1);
|
||||
return _result(0, 0, 1);
|
||||
}
|
||||
|
||||
var vec = _vec;
|
||||
|
|
@ -330,8 +334,6 @@ var dog_leg = function (subsys, rough) {
|
|||
var iter = 0, returnCode = 0;
|
||||
//var log = [];
|
||||
|
||||
var SUCCESS = 1, ITER_LIMIT = 2, SMALL_DELTA = 3, SMALL_STEP = 4, DIVERGENCE = 5, INVALID_STATE = 6;
|
||||
|
||||
while (returnCode === 0) {
|
||||
optim.DEBUG_HANDLER(iter, err);
|
||||
|
||||
|
|
@ -457,7 +459,7 @@ var dog_leg = function (subsys, rough) {
|
|||
}
|
||||
//log.push(returnCode);
|
||||
//window.___log(log);
|
||||
return new _result(iter, err, returnCode);
|
||||
return _result(iter, err, returnCode);
|
||||
};
|
||||
|
||||
var cg = function(A, x, b, tol, maxIt) {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,46 @@ export default [
|
|||
pm.refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
shortName: 'Length',
|
||||
description: 'Segment Length',
|
||||
selectionMatcher: (selection) => matchAll(selection, Segment, 1),
|
||||
|
||||
invoke: ctx => {
|
||||
const {viewer} = ctx;
|
||||
|
||||
const [firstSegment, ...others] = viewer.selected;
|
||||
|
||||
const firstConstr = new AlgNumConstraint(ConstraintDefinitions.SegmentLength, [firstSegment]);
|
||||
firstConstr.initConstants();
|
||||
|
||||
editConstraint(ctx, firstConstr, () => {
|
||||
const pm = viewer.parametricManager;
|
||||
pm.algnNumSystem.addConstraint(firstConstr);
|
||||
for (let other of others) {
|
||||
pm.algnNumSystem.addConstraint(new AlgNumConstraint(ConstraintDefinitions.SegmentLength, [other], {...firstConstr.constants}));
|
||||
}
|
||||
pm.refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
shortName: 'Lock',
|
||||
description: 'Lock Point',
|
||||
selectionMatcher: (selection) => matchTypes(selection, EndPoint, 1),
|
||||
|
||||
invoke: ctx => {
|
||||
const {viewer} = ctx;
|
||||
|
||||
const [point] = viewer.selected;
|
||||
|
||||
const constr = new AlgNumConstraint(ConstraintDefinitions.LockPoint, [point]);
|
||||
constr.initConstants();
|
||||
editConstraint(ctx, constr, () => viewer.parametricManager.addAlgNum(constr));
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -111,18 +111,13 @@ export const ConstraintDefinitions = indexById([
|
|||
initialValue: (constraint) => {
|
||||
const [a, b] = constraint.objects;
|
||||
return distanceAB(a, b);
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
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]]);
|
||||
defineParamsScope: ([pt1, pt2], callback) => {
|
||||
pt1.visitParams(callback);
|
||||
pt2.visitParams(callback);
|
||||
},
|
||||
|
||||
collectPolynomials: (polynomials, [x1, y1, x2, y2], {distance}) => {
|
||||
|
|
@ -201,6 +196,85 @@ export const ConstraintDefinitions = indexById([
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'SegmentLength',
|
||||
name: 'Segment Length',
|
||||
constants: {
|
||||
length: {
|
||||
type: 'number',
|
||||
description: 'length of the segment',
|
||||
initialValue: (constraint) => {
|
||||
const [segment] = constraint.objects;
|
||||
const dx = segment.b.x - segment.a.x;
|
||||
const dy = segment.b.y - segment.a.y;
|
||||
return Math.sqrt(dx*dx + dy*dy);
|
||||
},
|
||||
|
||||
// transform: length => length * length
|
||||
}
|
||||
},
|
||||
|
||||
defineParamsScope: ([segment], callback) => {
|
||||
callback(segment.params.t);
|
||||
},
|
||||
|
||||
collectPolynomials: (polynomials, [t], {length}) => {
|
||||
polynomials.push(new Polynomial( - length).monomial(1).term(t, POW_1_FN));
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'Polar',
|
||||
name: 'Polar Coordinate',
|
||||
|
||||
defineParamsScope: ([segment, originPt, targetPt], callback) => {
|
||||
callback(segment.params.ang);
|
||||
callback(segment.params.t);
|
||||
originPt.visitParams(callback);
|
||||
targetPt.visitParams(callback);
|
||||
},
|
||||
|
||||
collectPolynomials: (polynomials, [ang, t, x1, y1, x2, y2]) => {
|
||||
|
||||
|
||||
// v = [sin(ang), - cos(ang)]
|
||||
// v * t = pt2 - pt1
|
||||
|
||||
//sin(ang) * t - x2 + x1
|
||||
//-cos(ang) * t - y2 + y1
|
||||
|
||||
polynomials.push(new Polynomial().monomial() .term(ang, SIN_FN).term(t, POW_1_FN).monomial(-1).term(x2, POW_1_FN).monomial(1).term(x1, POW_1_FN));
|
||||
polynomials.push(new Polynomial().monomial(-1).term(ang, COS_FN).term(t, POW_1_FN).monomial(-1).term(y2, POW_1_FN).monomial(1).term(y1, POW_1_FN));
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
id: 'LockPoint',
|
||||
name: 'Lock Point',
|
||||
constants: {
|
||||
x: {
|
||||
type: 'number',
|
||||
description: 'X Coordinate',
|
||||
initialValue: (constraint) => constraint.objects[0].x,
|
||||
},
|
||||
y: {
|
||||
type: 'number',
|
||||
description: 'y Coordinate',
|
||||
initialValue: (constraint) => constraint.objects[0].y,
|
||||
}
|
||||
},
|
||||
|
||||
defineParamsScope: ([pt], callback) => {
|
||||
pt.visitParams(callback);
|
||||
},
|
||||
|
||||
collectPolynomials: (polynomials, [px, py], {x, y}) => {
|
||||
polynomials.push(new Polynomial(-x).monomial().term(px, POW_1_FN));
|
||||
polynomials.push(new Polynomial(-y).monomial().term(py, POW_1_FN));
|
||||
},
|
||||
}
|
||||
|
||||
]);
|
||||
|
||||
export class AlgNumConstraint {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class AlgNumSubSystem {
|
|||
|
||||
generator = NOOP;//new BoundaryObjectsGenerator();
|
||||
|
||||
constraints = [];
|
||||
allConstraints = [];
|
||||
|
||||
ownParams = new Set();
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ export class AlgNumSubSystem {
|
|||
|
||||
beingSolvedParams = new Set();
|
||||
|
||||
eliminatedParams = new Map();
|
||||
|
||||
// generators = [];
|
||||
subSystems = []; //subsystems are generated by generators only
|
||||
|
|
@ -28,13 +29,31 @@ export class AlgNumSubSystem {
|
|||
polynomials = [];
|
||||
substitutedParams = [];
|
||||
polyToConstr = new Map();
|
||||
|
||||
conflicting = new Set();
|
||||
redundant = new Set();
|
||||
|
||||
snapshot = new Map();
|
||||
|
||||
dof = 0;
|
||||
|
||||
SubSystem(generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
|
||||
get fullyConstrained() {
|
||||
return this.dof === 0;
|
||||
}
|
||||
|
||||
validConstraints(callback) {
|
||||
this.allConstraints.forEach(c => {
|
||||
if (!this.conflicting.has(c)) {
|
||||
callback(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addConstraint(constraint, _ancestorParams) {
|
||||
|
||||
if (this.canBeAdded(constraint.params)) {
|
||||
|
|
@ -42,20 +61,73 @@ export class AlgNumSubSystem {
|
|||
// this.constraints
|
||||
}
|
||||
|
||||
this.constraints.push(constraint);
|
||||
this.makeSnapshot();
|
||||
|
||||
this.allConstraints.push(constraint);
|
||||
|
||||
this.prepare();
|
||||
if (!this.isConflicting(constraint)) {
|
||||
this.solveFine();
|
||||
console.log(this.solveStatus);
|
||||
if (!this.solveStatus.success) {
|
||||
console.log("adding to conflicts");
|
||||
this.conflicting.add(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isConflicting(constraint)) {
|
||||
this.rollback();
|
||||
// } else if (this.fullyConstrained) {
|
||||
// this.rollback();
|
||||
// this.conflicting.add(constraint);
|
||||
// this.redundant.add(constraint);
|
||||
} else {
|
||||
this.updateDOF();
|
||||
this.updateFullyConstrainedObjects();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isConflicting(constraint) {
|
||||
return this.conflicting.has(constraint);
|
||||
}
|
||||
|
||||
updateDOF() {
|
||||
|
||||
let vars = 0;
|
||||
let equations = 0;
|
||||
|
||||
this.validConstraints(c => {
|
||||
equations ++;
|
||||
c.params.forEach(p => {
|
||||
if (!this.readOnlyParams.has(p)) {
|
||||
vars++
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.dof = vars - equations;
|
||||
}
|
||||
|
||||
makeSnapshot() {
|
||||
this.snapshot.clear();
|
||||
this.validConstraints(c => c.params.forEach(p => this.snapshot.set(p, p.get())));
|
||||
}
|
||||
|
||||
rollback() {
|
||||
this.snapshot.forEach((val, param) => param.set(val));
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.polynomials = [];
|
||||
this.substitutedParams = [];
|
||||
this.polyToConstr.clear();
|
||||
this.conflicting.clear();
|
||||
this.redundant.clear();
|
||||
this.eliminatedParams.clear();
|
||||
this.beingSolvedParams.clear();
|
||||
this.polyToConstr.clear();
|
||||
}
|
||||
|
||||
evaluatePolynomials() {
|
||||
this.constraints.forEach(c => {
|
||||
this.validConstraints(c => {
|
||||
c.collectPolynomials(this.polynomials);
|
||||
this.polynomials.forEach(p => this.polyToConstr.set(p, c))
|
||||
});
|
||||
|
|
@ -63,11 +135,6 @@ export class AlgNumSubSystem {
|
|||
console.log('reducing system:');
|
||||
this.polynomials.forEach(p => console.log(p.toString()));
|
||||
|
||||
const toEliminate = Array.from(this.readOnlyParams);
|
||||
|
||||
const toSubstitute = [];
|
||||
const linearSub = [];
|
||||
|
||||
let requirePass = true;
|
||||
|
||||
while (requirePass) {
|
||||
|
|
@ -79,74 +146,74 @@ export class AlgNumSubSystem {
|
|||
}
|
||||
|
||||
if (polynomial.monomials.length === 0) {
|
||||
if (!eqEps(polynomial.constant, 0)) {
|
||||
this.conflicting.add(this.polyToConstr.get(polynomial));
|
||||
this.conflicting.add(this.polyToConstr.get(polynomial));
|
||||
console.log("CONFLICT: " + polynomial.toString());
|
||||
if (eqEps(polynomial.constant, 0)) {
|
||||
this.redundant.add(this.polyToConstr.get(polynomial));
|
||||
console.log("REDUNDANT");
|
||||
}
|
||||
this.polynomials[i] = null;
|
||||
} else if (polynomial.monomials.length === 1) {
|
||||
} else if (polynomial.isLinear && polynomial.monomials.length === 1) {
|
||||
const monomial = polynomial.monomials[0];
|
||||
const terms = monomial.terms;
|
||||
if (terms.length === 1) {
|
||||
const term = terms[0];
|
||||
if (term.fn.degree === 1) {
|
||||
term.param.set(- polynomial.constant / monomial.constant);
|
||||
toEliminate.push(term.param);
|
||||
const p = term.param;
|
||||
const val = - polynomial.constant / monomial.constant;
|
||||
p.set(val);
|
||||
|
||||
this.eliminatedParams.set(p, val);
|
||||
|
||||
for (let polynomial of this.polynomials) {
|
||||
if (polynomial) {
|
||||
polynomial.eliminate(p, val);
|
||||
}
|
||||
}
|
||||
|
||||
requirePass = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.polynomials[i] = null;
|
||||
} else if (polynomial.monomials.length === 2 && polynomial.isLinear) {
|
||||
const [m1, m2] = polynomial.monomials;
|
||||
let p1 = m1.linearParam;
|
||||
let p2 = m2.linearParam;
|
||||
|
||||
const constant = - m2.constant / m1.constant;
|
||||
if (eqEps(polynomial.constant, 0)) {
|
||||
toSubstitute.push([m1.linearParam, m2.linearParam, constant]);
|
||||
this.substitutedParams.push([m1.linearParam, new Polynomial().monomial(constant).term(m2.linearParam, POW_1_FN)]);
|
||||
} else {
|
||||
linearSub.push([m1.param, m2.param, constant, - polynomial.constant / m1.constant]);
|
||||
}
|
||||
this.polynomials[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
while (toEliminate.length) {
|
||||
requirePass = true;
|
||||
const param = toEliminate.pop();
|
||||
for (let polynomial of this.polynomials) {
|
||||
if (polynomial) {
|
||||
polynomial.eliminate(param, param.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
while (toSubstitute.length) {
|
||||
requirePass = true;
|
||||
const [param, toParam, dotConstant] = toSubstitute.pop();
|
||||
for (let polynomial of this.polynomials) {
|
||||
if (polynomial) {
|
||||
polynomial.substitute(param, toParam, dotConstant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (linearSub.length) {
|
||||
while (linearSub.length) {
|
||||
const [param, toParam, k, b] = linearSub.pop();
|
||||
|
||||
let transaction = compositeFn();
|
||||
for (let polynomial of this.polynomials) {
|
||||
if (polynomial) {
|
||||
const polyTransaction = polynomial.linearSubstitution(param, toParam, k, b);
|
||||
if (!polyTransaction) {
|
||||
transaction = null;
|
||||
break;
|
||||
for (let polynomial of this.polynomials) {
|
||||
if (polynomial) {
|
||||
polynomial.substitute(p1, p2, constant);
|
||||
}
|
||||
transaction.push(polyTransaction);
|
||||
transaction.push(() => this.substitutedParams.push(param, new Polynomial(b).monomial(k).term(toParam, POW_1_FN)));
|
||||
}
|
||||
}
|
||||
if (transaction) {
|
||||
this.substitutedParams.push([p1, new Polynomial().monomial(constant).term(p2, POW_1_FN)]);
|
||||
this.polynomials[i] = null;
|
||||
requirePass = true;
|
||||
transaction();
|
||||
} else {
|
||||
const b = - polynomial.constant / m1.constant;
|
||||
|
||||
let transaction = compositeFn();
|
||||
for (let polynomial of this.polynomials) {
|
||||
if (polynomial) {
|
||||
const polyTransaction = polynomial.linearSubstitution(p1, p2, constant, b);
|
||||
if (!polyTransaction) {
|
||||
transaction = null;
|
||||
break;
|
||||
}
|
||||
transaction.push(polyTransaction);
|
||||
transaction.push(() => {
|
||||
this.substitutedParams.push([p1, new Polynomial(b).monomial(constant).term(p2, POW_1_FN)]);
|
||||
this.polynomials[i] = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (transaction) {
|
||||
transaction();
|
||||
requirePass = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,12 +235,13 @@ export class AlgNumSubSystem {
|
|||
|
||||
this.evaluatePolynomials();
|
||||
|
||||
// this.polynomialClusters = splitByIsolatedClusters(this.polynomials);
|
||||
|
||||
console.log('solving system:');
|
||||
this.polynomials.forEach(p => console.log(p.toString()));
|
||||
console.log('with respect to:');
|
||||
this.substitutedParams.forEach(([x, expr]) => console.log('X' + x.id + ' = ' + expr.toString()));
|
||||
|
||||
|
||||
const residuals = [];
|
||||
|
||||
this.polynomials.forEach(p => residuals.push(p.asResidual()));
|
||||
|
|
@ -189,6 +257,63 @@ export class AlgNumSubSystem {
|
|||
this.numericalSolver = prepare(residuals);
|
||||
}
|
||||
|
||||
splitByIsolatedClusters(polynomials) {
|
||||
|
||||
const graph = [polynomials.length];
|
||||
const params = [];
|
||||
const paramMap = new Map();
|
||||
polynomials.forEach((pl, i) => pl.visitParams(p => {
|
||||
let pIndex = paramMap.get(p);
|
||||
if (pIndex === undefined) {
|
||||
pIndex = paramMap.size;
|
||||
paramMap.set(p, pIndex);
|
||||
params.push(p);
|
||||
}
|
||||
graph[i][pIndex] = 1;
|
||||
}));
|
||||
|
||||
const visited = new Set();
|
||||
const clusters = [];
|
||||
|
||||
for (let param of params) {
|
||||
if (visited.has(param)) {
|
||||
continue
|
||||
}
|
||||
const stack = [param];
|
||||
const isolation = [];
|
||||
while (stack.length) {
|
||||
const p = stack.pop();
|
||||
if (visited.has(p)) {
|
||||
continue;
|
||||
}
|
||||
isolation.push(p);
|
||||
const index = paramMap.get(p);
|
||||
for (let i = 0; i < graph.length; ++i) {
|
||||
if (graph[i][index] === 1) {
|
||||
for (let j = 0; j < params.length; j ++) {
|
||||
if (j !== index) {
|
||||
stack.push(params[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isolation.length) {
|
||||
clusters.push(new Cluster(isolation));
|
||||
}
|
||||
}
|
||||
|
||||
return clusters;
|
||||
}
|
||||
|
||||
solveRough() {
|
||||
this.solve(true);
|
||||
}
|
||||
|
||||
solveFine() {
|
||||
this.solve(false);
|
||||
}
|
||||
|
||||
|
||||
solve(rough) {
|
||||
this.generator();
|
||||
|
|
@ -197,7 +322,15 @@ export class AlgNumSubSystem {
|
|||
solverParam.set(solverParam.objectParam.get());
|
||||
});
|
||||
|
||||
this.numericalSolver.solveSystem(rough);
|
||||
const solveStatus = this.numericalSolver.solveSystem(rough);
|
||||
if (!rough) {
|
||||
this.solveStatus = solveStatus;
|
||||
}
|
||||
console.log('numerical result: ' + solveStatus.success);
|
||||
|
||||
for (let [p, val] of this.eliminatedParams) {
|
||||
p.set(val);
|
||||
}
|
||||
|
||||
this.beingSolvedParams.forEach(solverParam => {
|
||||
solverParam.objectParam.set(solverParam.get());
|
||||
|
|
@ -208,6 +341,27 @@ export class AlgNumSubSystem {
|
|||
}
|
||||
}
|
||||
|
||||
updateFullyConstrainedObjects() {
|
||||
|
||||
|
||||
|
||||
this.validConstraints(c => {
|
||||
|
||||
c.objects.forEach(obj => {
|
||||
|
||||
let allLocked = true;
|
||||
|
||||
obj.visitParams(p => {
|
||||
|
||||
if (!this.eliminatedParams.has(p)) {
|
||||
allLocked = false;
|
||||
}
|
||||
});
|
||||
|
||||
obj.fullyConstrained = allLocked;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
canBeAdded(subjectParams, ancestorParams) {
|
||||
|
|
@ -223,6 +377,41 @@ export class AlgNumSubSystem {
|
|||
}
|
||||
|
||||
|
||||
class Cluster {
|
||||
|
||||
constructor(polynomials) {
|
||||
this.polynomials = polynomials;
|
||||
this.beingSolvedParams = new Set();
|
||||
const residuals = [];
|
||||
|
||||
this.polynomials.forEach(p => residuals.push(p.asResidual()));
|
||||
|
||||
for (let residual of residuals) {
|
||||
residual.params.forEach(solverParam => {
|
||||
if (!this.beingSolvedParams.has(solverParam)) {
|
||||
solverParam.reset(solverParam.objectParam.get());
|
||||
this.beingSolvedParams.add(solverParam);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
solve(rough) {
|
||||
this.beingSolvedParams.forEach(solverParam => {
|
||||
solverParam.set(solverParam.objectParam.get());
|
||||
});
|
||||
|
||||
this.solveStatus = this.numericalSolver.solveSystem(rough);
|
||||
|
||||
this.beingSolvedParams.forEach(solverParam => {
|
||||
solverParam.objectParam.set(solverParam.get());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class AlgNumSystem {
|
||||
constraints = [];
|
||||
generators = [];
|
||||
|
|
|
|||
|
|
@ -38,11 +38,16 @@ export class Polynomial {
|
|||
|
||||
substitute(param, toParam, dotConstant) {
|
||||
for (let m of this.monomials) {
|
||||
let touched = false;
|
||||
for (let i = 0; i < m.terms.length; ++i) {
|
||||
if (m.terms[i].param === param) {
|
||||
m.substitute(i, toParam, dotConstant);
|
||||
touched = true;
|
||||
}
|
||||
}
|
||||
if (touched) {
|
||||
m.mergeTerms();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +166,14 @@ export class Polynomial {
|
|||
|
||||
}
|
||||
|
||||
visitParams(callback) {
|
||||
for (let m of this.monomials) {
|
||||
for (let t of m.terms) {
|
||||
callback(t.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
|
||||
return this.monomials.map(m => {
|
||||
|
|
@ -227,8 +240,12 @@ export class Monomial {
|
|||
this.terms.splice(i, 1);
|
||||
}
|
||||
|
||||
substitute(i, toParam, dotConstant) {
|
||||
substitute(index, toParam, dotConstant) {
|
||||
this.terms[index].param = toParam;
|
||||
this.constant *= dotConstant;
|
||||
}
|
||||
|
||||
mergeTerms() {
|
||||
let wasMerge = false;
|
||||
for (let i = 0; i < this.terms.length; ++i) {
|
||||
const merger = this.terms[i];
|
||||
|
|
@ -250,7 +267,6 @@ export class Monomial {
|
|||
if (wasMerge) {
|
||||
this.terms = this.terms.filter(t => t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
equalVars(other) {
|
||||
|
|
@ -321,6 +337,7 @@ export class ToThePowerFunction {
|
|||
}
|
||||
|
||||
constructor(degree, fn, d1) {
|
||||
this.degree = degree;
|
||||
this.fn = fn;
|
||||
this.d1 = d1;
|
||||
this.id = '^' + degree;
|
||||
|
|
@ -358,10 +375,11 @@ export class FreeFunction {
|
|||
|
||||
fn;
|
||||
|
||||
constructor(fn, d1, id) {
|
||||
constructor(fn, d1, id, linearSubstitutionFn) {
|
||||
this.fn = fn;
|
||||
this.d1 = d1;
|
||||
this.id = id;
|
||||
this.linearSubstitutionFn = linearSubstitutionFn || null;
|
||||
}
|
||||
|
||||
apply(x) {
|
||||
|
|
@ -381,7 +399,7 @@ export class FreeFunction {
|
|||
}
|
||||
|
||||
linearSubstitution(k, x, b) {
|
||||
return null;
|
||||
return this.linearSubstitutionFn(k, x, b);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -415,7 +433,7 @@ export class CosineOfSum {
|
|||
}
|
||||
|
||||
render(x) {
|
||||
return 'cos(' + this.k.toFixed(2) + x + ' + ' + this.b.toFixed(2) + ')';
|
||||
return 'cos(' + this.k.toFixed(2) + '*' + x + ' + ' + this.b.toFixed(2) + ')';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -449,7 +467,7 @@ export class SineOfSum {
|
|||
}
|
||||
|
||||
render(x) {
|
||||
return 'sin(' + this.k.toFixed(2) + x + ' + ' + this.b.toFixed(2) + ')';
|
||||
return 'sin(' + this.k.toFixed(2) + '*' + x + ' + ' + this.b.toFixed(2) + ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ IO.prototype._serializeSketch = function(metadata) {
|
|||
}
|
||||
|
||||
sketch.constraints = [];
|
||||
const systemConstraints = this.viewer.parametricManager.algnNumSystem.constraints;
|
||||
const systemConstraints = this.viewer.parametricManager.algnNumSystem.allConstraints;
|
||||
for (let sc of systemConstraints) {
|
||||
if (!sc.internal) {
|
||||
sketch.constraints.push(sc.write());
|
||||
|
|
|
|||
|
|
@ -105,6 +105,10 @@ class ParametricManager {
|
|||
this.refresh();
|
||||
}
|
||||
|
||||
coincidePoints(pt1, pt2) {
|
||||
this.addAlgNum(new AlgNumConstraint(ConstraintDefinitions.PCoincident, [pt1, pt2]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ParametricManager.prototype.createConstantResolver = function() {
|
||||
|
|
|
|||
|
|
@ -47,18 +47,15 @@ export class Segment extends SketchObject {
|
|||
|
||||
let ang = Math.atan2(ny, nx);
|
||||
|
||||
if (this.ang !== undefined && Math.abs(ang - this.ang) > Math.PI) {
|
||||
ang = Math.atan2(-ny, -nx);
|
||||
}
|
||||
|
||||
this.params.ang.set(ang||0);
|
||||
this.params.w.set(nx * this.a.x + ny * this.a.y);
|
||||
this.params.t.set(l);
|
||||
}
|
||||
|
||||
stabilize(viewer) {
|
||||
this.syncGeometry();
|
||||
const c1 = new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [this.a, this]);
|
||||
const c2 = new AlgNumConstraint(ConstraintDefinitions.PointOnLine, [this.b, this]);
|
||||
const c2 = new AlgNumConstraint(ConstraintDefinitions.Polar, [this, this.a, this.b]);
|
||||
c1.internal = true;
|
||||
c2.internal = true;
|
||||
viewer.parametricManager.addAlgNum(c1);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {Generator} from '../id-generator'
|
||||
import {Shape} from './shape'
|
||||
import {Types} from '../io';
|
||||
import {Styles} from "../styles";
|
||||
|
||||
export class SketchObject extends Shape {
|
||||
constructor() {
|
||||
|
|
@ -11,6 +12,7 @@ export class SketchObject extends Shape {
|
|||
this.children = [];
|
||||
this.linked = [];
|
||||
this.layer = null;
|
||||
this.fullyConstrained = false;
|
||||
}
|
||||
|
||||
normalDistance(aim, scale) {
|
||||
|
|
@ -86,8 +88,12 @@ export class SketchObject extends Shape {
|
|||
ctx.save();
|
||||
viewer.setStyle(this.marked, ctx);
|
||||
}
|
||||
if (this.fullyConstrained) {
|
||||
ctx.save();
|
||||
viewer.setStyle(Styles.FULLY_CONSTRAINED, ctx);
|
||||
}
|
||||
this.drawImpl(ctx, scale, viewer);
|
||||
if (this.marked != null) ctx.restore();
|
||||
if (this.marked != null || this.fullyConstrained) ctx.restore();
|
||||
}
|
||||
|
||||
copy() {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ export const Styles = {
|
|||
fillStyle : "#000000"
|
||||
},
|
||||
|
||||
FULLY_CONSTRAINED: {
|
||||
lineWidth : 2,
|
||||
strokeStyle : "#d7e6ff",
|
||||
fillStyle : "#424e56"
|
||||
},
|
||||
|
||||
CONSTRUCTION : {
|
||||
lineWidth : 1,
|
||||
strokeStyle : "#aaaaaa",
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export class DragTool extends Tool {
|
|||
// this.lockedValues[i + 1] = this._point.y - this.lockedShifts[i + 1];
|
||||
// }
|
||||
this.obj.translate(dx, dy);
|
||||
|
||||
(this.obj.parent || this.obj).syncGeometry();
|
||||
if (!Tool.dumbMode(e)) {
|
||||
this.viewer.parametricManager.algnNumSystem.solve(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export class AddSegmentTool extends Tool {
|
|||
}
|
||||
|
||||
mousemove(e) {
|
||||
var p = this.viewer.screenToModel(e);
|
||||
let p = this.viewer.screenToModel(e);
|
||||
if (this.line != null) {
|
||||
this.viewer.snap(p.x, p.y, [this.line.a, this.line.b]);
|
||||
this.line.b.x = p.x;
|
||||
|
|
@ -35,7 +35,7 @@ export class AddSegmentTool extends Tool {
|
|||
if (this.line == null) {
|
||||
const b = this.viewer.screenToModel(e);
|
||||
let a = b;
|
||||
var needSnap = false;
|
||||
let needSnap = false;
|
||||
if (this.viewer.snapped != null) {
|
||||
a = this.viewer.snapped;
|
||||
this.viewer.cleanSnap();
|
||||
|
|
@ -43,7 +43,7 @@ export class AddSegmentTool extends Tool {
|
|||
}
|
||||
this.line = this.viewer.addSegment(a.x, a.y, b.x, b.y, this.viewer.activeLayer);
|
||||
if (needSnap) {
|
||||
this.line.a.coincideWith(a);
|
||||
this.viewer.parametricManager.coincidePoints(this.line.a, a);
|
||||
}
|
||||
this.firstPointPicked();
|
||||
this.viewer.refresh();
|
||||
|
|
@ -53,7 +53,7 @@ export class AddSegmentTool extends Tool {
|
|||
this.viewer.cleanSnap();
|
||||
this.line.b.x = p.x;
|
||||
this.line.b.y = p.y;
|
||||
this.line.b.coincideWith(p);
|
||||
this.viewer.parametricManager.coincidePoints(this.line.b, p);
|
||||
}
|
||||
this.nextPointPicked();
|
||||
}
|
||||
|
|
@ -61,10 +61,11 @@ export class AddSegmentTool extends Tool {
|
|||
|
||||
nextPointPicked() {
|
||||
this.pointPicked(this.line.b.x, this.line.b.y);
|
||||
this.line.stabilize(this.viewer);
|
||||
if (this.multi) {
|
||||
const b = this.line.b;
|
||||
this.line = this.viewer.addSegment(b.x, b.y, b.x, b.y, this.viewer.activeLayer);
|
||||
this.line.a.coincideWith(b);
|
||||
this.viewer.parametricManager.coincidePoints(this.line.a, b);
|
||||
} else {
|
||||
this.restart()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,6 @@ class Viewer {
|
|||
var b = new EndPoint(x2, y2);
|
||||
var line = new Segment(a, b);
|
||||
layer.add(line);
|
||||
line.stabilize(this);
|
||||
return line;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue