Here we'll translate into practice all the high-level steps outlined in the introduction, using Cogs.js in the form of a TypeScript module. This guide will assume you've the read introduction and know about the core concepts and also followed the GettingStarted guide.
You don't need to learn Angular to just to follow this, but of course it is worth investing in learning the framework that you'll use to build your application.
This guide shows creating an Angular app displaying a Cube from scratch with 3D navigation to rotate and zoom the camera around the cube.
Cogs.js provides TypeScript Type Declarations for ease of use when developing TypeScript code.
import * as Cogs from '@kognifai/cogsengine';
import { dvec3 } from '@kognifai/cogsengine';
import { vec3, quat } from 'gl-matrix';
dvec3 is a double precision version of gl-matrix vec3. Imported specially to make it more like gl-matrix objects.
Some Common classes are:
// The main Cogs.js GUI control containing graphics.
class Cogs.Control;
// Runtime class where Entities are created (= control.runtime).
class Cogs.Runtime;
// Functionality for the 3D scene (= runtime.scene or control.scene).
class Cogs.Scene;
// Base class for all Cogs Entities. Camera/Cube/...
class Cogs.Entity;
// Base class for all Cogs Components.
class Cogs.Component;
// Class for resource handling Textures, Models, .
class Cogs.Resources;
This file can act as a quick guide to Cogs.js. Note that some of the definitions are a bit unusual for TypeScript code, but required in that context.
The type definitions can be found in the Path: node_modules/@kognifai/cogsengine/dist/cogs.esm.d.ts in your application.
npm install -g @angular/cli
(Don't need Angular routing, Select CSS style)ng new --skip-git first-app
(where you want it)cd first-app
ng serve --open
ctrl-c
in the console to exit the development serverThis setup shows typical steps for how to add Cogs to an Angular application. Access right file, Packages required to run Cogs, Angular.json config to load Cogs and a very simple Cogs window showing a cube.
Steps to add Cogs and Angular to your project:
Required to allow installing KDI packages. See Adding Cogs to your project in Cogs.js Readme.md. Can use an existing copy from Cogs or KDI DigitalTwin.
npm install @kognifai/cogsengine
npm install gl-matrix
Required to make Cogs.js resource files and the WebAssembly binary available to Cogs.
Find the "assets" section in angular.json
and replace it with:
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "**/*",
"input": "node_modules/@kognifai/cogsengine/dist/",
"output": "node_modules/@kognifai/cogsengine/dist/"
}
],
Put the following in src/app/app.component.css
for fixes size canvas. Apply more elaborate layouts for own applications.
canvas {
width: 700px;
height: 600px;
margin: 20px;
}
Replace src/app/app.component.html
with:
<header style="text-align:center">
<h3>{{ title }}</h3>
</header>
<div #myparent class="cogsparent"></div>
Replace src/app/app.component.ts
with the following example code:
import { Component, ElementRef, DoCheck, ViewChild, OnDestroy, AfterViewInit } from '@angular/core';
import * as Cogs from '@kognifai/cogsengine';
import { dvec3 } from '@kognifai/cogsengine';
import { vec3 } from 'gl-matrix';
@Component({
selector: 'app-root',
standalone: true,
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements AfterViewInit, DoCheck, OnDestroy {
public title = 'Hello world Cogs!';
private _cogsControl: Cogs.Control | undefined;
private cogsReady = false;
private width = 0;
private height = 0;
// Used by Cogs FetchHandler
private cancellableFetches = new Map<number, XMLHttpRequest>();
/**
* View child of app component. Link to HTML def in 'app.component.html'
*/
@ViewChild('myparent')
private myparent: ElementRef | undefined;
/** Gets parent HTML element. Avoids testing undefined. */
private get parent(): ElementRef {
return this.myparent!;
}
/** Gets the created Cogs Control cla.crss. Avoids testing undefined. */
private get cogsControl() {
return this._cogsControl!;
}
initScene(): void {
console.log('APP initScene');
this.initializeNavigation();
this.cogsControl.runtime.Cube.create();
this.cogsReady = true;
}
/**
* Cogs does by default not provide any 3D navigation.
* A simple 3D navigation is included as a Cogs component.
* More complex application may provide its own navigation.
* @returns navigation component.
*/
initializeNavigation(): Cogs.OrbitingCameraController {
const runtime = this.cogsControl.runtime;
const camera = runtime.scene.camera;
// Ensure no existing controller component found.
// Can normally just create and add controller as next step below.
let cameraController = camera.getComponent(runtime.OrbitingCameraController);
if (!cameraController) {
cameraController = runtime.OrbitingCameraController.create();
camera.components.add(cameraController);
}
cameraController.cameraTarget = dvec3.fromValues(0, 0, 0);
cameraController.distance = 20;
cameraController.horizontalAngle = 0;
cameraController.verticalAngle = Math.PI / 2;
cameraController.maxDistance = 10000;
// Scene origin should be set near the centre of the scene.
// Important for large offsets like UTM coordinates.
runtime.scene.origin = dvec3.fromValues(0, 0, 0);
return cameraController;
}
/**
* Called first to initialize Cogs and set to the scene.
*/
async ngAfterViewInit(): Promise<void> {
const dataFetchHandler = this.createFetchHandler(this, true);
this._cogsControl = await Cogs.Control.create({
extensions: [],
variant: 'sdk',
parent: this.parent.nativeElement,
fetchHandler: dataFetchHandler,
onInitialized: () => this.initScene(),
print: function (text) {
console.log('print', text);
},
});
this.cogsControl.domElement.oncontextmenu = function (e: any) {
e.preventDefault();
};
}
// Optional cleanup.
ngOnDestroy(): void {
this.cogsControl.runtime.clear();
this.cogsControl.domElement.remove();
}
ngDoCheck(): void {
if (
this.cogsReady &&
(this.width !== this.parent.nativeElement.offsetWidth || this.height !== this.parent.nativeElement.offsetHeight)
) {
this.width = this.parent.nativeElement.offsetWidth;
this.height = this.parent.nativeElement.offsetHeight;
console.log('width, height', this.width, this.height);
this.sizeChanged(this.width, this.height);
}
}
sizeChanged(width: number, height: number): void {
this.cogsControl.domElement.style.width = width + '';
this.cogsControl.domElement.style.height = height + '';
this.cogsControl.runtime.resize(width, height);
}
/**
* Creates FetchHandler with request cancellation for Cogs requests
* @param owner - Angular Application
* @param logging - TRUE if logging the data requests.
* @returns Returns FetchHandler to be passed to Cogs Control.create
*/
private createFetchHandler(owner: AppComponent, logging: boolean): Cogs.IFetchHandler {
const fetcher: Cogs.IFetchHandler = {
fetch: function (url: string, offset: number, size: number, handler: Cogs.FetchResponseHandler, fetchId: number): boolean {
if (logging) {
cogsPrint(`[Debug] Test Application - fetchHandler.fetcher(${url}, ${fetchId} (${offset}, ${size}))`);
}
const xhr = new XMLHttpRequest();
if (typeof fetchId === 'number') {
owner.cancellableFetches.set(fetchId, xhr);
}
xhr.responseType = 'arraybuffer';
xhr.onloadend = function () {
owner.cancellableFetches.delete(fetchId);
handler(xhr.status === 200 || xhr.status === 206 ? xhr.response : undefined, xhr.status);
};
xhr.open('GET', url);
if (0 < size) {
const len = offset + Math.max(1, size) - 1;
const range = `bytes=${offset}-${len}`;
xhr.setRequestHeader('Range', range);
}
xhr.send();
return true;
},
cancel: function (fetchId: number): void {
if (logging) {
cogsPrint(`[Info] Test Application - fetchHandler.cancel(${fetchId})`);
}
const xhr = owner.cancellableFetches.get(fetchId);
if (xhr) {
xhr.abort();
}
},
};
return fetcher;
}
}
// Cogs Logging Levels= Trace=0, Debug=1, Info=2, Warning=3, Error=4,Fatal=5
const Category = {
Trace: 0,
Debug: 1,
Info: 2,
Warning: 3,
Error: 4,
Fatal: 5,
};
/** Logging level for Tests: 0= Trace+All */
let logLevel = Category.Trace;
/**
* Cogs Logging. Cogs Log entries are of type: [Trace/Debug/Info/Warning/Error/Fatal][module] text
* Default is skip Debug/Info me
*/
export function cogsPrint(text: string): void {
if (text.startsWith('[Trace]')) {
if (logLevel <= Category.Trace) {
console.trace(text);
}
} else if (text.startsWith('[Debug]')) {
if (logLevel <= Category.Debug) {
console.debug(text);
}
} else if (text.startsWith('[Info')) {
if (logLevel <= Category.Info) {
console.info(text);
}
} else if (text.startsWith('[Warn')) {
if (logLevel <= Category.Warning) {
console.warn(text);
}
} else if (text.startsWith('[Error') || text.startsWith('[Fatal')) {
console.error(text);
} else {
console.log('[Unknown] ' + text);
}
}
The cogsPrint and createFetchHandler functions can typically be reused as a first step.
Run npm start
(defined as "ng serve" in package.json)
Open link to web-page in browser shown when starting the angular server.