A beginner to explore how to implement Canvas engine (ignition start)

Starting! Want to fuck

I am very interested in visualization and music for web pages, so I wanted to have a look at others’ source code libraries and study how others implemented a WebGL visualization engine. Looking at it, I suddenly realized that why other people’s libraries are designed like this and why classes are defined like this is hard for me to believe. All I know is that they can write a mature engine.

The more I looked at the source code, the more I felt that it was too big and complicated. As the saying goes, Rome wasn’t built in a day, so I went back to the original version of the library when it was first submitted.

Unfortunately, I realized that it was already a fairly complete engine at the beginning, so I decided to write one myself so that I could be more impressed with the problems I encountered and get a clearer picture of the module design.

On the whole! The project

I’m going to start with lerNA that can manage multiple projects, although I won’t necessarily be able to write multiple project libraries. Learn how to use learn

After creating the project, I created a project called @canvas-lib/core, which I declared to be the core code library, and then a module called test, which always needs to be run on the page to see what it looks like. Thus the basic structure of the subproject is determined:

Module! To make up

The next part was to design the modules of the core library, which actually took several days, because the most basic design is constantly changing, so far there are the following modules:

Engine

The engine module is the starting point for the whole project, where most of the major classes are initialized. Of course, it is mainly Canvas Canvas module and Scene Scene module used to store objects on Canvas, as well as an immature event module distributor and a relatively simple rendering module purely realized by RAF.

export class Engine {
    public scene: Scene;
    public canvas: Canvas;
    public dispatcher:EventDispatch;

    constructor(container: string, canvasOptions? : CanvasOptions){
        this.initEngine(container,canvasOptions);
    }

    initEngine(container: string, canvasOptions? : CanvasOptions){
        this.scene = new Scene();
        this.canvas = new Canvas(container,this.scene,canvasOptions);
        new Render(this);
        this.dispatcher = new EventDispatch();
        new Behavior(this);
    }

    on(type:EventType,fn:(event: Event)=>void){
        this.dispatcher.on(new Event(type.this,{}),fn);
    }

    off(type:EventType,fn:(event: Event)=>void){
        this.dispatcher.off(type,fn); }}Copy the code

Canvas

There is default configuration in Canvas plan, but it has not been added yet, so options is optional, but it does not exist in the code directly return.

public initCanvas(container: string, options? : CanvasOptions) {
        if(! container)return;
        this._canvas = document.getElementById(container) as HTMLCanvasElement;
        this._ctx = this._canvas.getContext("2d");
        if (options) {
            if (options.width) this._canvas.width = options.width;
            if (options.height) this._canvas.height = options.height;
            if (options.bgColor) {
                this._ctx.fillStyle = options.bgColor.hex;
                this._ctx.fillRect(0.0.this._canvas.width, this._canvas.height); }}return this;
    }
Copy the code

Scene

The scene section is a relatively simple data to manage, and should be enriched later to make it easier to manage graphics in groups.

export class Scene {
    public entityList: Array<Entity>;

    constructor(){
        this.initScene();
    }

    initScene(){
        this.entityList = [];
        return this;
    }

    add(entity: Entity) {
        this.entityList.push(entity);
    }

    remove(id: string | number | symbol) {
        const idx = this.entityList.findIndex((entity: Entity) = > entity.id === id);
        const len = this.entityList.length;
        const temp = this.entityList[len - 1];
        this.entityList[idx] = temp;
        this.entityList.pop();
    }

    getContainsShapes(point:Point){
        const shapes = [];
        this.entityList.forEach(entity= >{
            if(entity.isContains(point)){ shapes.push(entity); }});returnshapes; }}Copy the code

EventDispatch

The event section also simply implements a base, and then adds whatever event is written elsewhere to the event type. This part is also the one that has been repeatedly modified the most among all modules. At the beginning, I was not familiar with the logic of event management, which led to confusion in the design. In the end, it was bloated and complicated when I used it.

export class EventDispatch {
    private _events:any;
    private _listener:Listener;

    constructor(){
        this._events = Object.create(null);
        this._listener = new Listener();
    }

    on(event:Event,fn:(event: Event)=>void){
        if(this._events[event.type]){
            this._events[event.type].push({
                fn,
                event
            });
        }else{
            this._events[event.type] = [{
                fn,
                event
            }];
            this._listener.addEventListener(event.type,this.mouseEvent.bind(this)); }}off(type:EventType,fn:(event: Event)=>void){
        if(this._events[type]) {const idx = this._events[type].findIndex(i= >i.fn===fn);
            const len = this._events[type].length;
            if(len<2&&idx<1) {delete this._events[type];
                this._listener.removeEventListener(type.this.mouseEvent.bind(this));
            }else{
                this._events[type][idx] = this._events[type][len-1];
                this._events[type].pop(); }}}dispatch(type:string,data:any){
        if(this._events[type]) {this._events[type].forEach(_event= >{ _event.event.data = {... _event.event.data,... data}; _event.fn(_event.event); }); }}mouseEvent(event:MouseEvent){
        this.dispatch(<EventType>event.type,{event}); }}Copy the code

Continue! Get down

At present, it is only to make an initial summary, which is to build a basic framework. It can be based on this, first horizontal rich content, and then longitudinal expansion. When I ask for advice on module design or feature design, people ask me what I want to achieve in the end. It’s results-oriented. But I said I don’t know, I just want to learn from the beginning, there is no specific goal, I think I might be learning-oriented, do whatever comes to mind. Although this is messy, I will remember that one out is one out, but I feel that this is my favorite front end, I can do what I want to do, interest, is correct.