// Copyright 2016 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland // www.source-code.biz, www.inventec.ch/chdh // // License: GPL, GNU General Public License, http://www.gnu.org/licenses/gpl.html // Home page: http://www.source-code.biz/snippets/typescript/patterns /// /// /// /// /// // Application controller for the square grid pattern generator. namespace SquareGridPatternApplication { import BitSpace = Utils.BitSpace; import CurvePoint = FunctionCurveEditor.Point; import UnivariateNumericFunction = Utils.UnivariateNumericFunction; enum FieldType {scalarExponential, scalarCurve, vectorExponential, vectorCurve, vectorDir, enumCount}; function isFieldTypeScalar (fieldType: FieldType) { return fieldType == FieldType.scalarExponential || fieldType == FieldType.scalarCurve; } function isFieldTypeVector (fieldType: FieldType) { return !isFieldTypeScalar(fieldType); } function isFieldTypeExponential (fieldType: FieldType) { return fieldType == FieldType.scalarExponential || fieldType == FieldType.vectorExponential; } function isFieldTypeCurve (fieldType: FieldType) { return !isFieldTypeExponential(fieldType); } var defaultAreaWidth: number; var defaultAreaHeight: number; const defaultStepWidth = 3; const defaultFieldType = FieldType.scalarExponential; const defaultInfluenceRange = 25; const defaultTargetFunction = "Math.abs(x - 5)"; const defaultAggregateFunction = "f1 + f2"; const defaultExponent = -1; const initialCurveKnots: CurvePoint[] = [ new CurvePoint( -1, 1 ), new CurvePoint(-0.5, 1 ), new CurvePoint( 0, 1 ), new CurvePoint( 1, 1 ), new CurvePoint( 3, 0.5 ), new CurvePoint( 6, 0.1 ), new CurvePoint( 25, 0 ), new CurvePoint( 40, 0 ), new CurvePoint( 60, 0 ) ]; const initialDirCurveKnots: CurvePoint[] = [ new CurvePoint(-Math.PI, -1 ), new CurvePoint( -1, 0 ), new CurvePoint( -0.35, 0.8), new CurvePoint( 0, 1 ), new CurvePoint( 0.35, 0.8), new CurvePoint( 1, 0 ), new CurvePoint( Math.PI, -1 ) ]; class FieldBlock { position: number; // relative position of this field block, first = 0 $fieldBlock: JQuery; $fieldBlockId: JQuery; $fieldTypeControl: JQuery; influenceRangeControl: InputControls.IntegerInputControl; $squareRangeControl: JQuery; $targetFunctionControl: JQuery; exponentControl: InputControls.NumberInputControl | null; foldingFactorControl: InputControls.IntegerInputControl | null; // only used for vector field types curveEditor: FunctionCurveEditor.Widget | null; // only used for curve field types dirCurveEditor: FunctionCurveEditor.Widget | null; // only used for field type vectorDir $fieldInfo: JQuery; // getFieldType() : FieldType { return Number(this.$fieldTypeControl.val()); }} interface FieldBlockInitParms { fieldType: FieldType | null; influenceRange: number | null; curveEditorInitParms: CurveEditorInitParms | null; // only used for the following fields: knots, linearInterpolation dirCurveEditorInitParms: CurveEditorInitParms | null; } // only used for the following fields: knots, linearInterpolation interface CurveEditorInitParms { xMin: number; xMax: number; relevantXMin: number; relevantXMax: number; yMin: number; yMax: number; knots: CurvePoint[]; linearInterpolation: boolean; } interface SavedAppState { areaWidth?: number; areaHeight?: number; stepWidth?: number; aggregateFunction?: string; randomPoints?: number; fields?: SavedFieldBlockState[]; } interface SavedFieldBlockState { fieldType?: string; // string with field type name influenceRange?: number; squareRange?: boolean; targetFunction?: string; exponent?: number; foldingFactor?: number; curve?: SavedCurveState; dirCurve?: SavedCurveState; } interface SavedCurveState { linearInterpolation?: boolean; knots?: number[]; } // x/y pairs //------------------------------------------------------------------------------ class Application { generator: SquareGridPatternGenerator.PatternGenerator | null; autoStart: boolean; // GUI elements and controls: $appContainer: JQuery; canvas: HTMLCanvasElement; areaWidthControl: InputControls.IntegerInputControl; areaHeightControl: InputControls.IntegerInputControl; stepWidthControl: InputControls.IntegerInputControl; $aggregateFunctionLabel: JQuery; $aggregateFunctionControl: JQuery; randomPointsControl: InputControls.NumberInputControl; $fieldBlocks: JQuery; fieldBlocks: FieldBlock[]; constructor (appContainer: HTMLDivElement) { this.$appContainer = $(appContainer); this.init(); this.restoreAppStateFromUrl(); if (this.autoStart) { this.start(); }} init() { this.canvas = this.$appContainer.find(".sgpCanvas")[0]; defaultAreaWidth = this.canvas.width; defaultAreaHeight = this.canvas.height; this.areaWidthControl = new InputControls.IntegerInputControl(this.$appContainer.find(".sgpAreaWidthControl"), defaultAreaWidth); this.areaWidthControl.onAfterUpdate = () => {this.resizeCanvas(); }; this.areaHeightControl = new InputControls.IntegerInputControl(this.$appContainer.find(".sgpAreaHeightControl"), defaultAreaHeight); this.areaHeightControl.onAfterUpdate = () => {this.resizeCanvas(); }; this.stepWidthControl = new InputControls.IntegerInputControl(this.$appContainer.find(".sgpStepWidthControl"), defaultStepWidth); this.stepWidthControl.onAfterUpdate = () => {this.reset(); }; this.$aggregateFunctionLabel = this.$appContainer.find(".sgpAggregateFunctionLabel"); this.$aggregateFunctionControl = this.$appContainer.find(".sgpAggregateFunctionControl"); this.$aggregateFunctionControl.val(defaultAggregateFunction); this.$aggregateFunctionControl.change(() => {this.reset(); }); this.randomPointsControl = new InputControls.NumberInputControl(this.$appContainer.find(".sgpRandomPointsControl"), 0); this.randomPointsControl.onAfterUpdate = () => {this.reset(); }; this.$fieldBlocks = this.$appContainer.find(".sgpFieldBlocks"); this.fieldBlocks = []; this.$appContainer.find(".sgpStartButton").click(() => {this.start(); }); this.$appContainer.find(".sgpStopButton").click(() => {this.stop(); }); this.$appContainer.find(".sgpClearButton").click(() => {this.clear(); }); this.$appContainer.find(".sgpSnapshotButton").click(() => {this.snapshot(); }); window.onpopstate = () => { this.restoreAppStateFromUrl(); }; } addFieldBlock (initParms?: FieldBlockInitParms, fieldBlockPos: number = this.fieldBlocks.length) : FieldBlock { let fieldBlock = new FieldBlock(); let $fieldBlock = $( '
' + '
' + '
' + '
' + '
-
' + '
+
' + '
' + '
' + '
' + 'Field type: ' + '
' + '
' + // contains the elements that depend on the field type '
' + '
'); fieldBlock.$fieldBlock = $fieldBlock; if (fieldBlockPos >= this.fieldBlocks.length) { this.$fieldBlocks.append($fieldBlock); } else { this.fieldBlocks[fieldBlockPos].$fieldBlock.before($fieldBlock); } fieldBlock.$fieldBlockId = $fieldBlock.find(".sgpFieldBlockId"); fieldBlock.$fieldTypeControl = $fieldBlock.find(".sgpFieldTypeControl"); let fieldType: FieldType = (initParms != null && initParms.fieldType != null) ? initParms.fieldType : defaultFieldType; Utils.loadSelectElementOptionsEnum(fieldBlock.$fieldTypeControl, FieldType, fieldType); fieldBlock.$fieldTypeControl.change(() => {this.buildFieldParmsBlock(fieldBlock); this.onFieldBlockUpdate(fieldBlock); }); let $delFieldBlockButton = $fieldBlock.find(".sgpDelFieldBlockButton"); $delFieldBlockButton.click(() => {this.reset(); this.deleteFieldBlock(fieldBlock.position); }); let $addFieldBlockButton = $fieldBlock.find(".sgpAddFieldBlockButton"); $addFieldBlockButton.click(() => {this.reset(); this.addFieldBlock(undefined, fieldBlock.position + 1); }); this.buildFieldParmsBlock(fieldBlock, initParms); this.updateFieldInfo(fieldBlock); this.fieldBlocks.splice(fieldBlockPos, 0, fieldBlock); this.renumberFieldBlocks(); return fieldBlock; } deleteFieldBlocks() { this.fieldBlocks = []; this.$fieldBlocks.html(""); } deleteFieldBlock (fieldBlockPos: number) { let fieldBlock = this.fieldBlocks[fieldBlockPos]; fieldBlock.$fieldBlock.remove(); this.fieldBlocks.splice(fieldBlockPos, 1); this.renumberFieldBlocks(); if (this.fieldBlocks.length == 0) { this.addFieldBlock(); }} renumberFieldBlocks() { let n = this.fieldBlocks.length; for (let i = 0; i < n; i++) { let fieldBlock = this.fieldBlocks[i]; fieldBlock.position = i; fieldBlock.$fieldBlockId.html((i + 1).toString()); } // Side effect: this.$aggregateFunctionLabel.toggle(n > 1); } buildFieldParmsBlock (fieldBlock: FieldBlock, initParms?: FieldBlockInitParms) { let fieldType = fieldBlock.getFieldType(); let $parmsBlock = fieldBlock.$fieldBlock.find(".sgpFieldParmsBlock"); $parmsBlock.html( '
' + '' + '' + (!isFieldTypeVector(fieldType) ? '' : '') + '' + '' + '' + '
' + (!isFieldTypeCurve(fieldType) ? '' : this.buildCurveEditorHtml("sgpCurveEditor", 900, 300, true)) + (fieldType != FieldType.vectorDir ? '' : this.buildCurveEditorHtml("sgpDirCurveEditor", 900, 200))); let influenceRange: number = (initParms != null && initParms.influenceRange > 0) ? initParms.influenceRange! : defaultInfluenceRange; fieldBlock.influenceRangeControl = new InputControls.IntegerInputControl($parmsBlock.find(".sgpInfluenceRangeControl "), influenceRange); fieldBlock.influenceRangeControl.onAfterUpdate = () => {this.onFieldBlockUpdate(fieldBlock, true); }; fieldBlock.$squareRangeControl = $parmsBlock.find(".sgpSquareRangeControl"); fieldBlock.$squareRangeControl.change(() => {this.onFieldBlockUpdate(fieldBlock); }); fieldBlock.$targetFunctionControl = $parmsBlock.find(".sgpTargetFunctionControl"); fieldBlock.$targetFunctionControl.val(defaultTargetFunction); fieldBlock.$targetFunctionControl.change(() => {this.onFieldBlockUpdate(fieldBlock); }); fieldBlock.exponentControl = new InputControls.NumberInputControl($parmsBlock.find(".sgpExponentControl"), defaultExponent); fieldBlock.exponentControl.onAfterUpdate = () => {this.onFieldBlockUpdate(fieldBlock); }; fieldBlock.$fieldInfo = $parmsBlock.find(".sgpFieldInfo"); if (isFieldTypeVector(fieldType)) { fieldBlock.foldingFactorControl = new InputControls.IntegerInputControl($parmsBlock.find(".sgpFoldingFactorControl"), 1); fieldBlock.foldingFactorControl.onAfterUpdate = () => {this.onFieldBlockUpdate(fieldBlock); }; } else { fieldBlock.foldingFactorControl = null; } if (isFieldTypeCurve(fieldType)) { let canvas: HTMLCanvasElement = $parmsBlock.find(".sgpCurveEditor")[0]; let c0 = (initParms != null) ? initParms.curveEditorInitParms : null; let c = {}; c.xMin = -0.5; c.xMax = Math.max(25, influenceRange) + 0.5; c.relevantXMin = 1; c.relevantXMax = influenceRange; c.yMin = -1.25; c.yMax = 1.25; c.knots = (c0 != null && c0.knots != null) ? c0.knots : initialCurveKnots; c.linearInterpolation = (c0 != null && c0.linearInterpolation != null) ? c0.linearInterpolation : false; fieldBlock.curveEditor = this.buildCurveEditor(canvas, c, fieldBlock); $parmsBlock.find(".sgpCurveEditorHelpButton").click(() => {this.toggleCurveEditorHelp(fieldBlock); }); } else { fieldBlock.curveEditor = null; } if (fieldType == FieldType.vectorDir) { let canvas: HTMLCanvasElement = $parmsBlock.find(".sgpDirCurveEditor")[0]; let c0 = (initParms != null) ? initParms.dirCurveEditorInitParms : null; let c = {}; c.xMin = -3.5; c.xMax = 3.5; c.relevantXMin = -Math.PI; c.relevantXMax = Math.PI; c.yMin = -1.25; c.yMax = 1.25; c.knots = (c0 != null && c0.knots != null) ? c0.knots : initialDirCurveKnots; c.linearInterpolation = (c0 != null && c0.linearInterpolation != null) ? c0.linearInterpolation : false; fieldBlock.dirCurveEditor = this.buildCurveEditor(canvas, c, fieldBlock); } else { fieldBlock.dirCurveEditor = null; }} buildCurveEditorHtml (id: string, width: number = 900, height: number = 300, includeHelp: boolean = false) : string { let s = '' + 'Error: Your browser does not support the HTML canvas element.' + ''; if (includeHelp) { s = s + '
help
' + '
'; } return s; } onFieldBlockUpdate (fieldBlock: FieldBlock, updateCurveEditorParms: boolean = false) { this.reset(); this.updateFieldInfo(fieldBlock); if (updateCurveEditorParms) { this.updateCurveEditorParms(fieldBlock); }} updateFieldInfo (fieldBlock: FieldBlock) { fieldBlock.$fieldInfo.html(this.genFieldInfo(fieldBlock)); } updateCurveEditorParms (fieldBlock: FieldBlock) { if (fieldBlock.curveEditor != null) { let influenceRange = fieldBlock.influenceRangeControl.getValue(); let cnf = fieldBlock.curveEditor.getConfiguration(); if (cnf.relevantXMax != influenceRange) { cnf.relevantXMax = influenceRange; fieldBlock.curveEditor.setConfiguration(cnf); }}} toggleCurveEditorHelp (fieldBlock: FieldBlock) { let t = fieldBlock.$fieldBlock.find(".sgpCurveEditorHelpText"); t.toggle(); if (!t.is(":hidden")) { t.html(fieldBlock.curveEditor!.getFormattedHelpText()); }} buildCurveEditor (canvas: HTMLCanvasElement, initParms: CurveEditorInitParms, fieldBlock: FieldBlock) : FunctionCurveEditor.Widget { let cnf = new FunctionCurveEditor.Configuration(); cnf.knots = initParms.knots; cnf.planeOrigin = new CurvePoint(initParms.xMin, initParms.yMin); cnf.zoomFactorX = canvas.width / (initParms.xMax - initParms.xMin); cnf.zoomFactorY = canvas.height / (initParms.yMax - initParms.yMin); cnf.extendedDomain = true; cnf.relevantXMin = initParms.relevantXMin; cnf.relevantXMax = initParms.relevantXMax; cnf.gridEnabled = true; cnf.snapToGridEnabled = true; cnf.linearInterpolation = initParms.linearInterpolation; let editor = new FunctionCurveEditor.Widget(canvas, cnf); editor.setOnAfterUpdate(() => {this.onFieldBlockUpdate(fieldBlock); }); return editor; } resizeCanvas() { this.reset(); this.canvas.width = this.areaWidthControl.getValue(); this.canvas.height = this.areaHeightControl.getValue(); } reset() { this.stop(); this.generator = null; } stop() { if (this.generator != null) { this.generator.stop(); }} clear() { this.canvas.width = 1; this.resizeCanvas(); } snapshot() { if (this.generator == null) { return; } this.generator.stop(); let data = this.canvas.toDataURL("image/png"); window.open(data); } start() { try { this.start2() } catch (e) { alert("Error: " + e); throw e; }} start2() { if (this.generator == null) { this.saveAppStateInUrl(); this.generator = this.createPatternGenerator(); let randomPoints = this.randomPointsControl.getValue(); if (randomPoints > 0) { let area = this.areaWidthControl.getValue() * this.areaHeightControl.getValue(); let n = Math.min(area, Math.round(area * randomPoints / 100)); this.generator.setRandomPoints(n); }} this.generator.start(); } createPatternGenerator() : SquareGridPatternGenerator.PatternGenerator { let width = this.areaWidthControl.getValue(); let height = this.areaHeightControl.getValue(); let bitSpace = new BitSpace(width, height); let finderConfig = this.genFinderConfig(); let finder = new SquareGridPatternFinder1.Finder(bitSpace, finderConfig); return new SquareGridPatternGenerator.PatternGenerator(bitSpace, finder, this.canvas); } genFinderConfig() : SquareGridPatternFinder1.FinderConfig { let cnf = new SquareGridPatternFinder1.FinderConfig(); cnf.stepWidth = this.stepWidthControl.getValue(); let n = this.fieldBlocks.length; cnf.fieldConfigs = Array(n); for (let i = 0; i < n; i++) { cnf.fieldConfigs[i] = this.createFinderFieldConfig(this.fieldBlocks[i]); } if (n > 1) { let parms = ""; for (let i = 0; i < n; i++) { parms += (i > 0 ? "," : "") + "f" + (i + 1); } let aggregateFunctionText = this.$aggregateFunctionControl.val(); cnf.aggregateFunction = new Function(parms, "return " + aggregateFunctionText + ";"); } return cnf; } createFinderFieldConfig (fieldBlock: FieldBlock) { let cnf = new SquareGridPatternFinder1.FieldConfig(); let fieldType = fieldBlock.getFieldType(); cnf.influenceRange = fieldBlock.influenceRangeControl.getValue(); cnf.squareRange = fieldBlock.$squareRangeControl.prop("checked"); cnf.fieldFunction = this.genFieldFunction(fieldBlock); cnf.fieldType = (fieldType == FieldType.vectorDir) ? SquareGridPatternFinder1.FieldType.vectorDir : isFieldTypeScalar(fieldType) ? SquareGridPatternFinder1.FieldType.scalar : SquareGridPatternFinder1.FieldType.vector; cnf.foldingFactor = isFieldTypeVector(fieldType) ? fieldBlock.foldingFactorControl!.getValue(): null; cnf.dirFunction = (fieldType == FieldType.vectorDir) ? fieldBlock.dirCurveEditor!.getFunction() : null; let targetFunctionText = fieldBlock.$targetFunctionControl.val(); cnf.targetFunction = new Function("x", "return " + targetFunctionText + ";"); return cnf; } genFieldInfo (fieldBlock: FieldBlock) { let fieldType = fieldBlock.getFieldType(); let influenceRange = fieldBlock.influenceRangeControl.getValue(); let squareRange = fieldBlock.$squareRangeControl.prop("checked"); let foldingFactor = isFieldTypeVector(fieldType) ? fieldBlock.foldingFactorControl!.getValue(): null; let fieldFunction = this.genFieldFunction(fieldBlock); if (isFieldTypeScalar(fieldType)) { return SquareGridPatternFinder1.genScalarFieldInfo(fieldFunction, influenceRange, squareRange); } if (isFieldTypeVector(fieldType)) { return SquareGridPatternFinder1.genVectorFieldInfo(fieldFunction, influenceRange, squareRange, foldingFactor!); } return ""; } genFieldFunction (fieldBlock: FieldBlock) : UnivariateNumericFunction { let fieldType = fieldBlock.getFieldType(); if (isFieldTypeExponential(fieldType)) { return this.genExponentialFieldFunction(fieldBlock); } else { return this.genCurveFieldFunction(fieldBlock); }} genCurveFieldFunction (fieldBlock: FieldBlock) : UnivariateNumericFunction { let exponent = fieldBlock.exponentControl!.getValue(); let curveFunction = fieldBlock.curveEditor!.getFunction(); if (exponent == 0) { return curveFunction; } return function (x: number) : number { if (x == 0) { x = 1E-6; } return curveFunction(x) * Math.pow(x, exponent); }; } genExponentialFieldFunction (fieldBlock: FieldBlock) : UnivariateNumericFunction { let exponent = fieldBlock.exponentControl!.getValue(); return function (x: number) : number { if (x == 0) { x = 1E-6; } return Math.pow(x, exponent); }; } //--- Save/restore state ---------------------------------------------------- saveAppStateInUrl() { let oldAppState = this.parseLocationHash().appState; let newAppState = this.encodeAppState(); if (newAppState == oldAppState) { return; } history.pushState(null, "", "#" + newAppState); } encodeAppState() { let d = this.encodePatternParms(); let json = JSON.stringify(d); return encodeURIComponent(json); } // Extracts the essential pattern parameters in a format suitable for long-time storage in URLs. encodePatternParms() : SavedAppState { let d = {}; d.areaWidth = this.areaWidthControl.getValue(); d.areaHeight = this.areaHeightControl.getValue(); d.stepWidth = this.stepWidthControl.getValue(); if (this.fieldBlocks.length > 1) { d.aggregateFunction = this.$aggregateFunctionControl.val(); } let randomPoints = this.randomPointsControl.getValue(); if (randomPoints != 0) { d.randomPoints = randomPoints; } d.fields = Array(this.fieldBlocks.length); for (let i = 0; i < this.fieldBlocks.length; i++) { d.fields[i] = this.encodeFieldBlockParms(this.fieldBlocks[i]); } return d; } encodeFieldBlockParms (fieldBlock: FieldBlock) : SavedFieldBlockState { let fieldType: FieldType = fieldBlock.getFieldType(); let d = {}; d.fieldType = FieldType[fieldType]; // string with field type name d.influenceRange = fieldBlock.influenceRangeControl.getValue(); if (fieldBlock.$squareRangeControl.prop("checked")) { d.squareRange = true; } d.targetFunction = fieldBlock.$targetFunctionControl.val(); d.exponent = fieldBlock.exponentControl!.getValue(); if (isFieldTypeVector(fieldType)) { let foldingFactor = fieldBlock.foldingFactorControl!.getValue(); if (foldingFactor != 1) { d.foldingFactor = foldingFactor; }} if (isFieldTypeCurve(fieldType)) { d.curve = this.encodeCurveParms(fieldBlock.curveEditor!); } if (fieldType == FieldType.vectorDir) { d.dirCurve = this.encodeCurveParms(fieldBlock.dirCurveEditor!); } return d; } encodeCurveParms (curveEditor: FunctionCurveEditor.Widget) : SavedCurveState { let cnf = curveEditor.getConfiguration(); let d = {}; if (cnf.linearInterpolation) { d.linearInterpolation = true; } d.knots = Array(cnf.knots.length * 2); for (let i = 0; i < cnf.knots.length; i++) { d.knots[2 * i] = cnf.knots[i].x; d.knots[2 * i + 1] = cnf.knots[i].y; } return d; } restoreAppStateFromUrl() { this.reset(); let r = this.parseLocationHash(); this.autoStart = r.autoStart; if (r.appState != null) { this.restoreAppStateWithErrorHandling(r.appState); } else { this.resetAppState(); }} parseLocationHash() { let s: string | null = location.hash; s = (s != null && s.length > 1 && s.charAt(0) == "#") ? s.substring(1) : null; let autoStart: boolean = false; if (s != null && s.length > 1 && s.charAt(0) == "S") { autoStart = true; s = s.substring(1); } let appState = (s != null && s.length > 1) ? s : null; return {appState: appState, autoStart: autoStart}; } resetAppState() { this.areaWidthControl.setValue(defaultAreaWidth); this.areaHeightControl.setValue(defaultAreaHeight); this.resizeCanvas(); this.stepWidthControl.setValue(defaultStepWidth); this.randomPointsControl.setValue(0); this.deleteFieldBlocks(); this.addFieldBlock(); } restoreAppStateWithErrorHandling (appState: string) { try { this.restoreAppState(appState); } catch (e) { alert("Unable to restore application state from saved state. " + e); console.log(e); this.resetAppState(); return; }} restoreAppState (appState: string) { let json = decodeURIComponent(appState); let d: SavedAppState = JSON.parse(json); this.loadPatternParms(d); } loadPatternParms (d: SavedAppState) { this.areaWidthControl.setValue(d.areaWidth || defaultAreaWidth); this.areaHeightControl.setValue(d.areaHeight || defaultAreaHeight); this.resizeCanvas(); this.stepWidthControl.setValue(d.stepWidth || defaultStepWidth); if (d.fields != null && d.fields.length > 1) { this.$aggregateFunctionControl.val(d.aggregateFunction || defaultAggregateFunction); } else { this.$aggregateFunctionControl.val(defaultAggregateFunction); } this.randomPointsControl.setValue(d.randomPoints || 0); this.deleteFieldBlocks(); if (d.fields != null) { for (let i = 0; i < d.fields.length; i++) { this.loadFieldBlock(d.fields[i]); }} else { this.addFieldBlock(); }} loadFieldBlock (d: SavedFieldBlockState) { let initParms = {}; let fieldType = (d.fieldType != null) ? Utils.decodeEnum(FieldType, d.fieldType) : null; if (fieldType == null) { throw new Error("Invalid field type \"" + d.fieldType + "\"."); } initParms.fieldType = fieldType; initParms.influenceRange = d.influenceRange ? d.influenceRange : defaultInfluenceRange; if (isFieldTypeCurve(fieldType) && d.curve != null) { initParms.curveEditorInitParms = this.decodeCurveParms(d.curve); } if (fieldType == FieldType.vectorDir && d.dirCurve != null) { initParms.dirCurveEditorInitParms = this.decodeCurveParms(d.dirCurve); } let fieldBlock: FieldBlock = this.addFieldBlock(initParms); if (d.squareRange) { fieldBlock.$squareRangeControl.prop("checked", true); } if (d.targetFunction != null) { fieldBlock.$targetFunctionControl.val(d.targetFunction); } if (d.exponent != null) { fieldBlock.exponentControl!.setValue(d.exponent); } if (isFieldTypeVector(fieldType) && d.foldingFactor) { fieldBlock.foldingFactorControl!.setValue(d.foldingFactor); } this.updateFieldInfo(fieldBlock); } decodeCurveParms (d: SavedCurveState) : CurveEditorInitParms { let initParms = {}; if (d.linearInterpolation) { initParms.linearInterpolation = true; } if (d.knots != null) { let n = Math.floor(d.knots.length / 2); initParms.knots = Array(n); for (let i = 0; i < n; i++) { initParms.knots[i] = new CurvePoint(d.knots[2 * i], d.knots[2 * i + 1]); }} return initParms; }} //------------------------------------------------------------------------------ function startup() { let appContainer: HTMLDivElement = $("#sgpApplicationContainer")[0]; new Application(appContainer); } $(function() { startup(); }); } // end namespace SquareGridPatternApplication