트리 이미지 생성, 세 번째 부분.분형에서 진짜 나무로
44900 단어 typescriptoopdddarchitecture
두 번째 부분에서 우리는 응용 프로그램 구조를 설계하고 환경과 의존 주입을 설정했다.마지막으로 트리의 문자열 표현을 생성할 수 있는 L-Systems 모듈을 만들었습니다.
에서 캔버스 요소에 액세스하기 위해 형상 모듈과 DOM 어댑터를 생성했습니다.마지막으로, 우리는 화면에 첫 번째 그림을 보여 주었다.
마지막 글에서 우리는 L 시스템 역할에 대한 번역기를 만들 것이다.그 밖에 우리는 피타고라스 나무를 생성하고 랜덤성을 첨가하여 진정한 나무처럼 보일 것이다.
마지막으로 다음과 같이 트리 이미지를 생성하는 응용 프로그램을 만듭니다.
제작 번역
해석기 모듈은 L 시스템의 문자열 표현을 받아들여 하나의 그림 명령으로 번역합니다.API를 설계하고 종속성을 정의합니다.
이 모듈은 기하학적 모듈에 의존하고
SystemInterpreter 인터페이스를 제공한다.// src/interpreter/types.ts
export interface SystemInterpreter {
translate(expression: Expression): List<Line>;
}
이 인터페이스를 실현해 봅시다.// src/interpreter/implementation.ts
import { AppSettings } from "../settings";
import { StartSelector } from "../geometry/location";
import { ShapeBuilder } from "../geometry/shape";
import { Stack } from "./stack/types";
import { SystemInterpreter } from "./types";
export class SystemToGeometryMapper implements SystemInterpreter {
// We will change these fields while translating commands:
private currentLocation: Point = { x: 0, y: 0 };
private currentAngle: DegreesAmount = 0;
// These fields keep characters of an initial expression
// and a list of corresponding commands:
private systemTokens: List<Character> = [];
private drawInstructions: List<Line> = [];
// Define dependencies:
constructor(
private shapeBuilder: ShapeBuilder,
private startSelector: StartSelector,
private stack: Stack<TreeJoint>,
private settings: AppSettings,
) {}
// Implement public methods:
public translate(expression: Expression): List<Line> {
this.currentLocation = { ...this.startSelector.selectStart() };
this.systemTokens = expression.split("");
this.systemTokens.forEach(this.translateToken);
return this.drawInstructions;
}
// …
}
translate 방법에서는 L 시스템 표현식을 사용하여 단일 문자로 분할했습니다.그리고 우리는 translateToken 방법으로 모든 문자를 처리하고, 잠시 후에 이 방법을 작성할 것이다.따라서 번역된 명령이 모두 포함된
drawInstructions 목록을 되돌려줍니다.You may notice the
Stack<TreeJoint>in the dependencies. This is literal stack structure implementation. You can find its code on GitHub.
그리고 private
translateToken 방법을 만듭니다.// src/interpreter/implementation.ts
export class SystemToGeometryMapper implements SystemInterpreter {
// …
private translateToken = (token: Character): void => {
switch (token) {
// If the character is 0 or 1
// we draw a line from the current position
// with a current angle:
case "0":
case "1": {
const line = this.shapeBuilder.createLine(
this.currentLocation,
this.settings.stemLength,
this.currentAngle,
);
this.drawInstructions.push(line);
this.currentLocation = { ...line.end };
break;
}
// If the character is an opening bracket we turn left
// and push the current position and angle in the stack:
case "[": {
this.currentAngle -= this.settings.jointAngle;
this.stack.push({
location: { ...this.currentLocation },
rotation: this.currentAngle,
stemWidth: this.settings.stemLength,
});
break;
}
// If the character is the closing bracket
// we pop the last position and the angle from the stack
// and turn right from there:
case "]": {
const lastJoint = this.stack.pop();
this.currentLocation = { ...lastJoint.location };
this.currentAngle = lastJoint.rotation + 2 * this.settings.jointAngle;
break;
}
}
};
}
이제 영패에 이 방법을 사용할 때, 그것은 새로운 선을 그리거나, 어느 쪽으로 돌아갈지 결정한다.창고는 우리가 마지막 지점으로 돌아가는 것을 돕는다.이제 설정을 업데이트하고 이 메서드를 실행하여 다음 사항을 살펴보겠습니다.
// src/settings/index.ts
export const settings: AppSettings = {
canvasSize: {
width: 800,
height: 600,
},
// Using 5 iterations
// with Pythagoras tree rules:
iterations: 5,
initiator: "0",
rules: {
"1": "11",
"0": "1[0]0",
},
// Stem length is 10 pixels;
// turn 45 degrees each time:
stemLength: 10,
jointAngle: 45,
};
애플리케이션 포털 업데이트:// src/index.ts
const builder = container.get<SystemBuilder>();
const drawer = container.get<Drawer>();
const interpreter = container.get<SystemInterpreter>();
const settings = container.get<AppSettings>();
const system = builder.build(settings);
const lines = interpreter.translate(system);
lines.forEach((line) => drawer.drawLine(line));
이러한 설정을 통해 우리는 규범화된 피타고라스 나무를 얻었다.
우리는 각도 게임을 해서 우리가 어떤 데이터를 얻을 수 있는지 볼 수 있다😃
90도에서 우리는 안테나를 얻었다.
15도의 온도에서 우리는 풀 한 조각을 얻었다.
화씨 115도, 우리는 얻는다...음...
쿨!우리는 이미 이 나무의 기본 지식을 가지고 있다.하지만 우리가 그것을 더욱 진실하게 만들기 전에 입구점을 정리해야 한다.
입구점 정리
지금 입구가 좀 더러워요.
// src/index.ts
const builder = container.get<SystemBuilder>();
const drawer = container.get<Drawer>();
const interpreter = container.get<SystemInterpreter>();
const settings = container.get<AppSettings>();
const system = builder.build(settings);
const lines = interpreter.translate(system);
lines.forEach((line) => drawer.drawLine(line));
우리는 용기에서 너무 많은 서비스를 받아서 모든 조작을 수동으로 초기화했다.응용 프로그램 시작을 담당하는 개별 객체에 이러한 모든 것을 숨깁니다.// src/app/types.ts
export interface Application {
start(): void;
}
이제 모든 코드를 start 메서드 뒤에 숨깁니다.// src/app/implementation.ts
export class App implements Application {
constructor(
private builder: SystemBuilder,
private drawer: Drawer,
private interpreter: SystemInterpreter,
private settings: AppSettings,
) {}
start(): void {
const system = this.builder.build(this.settings);
const lines = this.interpreter.translate(system);
lines.forEach((line) => this.drawer.drawLine(line));
}
}
...등록:// src/app/composition.ts
import { container } from "../composition";
import { App } from "./implementation";
import { Application } from "./types";
container.registerSingleton<Application, App>();
이제 입구가 더 깨끗해졌어요.// src/index.ts
import { container } from "./composition";
import { Application } from "./app/types";
const app = container.get<Application>();
app.start();
나무를 더 진실하게
지금 우리의 나무는 너무 엄격하고 수학적이다.보다 사실적으로 보이기 위해서는 다음과 같은 몇 가지 임의성과 역동성을 추가해야 합니다.
"2" 를 추가했다.현재 나무 모양의 분지는 매번 교체될 때마다 두 배로 줄어들고 새로운 상수는 이 과정을 늦춘다.우리는 또 공리를 좀 더 길게 해서 나무 줄기를 더욱 길게 할 것이다.마지막으로 우리는 교체 횟수를 12회로 늘릴 것이다.
// src/settings/index.ts
export const settings: AppSettings = {
// …
iterations: 12,
initiator: "22220",
rules: {
"1": "21",
"0": "1[20]20",
},
leafWidth: 4,
stemWidth: 16,
// …
};
이제 해석기 코드를 변경하겠습니다.export class SystemToGeometryMapper implements SystemInterpreter {
private currentLocation: Point = { x: 0, y: 0 };
private currentAngle: DegreesAmount = 0;
// We will also change the stem width:
private currentWidth: PixelsAmount = 0;
private systemTokens: List<Character> = [];
private drawInstructions: List<Instruction> = [];
constructor(
private shapeBuilder: ShapeBuilder,
private startSelector: StartSelector,
private stack: Stack<TreeJoint>,
private settings: AppSettings,
// Here, we're going to need a random source.
// In our case, it is a wrapper over `Math.random`
// with a bit more convenient API.
// You can find its source on GitHub as well.
private random: RandomSource,
) {}
// …
}
그리고 잎사귀 (("0" 문자) 를 처리하면 녹색을 무작위로 선택합니다.private translateToken = (token: Character): void => {
switch (token) {
case "0": {
const line = this.createLine();
this.currentLocation = { ...line.end };
this.drawInstructions.push({
line,
color: this.selectLeafColor(), // Adding the leaf color
width: this.settings.leafWidth, // and width.
});
break;
}
// …
}
}
그리고 나서 우리는 때때로 새로운 지점을 뛰어넘는다.이로 인해 브랜치 위치가 더욱 혼란스러워집니다.private translateToken = (token: Character): void => {
switch (token) {
// …
case "1":
case "2": {
// Draw a new branch only in 60% of cases:
if (this.shouldSkip()) return;
const line = this.createLine();
this.drawInstructions.push({ line, width: this.currentWidth });
this.currentLocation = { ...line.end };
break;
}
// …
}
};
회전 시 각도 값에 임의 편차를 추가합니다.private translateToken = (token: Character): void => {
switch (token) {
// …
case "[": {
// Making the width smaller:
this.currentWidth *= 0.75;
// Adding a random angle deviation:
this.currentAngle -=
this.settings.jointAngle + this.randomAngleDeviation();
// Remember the branching position,
// angle, and current branch width:
this.stack.push({
location: { ...this.currentLocation },
rotation: this.currentAngle,
stemWidth: this.currentWidth,
});
break;
}
case "]": {
// Getting the last branching position:
const lastJoint = this.stack.pop();
// Using its position, angle, and width as current:
this.currentWidth = lastJoint.stemWidth;
this.currentLocation = { ...lastJoint.location };
this.currentAngle =
lastJoint.rotation +
2 * this.settings.jointAngle +
this.randomAngleDeviation();
break;
}
}
};
또한 부족한 모든 사유 방법도 추가했습니다.export class SystemToGeometryMapper implements SystemInterpreter {
// …
private createLine = (): Line => {
return this.shapeBuilder.createLine(
this.currentLocation,
this.settings.stemLength,
this.currentAngle,
);
};
// Draw branches only 60% of the time:
private shouldSkip = (): boolean => {
return this.random.getValue() > 0.4;
};
// Random deviation will be from -5 to 5 degrees:
private randomAngleDeviation = (): Angle => {
return this.random.getBetweenInclusive(-5, 5);
};
// Green color will be chosen among 3 different colors:
private selectLeafColor = (): Color => {
const randomColor = this.random.getBetweenInclusive(0, 2);
return leafColors[randomColor];
};
}
마지막으로 애플리케이션을 실행하고 결과를 살펴보겠습니다.
우리는 진정한 나무 한 그루가 있다!🌳
변화는 현지의 것이다
중요한 것은 최근에 변경한 사항이
Interpreter 모듈의 제한을 받았다는 것이다.비록 이미지에 커다란 변화가 생겼지만, 우리는 해석기의 실현만 바꾸었다.다른 모든 모듈은 그대로 유지됩니다.더 중요한 것은 인터페이스도 마찬가지다.우리는
SystemInterpreter와 ShapeBuilder를 바꿀 필요가 없다.
우리는 심지어 완전히 바꾸어 실현할 수 있다!인터페이스가 같기만 하면 프로그램은 아무런 추가 변경도 필요 없이 작업을 할 수 있다.
결실
이제 전체 시스템을 살펴보겠습니다.
모듈은 인터페이스를 통해 통신한다.
그것은 테스트에 편리하다.각 모듈은 개별적으로 테스트할 수 있습니다.의존항은 mock objects로 바꾸어 같은 인터페이스를 실현할 수 있다.
인터페이스는 세부 사항을 테스트할 필요가 없기 때문에 우리에게 무엇을 테스트해야 하는지 알려준다.인터페이스는 공용 API를 보여주고 테스트할 내용을 보여줍니다.
또 다른 장점은 서로 다른 가방에서 모듈을 조합하는 것이다.“DDD, Hexagonal, Onion, Clean, CQRS, …How I put it all together”에서 이에 대해 상세하게 설명하였다.
예를 들어, 응용 프로그램 계층별로 패키지를 분할할 수 있습니다.
의존항은 항상 영역을 가리킨다.이것은 역층을 완전히 독립시키기 때문에 우리는 많은 다른 응용 프로그램 사이에서 코드를 공유할 수 있다.
내부 핵과 인프라 공유에 대한 방주
인프라는 일반적으로 데이터베이스, 검색엔진 및 기타 외부 구동 서비스를 연결하는 코드이다.
Google 응용 프로그램에는 결과를 어떤 방식으로도 저장할 필요가 없기 때문에 인프라가 없습니다.
만약 우리가 그것을 가지고 있다면 인프라 시설 모듈은 매우 비슷할 것이다.
Reference
이 문제에 관하여(트리 이미지 생성, 세 번째 부분.분형에서 진짜 나무로), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/bespoyasov/generating-trees-images-part-3-from-fractal-to-a-real-tree-26nb텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)