Add basic tiled rendering

This commit is contained in:
schmelczerandras 2020-07-27 22:53:17 +02:00
parent 854e5a55a1
commit edffd22c2e
26 changed files with 350 additions and 167 deletions

View file

@ -1,8 +1,8 @@
import { IRenderer } from '../../drawing/i-renderer';
import { Command } from '../command';
export class BeforeDrawCommand extends Command {
public constructor(public readonly drawer?: IRenderer) {
export class BeforeRenderCommand extends Command {
public constructor(public readonly renderer?: IRenderer) {
super();
}
}

View file

@ -1,8 +1,8 @@
import { IRenderer } from '../../drawing/i-renderer';
import { Command } from '../command';
export class DrawCommand extends Command {
public constructor(public readonly drawer?: IRenderer) {
export class RenderCommand extends Command {
public constructor(public readonly renderer?: IRenderer) {
super();
}
}

View file

@ -1,9 +1,8 @@
import { IProgram } from '../program/i-program';
import { FrameBuffer } from './frame-buffer';
export class DefaultFrameBuffer extends FrameBuffer {
constructor(gl: WebGL2RenderingContext, programs: Array<IProgram>) {
super(gl, programs);
constructor(gl: WebGL2RenderingContext) {
super(gl);
this.frameBuffer = null;
this.setSize();

View file

@ -1,5 +1,4 @@
import { vec2 } from 'gl-matrix';
import { IProgram } from '../program/i-program';
export abstract class FrameBuffer {
public renderScale = 1;
@ -8,12 +7,9 @@ export abstract class FrameBuffer {
protected size: vec2;
protected frameBuffer: WebGLFramebuffer;
constructor(
protected gl: WebGL2RenderingContext,
protected programs: Array<IProgram>
) {}
constructor(protected gl: WebGL2RenderingContext) {}
public render(uniforms: any, colorInput?: WebGLTexture) {
public bindAndClear(colorInput?: WebGLTexture) {
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer);
if (colorInput !== null) {
@ -23,11 +19,6 @@ export abstract class FrameBuffer {
this.gl.viewport(0, 0, this.size.x, this.size.y);
this.gl.clearColor(0, 0, 0, 0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
this.programs.forEach((p) => {
p.bindAndSetUniforms(uniforms);
p.draw();
});
}
public setSize() {

View file

@ -1,11 +1,10 @@
import { IProgram } from '../program/i-program';
import { FrameBuffer } from './frame-buffer';
export class IntermediateFrameBuffer extends FrameBuffer {
private frameTexture: WebGLTexture;
constructor(gl: WebGL2RenderingContext, programs: Array<IProgram>) {
super(gl, programs);
constructor(gl: WebGL2RenderingContext) {
super(gl);
this.frameTexture = this.gl.createTexture();
this.configureTexture();

View file

@ -10,7 +10,7 @@ export class FragmentShaderOnlyProgram extends Program {
substitutions: { [name: string]: string }
) {
super(gl, vertexShaderSource, fragmentShaderSource, substitutions);
this.prepareScreenQuad('a_position');
this.prepareScreenQuad('vertexPosition');
}
public bind() {

View file

@ -1,5 +1,8 @@
import { vec2 } from 'gl-matrix';
export interface IProgram {
bindAndSetUniforms(values: { [name: string]: any }): void;
setDrawingRectangle(topLeft: vec2, size: vec2): void;
draw(): void;
delete(): void;
}

View file

@ -1,3 +1,4 @@
import { mat2d, vec2 } from 'gl-matrix';
import { createShader } from '../helper/create-shader';
import { loadUniform } from '../helper/load-uniform';
import { IProgram } from './i-program';
@ -5,6 +6,8 @@ import { IProgram } from './i-program';
export abstract class Program implements IProgram {
protected program: WebGLProgram;
private shaders: Array<WebGLShader> = [];
private modelTransform = mat2d.identity(mat2d.create());
private readonly ndcToUv: mat2d;
private uniforms: Array<{
name: Array<string>;
@ -20,15 +23,22 @@ export abstract class Program implements IProgram {
) {
this.createProgram(vertexShaderSource, fragmentShaderSource, substitutions);
this.queryUniforms();
this.ndcToUv = mat2d.fromScaling(mat2d.create(), vec2.fromValues(0.5, 0.5));
mat2d.translate(this.ndcToUv, this.ndcToUv, vec2.fromValues(1, 1));
}
public bindAndSetUniforms(values: { [name: string]: any }) {
this.bind();
this.setUniforms(values);
this.setUniforms({ modelTransform: this.modelTransform, ...values });
}
public bind() {
this.gl.useProgram(this.program);
public setDrawingRectangle(topLeft: vec2, size: vec2) {
mat2d.invert(this.modelTransform, this.ndcToUv);
mat2d.translate(this.modelTransform, this.modelTransform, topLeft);
mat2d.scale(this.modelTransform, this.modelTransform, size);
mat2d.multiply(this.modelTransform, this.modelTransform, this.ndcToUv);
}
public setUniforms(values: { [name: string]: any }) {
@ -51,6 +61,10 @@ export abstract class Program implements IProgram {
public abstract draw(): void;
protected bind() {
this.gl.useProgram(this.program);
}
private queryUniforms() {
const uniformCount = this.gl.getProgramParameter(
this.program,

View file

@ -1,4 +1,4 @@
import { InfoText } from '../../../objects/types/info-text';
import { vec2 } from 'gl-matrix';
import { FragmentShaderOnlyProgram } from './fragment-shader-only-program';
import { IProgram } from './i-program';
@ -9,6 +9,9 @@ export class UniformArrayAutoScalingProgram implements IProgram {
}> = [];
private current: FragmentShaderOnlyProgram;
private drawingRectangleTopLeft = vec2.fromValues(0, 0);
private drawingRectangleSize = vec2.fromValues(1, 1);
constructor(
private gl: WebGL2RenderingContext,
private vertexShaderSource: string,
@ -17,6 +20,7 @@ export class UniformArrayAutoScalingProgram implements IProgram {
private options: {
getValueFromUniforms: (values: { [name: string]: any }) => number;
uniformArraySizeName: string;
enablingMacro: string;
startingValue: number;
steps: number;
maximumValue: number;
@ -31,12 +35,10 @@ export class UniformArrayAutoScalingProgram implements IProgram {
}
}
bindAndSetUniforms(values: { [name: string]: any }): void {
public bindAndSetUniforms(values: { [name: string]: any }): void {
let value = this.options.getValueFromUniforms(values);
value = Math.min(this.options.maximumValue, value);
InfoText.modifyRecord(this.options.uniformArraySizeName, value.toString());
const closest = this.programs.find(
(p) => value < p.value && value + this.options.steps >= p.value
);
@ -46,14 +48,23 @@ export class UniformArrayAutoScalingProgram implements IProgram {
this.current = this.createProgram(value + this.options.steps);
}
this.current.setDrawingRectangle(
this.drawingRectangleTopLeft,
this.drawingRectangleSize
);
this.current.bindAndSetUniforms(values);
}
draw(): void {
public setDrawingRectangle(topLeft: vec2, size: vec2) {
this.drawingRectangleTopLeft = topLeft;
this.drawingRectangleSize = size;
}
public draw(): void {
this.current.draw();
}
delete(): void {
public delete(): void {
this.programs.forEach((p) => p.program.delete());
}
@ -64,6 +75,7 @@ export class UniformArrayAutoScalingProgram implements IProgram {
this.fragmentShaderSource,
{
[this.options.uniformArraySizeName]: Math.floor(arraySize).toString(),
[this.options.enablingMacro]: arraySize > 0 ? '1' : '0',
...this.substitutions,
}
);

View file

@ -1,14 +1,20 @@
import { vec2 } from 'gl-matrix';
import { Circle } from '../math/circle';
import { Circle } from './primitives/circle';
import { IPrimitive } from './primitives/i-primitive';
export interface IRenderer {
startFrame(deltaTime: DOMHighResTimeStamp): void;
finishFrame(): void;
giveUniforms(uniforms: any): void;
appendToUniformList(listName: string, ...values: Array<any>): void;
drawPrimitive(primitive: IPrimitive): void;
setCameraPosition(position: vec2): void;
setCursorPosition(position: vec2): void;
setInViewArea(size: number): vec2;
giveUniforms(uniforms: any): void;
appendToUniformList(listName: string, ...values: Array<any>): void;
screenUvToWorldCoordinate(mousePosition: vec2): vec2;
drawInfoText(text: string): void;
isOnScreen(boundingCircle: Circle): boolean;

View file

@ -0,0 +1,23 @@
import { vec2 } from 'gl-matrix';
import { IPrimitive } from './i-primitive';
export class Circle implements IPrimitive {
public constructor(public center: vec2, public radius: number) {}
public distance(target: vec2): number {
return vec2.distance(this.center, target) - this.radius;
}
public minimumDistance(target: vec2): number {
return vec2.distance(this.center, target) - this.radius;
}
public isInside(target: vec2): boolean {
return this.distance(target) < 0;
}
public areIntersecting(other: Circle): boolean {
const distance = vec2.distance(this.center, other.center);
return distance < this.radius + other.radius;
}
}

View file

@ -0,0 +1,6 @@
import { vec2 } from 'gl-matrix';
export interface IPrimitive {
distance(target: vec2): number;
minimumDistance(target: vec2): number;
}

View file

@ -0,0 +1,46 @@
import { vec2 } from 'gl-matrix';
import { clamp01 } from '../../helper/clamp';
import { mix } from '../../helper/mix';
import { Circle } from './circle';
import { IPrimitive } from './i-primitive';
export class TunnelShape implements IPrimitive {
public readonly toFromDelta: vec2;
private toFromDeltaLength: number;
private boundingCircle: Circle;
constructor(
public readonly from: vec2,
public readonly to: vec2,
public readonly fromRadius: number,
public readonly toRadius: number
) {
this.toFromDelta = vec2.subtract(vec2.create(), to, from);
this.toFromDeltaLength = vec2.length(this.toFromDelta);
this.boundingCircle = new Circle(
vec2.fromValues(from.x / 2 + to.x / 2, from.y / 2 + to.y / 2),
Math.max(fromRadius, toRadius) + vec2.distance(from, to)
);
}
public distance(target: vec2): number {
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01(
vec2.dot(targetFromDelta, this.toFromDelta) /
this.toFromDeltaLength /
this.toFromDeltaLength
);
return (
vec2.distance(
targetFromDelta,
vec2.scale(vec2.create(), this.toFromDelta, h)
) - mix(this.fromRadius, this.toRadius, h)
);
}
public minimumDistance(target: vec2): number {
return this.boundingCircle.distance(target);
}
}

View file

@ -1,13 +1,16 @@
import { mat2d, vec2 } from 'gl-matrix';
import { clamp } from '../helper/clamp';
import { Circle } from '../math/circle';
import { Rectangle } from '../math/rectangle';
import { InfoText } from '../objects/types/info-text';
import { DefaultFrameBuffer } from './graphics-library/frame-buffer/default-frame-buffer';
import { IntermediateFrameBuffer } from './graphics-library/frame-buffer/intermediate-frame-buffer';
import { WebGlStopwatch } from './graphics-library/helper/stopwatch';
import { IProgram } from './graphics-library/program/i-program';
import { UniformArrayAutoScalingProgram } from './graphics-library/program/uniform-array-autoscaling-program';
import { IRenderer } from './i-renderer';
import { Circle } from './primitives/circle';
import { IPrimitive } from './primitives/i-primitive';
import { Rectangle } from './primitives/rectangle';
import { TunnelShape } from './primitives/tunnel-shape';
export class WebGl2Renderer implements IRenderer {
private gl: WebGL2RenderingContext;
@ -19,6 +22,12 @@ export class WebGl2Renderer implements IRenderer {
private cursorPosition = vec2.create();
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
private lightingFrameBuffer: DefaultFrameBuffer;
private distanceProgram: IProgram;
private lightingProgram: IProgram;
private primitives: Array<IPrimitive>;
private tileMultiplier = 5;
private targetDeltaTime = (1 / 30) * 1000;
private deltaTimeError = (1 / 1000) * 1000;
@ -46,7 +55,7 @@ export class WebGl2Renderer implements IRenderer {
this.targetDeltaTime - this.deltaTimeError
) {
this.distanceFieldFrameBuffer.renderScale +=
this.additiveQualityIncrease / 4;
this.additiveQualityIncrease / 3;
this.lightingFrameBuffer.renderScale += this.additiveQualityIncrease;
} else if (
this.exponentialDecayedDeltaTime >
@ -91,39 +100,7 @@ export class WebGl2Renderer implements IRenderer {
throw new Error('WebGl2 is not supported');
}
const distanceScale = 64;
this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl, [
new UniformArrayAutoScalingProgram(
this.gl,
shaderSources[0][0],
shaderSources[0][1],
{ distanceScale },
{
getValueFromUniforms: (v) => (v.lines ? v.lines.length / 2 : 0),
uniformArraySizeName: 'lineCount',
startingValue: 5,
steps: 5,
maximumValue: 100,
}
),
]);
this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl, [
new UniformArrayAutoScalingProgram(
this.gl,
shaderSources[1][0],
shaderSources[1][1],
{ distanceScale },
{
getValueFromUniforms: (v) => (v.lights ? v.lights.length : 0),
uniformArraySizeName: 'lightCount',
startingValue: 1,
steps: 1,
maximumValue: 8,
}
),
]);
this.createPipeline(shaderSources);
this.distanceFieldFrameBuffer.renderScale = 0.5;
this.lightingFrameBuffer.renderScale = 1;
@ -133,8 +110,49 @@ export class WebGl2Renderer implements IRenderer {
} catch {}
}
private createPipeline(shaderSources: Array<[string, string]>) {
const distanceScale = 64;
this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl);
this.distanceProgram = new UniformArrayAutoScalingProgram(
this.gl,
shaderSources[0][0],
shaderSources[0][1],
{ distanceScale },
{
getValueFromUniforms: (v) => (v.lines ? v.lines.length / 2 : 0),
uniformArraySizeName: 'lineCount',
startingValue: 0,
enablingMacro: 'linesEnabled',
steps: 1,
maximumValue: 15,
}
);
this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl);
this.lightingProgram = new UniformArrayAutoScalingProgram(
this.gl,
shaderSources[1][0],
shaderSources[1][1],
{ distanceScale },
{
getValueFromUniforms: (v) => (v.lights ? v.lights.length : 0),
uniformArraySizeName: 'lightCount',
startingValue: 1,
enablingMacro: null,
steps: 1,
maximumValue: 8,
}
);
}
public drawPrimitive(primitive: IPrimitive) {
this.primitives.push(primitive);
}
public startFrame(deltaTime: DOMHighResTimeStamp): void {
this.configureRenderScale(deltaTime);
this.primitives = [];
this.stopwatch?.start();
this.uniforms = {};
@ -144,11 +162,81 @@ export class WebGl2Renderer implements IRenderer {
public finishFrame() {
this.calculateOwnUniforms();
this.distanceFieldFrameBuffer.render(this.uniforms);
this.lightingFrameBuffer.render(
this.uniforms,
this.distanceFieldFrameBuffer.bindAndClear();
const q = 1 / this.tileMultiplier;
const uvSize = vec2.fromValues(q, q);
const possiblyOnScreenPrimitives = this.primitives.filter(
(p) => p.minimumDistance(this.viewCircle.center) < this.viewCircle.radius
) as Array<TunnelShape>;
InfoText.modifyRecord(
'nearby lines',
possiblyOnScreenPrimitives.length.toString()
);
const origin = vec2.transformMat2d(
vec2.create(),
vec2.fromValues(0, 0),
this.uniforms.uvToWorld
);
const firstCenter = vec2.transformMat2d(
vec2.create(),
vec2.fromValues(q, q),
this.uniforms.uvToWorld
);
vec2.subtract(firstCenter, firstCenter, origin);
const worldR = vec2.length(firstCenter);
this.uniforms.maxMinDistance = 2 * worldR;
let sumLineCount = 0;
for (let x = 0; x < 1; x += q) {
for (let y = 0; y < 1; y += q) {
const uvBottomLeft = vec2.fromValues(x, y);
this.distanceProgram.setDrawingRectangle(uvBottomLeft, uvSize);
const tileCenterWorldCoordinates = vec2.transformMat2d(
vec2.create(),
vec2.add(vec2.create(), uvBottomLeft, vec2.fromValues(q, q)),
this.uniforms.uvToWorld
);
const primitivesNearTile = possiblyOnScreenPrimitives.filter(
(p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR
);
sumLineCount += primitivesNearTile.length;
this.uniforms.lines = [];
this.uniforms.radii = [];
for (let tunnel of primitivesNearTile) {
this.uniforms.lines.push(tunnel.from);
this.uniforms.lines.push(tunnel.toFromDelta);
this.uniforms.radii.push(tunnel.fromRadius);
this.uniforms.radii.push(tunnel.toRadius);
}
this.distanceProgram.bindAndSetUniforms(this.uniforms);
this.distanceProgram.draw();
}
}
InfoText.modifyRecord(
'lines',
(sumLineCount / this.tileMultiplier / this.tileMultiplier).toFixed(2)
);
this.lightingFrameBuffer.bindAndClear(
this.distanceFieldFrameBuffer.colorTexture
);
this.lightingProgram.bindAndSetUniforms(this.uniforms);
this.lightingProgram.draw();
this.stopwatch?.stop();
}
@ -158,13 +246,11 @@ export class WebGl2Renderer implements IRenderer {
this.distanceFieldFrameBuffer.getSize()
);
const ndcToWorld = mat2d.fromTranslation(
const uvToWorld = mat2d.fromTranslation(
mat2d.create(),
this.viewBox.topLeft
);
mat2d.scale(ndcToWorld, ndcToWorld, this.viewBox.size);
mat2d.scale(ndcToWorld, ndcToWorld, vec2.fromValues(0.5, 0.5));
mat2d.translate(ndcToWorld, ndcToWorld, vec2.fromValues(1, 1));
mat2d.scale(uvToWorld, uvToWorld, this.viewBox.size);
const worldToDistanceUV = mat2d.scale(
mat2d.create(),
@ -173,13 +259,20 @@ export class WebGl2Renderer implements IRenderer {
);
mat2d.invert(worldToDistanceUV, worldToDistanceUV);
const ndcToUv = mat2d.fromScaling(
mat2d.create(),
vec2.fromValues(0.5, 0.5)
);
mat2d.translate(ndcToUv, ndcToUv, vec2.fromValues(1, 1));
const cursorPosition = this.screenUvToWorldCoordinate(this.cursorPosition);
this.giveUniforms({
distanceScreenToWorld,
worldToDistanceUV,
cursorPosition,
ndcToWorld,
ndcToUv,
uvToWorld,
viewBoxSize: this.viewBox.size,
});
}
@ -241,6 +334,7 @@ export class WebGl2Renderer implements IRenderer {
);
const halfDiagonal = vec2.scale(vec2.create(), this.viewBox.size, 0.5);
this.viewCircle.center = vec2.add(
vec2.create(),
this.viewBox.topLeft,

View file

@ -4,8 +4,8 @@ import caveVertexShader from '../shaders/passthrough-distance-vs.glsl';
import lightsVertexShader from '../shaders/passthrough-shading-vs.glsl';
// import lightsShader from '../shaders/rainbow-shading-fs.glsl';
import { CommandBroadcaster } from './commands/command-broadcaster';
import { BeforeDrawCommand } from './commands/types/before-draw';
import { DrawCommand } from './commands/types/draw';
import { BeforeRenderCommand } from './commands/types/before-render';
import { RenderCommand } from './commands/types/draw';
import { StepCommand } from './commands/types/step';
import { WebGl2Renderer } from './drawing/webgl2-renderer';
import { timeIt } from './helper/timing';
@ -76,8 +76,8 @@ export class Game {
this.objects.sendCommand(new StepCommand(deltaTime));
this.renderer.startFrame(deltaTime);
this.objects.sendCommand(new BeforeDrawCommand(this.renderer));
this.objects.sendCommand(new DrawCommand(this.renderer));
this.objects.sendCommand(new BeforeRenderCommand(this.renderer));
this.objects.sendCommand(new RenderCommand(this.renderer));
this.renderer.finishFrame();
window.requestAnimationFrame(this.gameLoop.bind(this));

View file

@ -1,4 +1,5 @@
export const clamp = (value: number, min: number, max: number): number =>
Math.min(max, Math.max(min, value));
export const clamp01 = (value: number): number => clamp(value, 0, 1);
export const clamp01 = (value: number): number =>
Math.min(1, Math.max(0, value));

View file

@ -0,0 +1,2 @@
export const mix = (from: number, to: number, q: number) =>
from + (to - from) * q;

View file

@ -1,15 +0,0 @@
import { vec2 } from 'gl-matrix';
export class Circle {
public constructor(public center: vec2, public radius: number) {}
public isInside(position: vec2): boolean {
const distance = vec2.distance(this.center, position);
return distance < this.radius;
}
public areIntersecting(other: Circle): boolean {
const distance = vec2.distance(this.center, other.center);
return distance < this.radius + other.radius;
}
}

View file

@ -1,5 +1,5 @@
import { vec2 } from 'gl-matrix';
import { BeforeDrawCommand } from '../../commands/types/before-draw';
import { BeforeRenderCommand } from '../../commands/types/before-render';
import { CursorMoveCommand } from '../../commands/types/cursor-move-command';
import { MoveToCommand } from '../../commands/types/move-to';
import { ZoomCommand } from '../../commands/types/zoom';
@ -13,7 +13,7 @@ export class Camera extends GameObject {
constructor(private light: CircleLight) {
super();
this.addCommandExecutor(BeforeDrawCommand, this.draw.bind(this));
this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
this.addCommandExecutor(
CursorMoveCommand,
@ -22,10 +22,10 @@ export class Camera extends GameObject {
this.addCommandExecutor(ZoomCommand, this.zoom.bind(this));
}
private draw(c: BeforeDrawCommand) {
c.drawer.setCameraPosition(this.position);
c.drawer.setCursorPosition(this.cursorPosition);
this._boundingBoxSize = c.drawer.setInViewArea(this.inViewArea);
private draw(c: BeforeRenderCommand) {
c.renderer.setCameraPosition(this.position);
c.renderer.setCursorPosition(this.cursorPosition);
this._boundingBoxSize = c.renderer.setInViewArea(this.inViewArea);
}
private moveTo(c: MoveToCommand) {

View file

@ -1,7 +1,7 @@
import { vec2, vec3 } from 'gl-matrix';
import { DrawCommand } from '../../commands/types/draw';
import { RenderCommand } from '../../commands/types/draw';
import { MoveToCommand } from '../../commands/types/move-to';
import { Circle } from '../../math/circle';
import { Circle } from '../../drawing/primitives/circle';
import { GameObject } from '../game-object';
const range = 2000;
@ -18,13 +18,13 @@ export class CircleLight extends GameObject {
this.boundingCircle = new Circle(center, range);
this.addCommandExecutor(DrawCommand, this.draw.bind(this));
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
}
private draw(c: DrawCommand) {
if (c.drawer.isPositionOnScreen(this.center)) {
c.drawer.appendToUniformList('lights', {
private draw(c: RenderCommand) {
if (c.renderer.isPositionOnScreen(this.center)) {
c.renderer.appendToUniformList('lights', {
center: this.center,
radius: this.radius,
value: this.value,

View file

@ -1,11 +1,11 @@
import { RenderCommand } from '../../commands/types/draw';
import { GameObject } from '../game-object';
import { DrawCommand } from '../../commands/types/draw';
export class InfoText extends GameObject {
constructor() {
super();
this.addCommandExecutor(DrawCommand, this.draw.bind(this));
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
}
private static records: Map<string, string> = new Map();
@ -14,9 +14,9 @@ export class InfoText extends GameObject {
InfoText.records.set(key, value);
}
private draw(e: DrawCommand) {
private draw(e: RenderCommand) {
let text = '';
InfoText.records.forEach((v, k) => (text += `${k}\n\t${v}\n`));
e.drawer.drawInfoText(text);
e.renderer.drawInfoText(text);
}
}

View file

@ -1,36 +1,20 @@
import { vec2 } from 'gl-matrix';
import { DrawCommand } from '../../commands/types/draw';
import { Circle } from '../../math/circle';
import { RenderCommand } from '../../commands/types/draw';
import { TunnelShape } from '../../drawing/primitives/tunnel-shape';
import { GameObject } from '../game-object';
export interface Line {}
export class Tunnel extends GameObject {
private boundingCircle: Circle;
private tangent: vec2;
private primitive: TunnelShape;
constructor(
private from: vec2,
private to: vec2,
private radiusFrom: number,
private radiusTo: number
) {
constructor(from: vec2, to: vec2, fromRadius: number, toRadius: number) {
super();
this.boundingCircle = new Circle(
vec2.fromValues(from.x / 2 + to.x / 2, from.y / 2 + to.y / 2),
radiusFrom + radiusTo + vec2.distance(from, to)
);
this.tangent = vec2.subtract(vec2.create(), to, from);
this.addCommandExecutor(DrawCommand, this.draw.bind(this));
this.primitive = new TunnelShape(from, to, fromRadius, toRadius);
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
}
private draw(c: DrawCommand) {
if (c.drawer.isOnScreen(this.boundingCircle)) {
c.drawer.appendToUniformList('lines', this.from, this.tangent);
c.drawer.appendToUniformList('radii', this.radiusFrom, this.radiusTo);
}
private draw(c: RenderCommand) {
c.renderer.drawPrimitive(this.primitive);
}
}

View file

@ -2,27 +2,33 @@
precision mediump float;
#define INFINITY 200.0
#define LINES_ENABLED {linesEnabled}
#define LINE_COUNT {lineCount}
#define CAVE_COLOR vec3(0.36, 0.38, 0.76)
#define AIR_COLOR vec3(0.7)
#define DISTANCE_SCALE {distanceScale}
// start, end - start
uniform vec2[LINE_COUNT * 2] lines;
// startRadius, endRadois
uniform float[LINE_COUNT * 2] radii;
uniform float maxMinDistance;
#if LINES_ENABLED
// start, end - start
uniform vec2[LINE_COUNT * 2] lines;
// startRadius, endRadois
uniform float[LINE_COUNT * 2] radii;
#endif
in vec2 worldCoordinates;
out vec4 fragmentColor;
void main() {
float minDistance = INFINITY;
#if LINES_ENABLED
float minDistance = maxMinDistance;
for (int i = 0; i < LINE_COUNT; i++) {
vec2 pa = worldCoordinates - lines[2 * i];
vec2 ba = lines[2 * i + 1];
float baLength = length(ba);
// todo: do we really want this dot(pa / baLength, ba / baLength)
float h = clamp(dot(pa / baLength, ba / baLength), 0.0, 1.0);
float lineDistance = distance(pa, ba * h) - mix(radii[2 * i], radii[2 * i + 1], h);
@ -34,4 +40,8 @@ void main() {
mix(CAVE_COLOR, AIR_COLOR, clamp(distance, -10.0, 0.0) / 10.0 + 1.0),
distance / DISTANCE_SCALE
);
#else
fragmentColor = vec4(AIR_COLOR, maxMinDistance / DISTANCE_SCALE);
#endif
}

View file

@ -2,11 +2,15 @@
precision mediump float;
uniform mat3 ndcToWorld;
in vec4 a_position;
uniform mat3 modelTransform;
uniform mat3 uvToWorld;
uniform mat3 ndcToUv;
in vec4 vertexPosition;
out vec2 worldCoordinates;
void main() {
worldCoordinates = (vec3(a_position.xy, 1.0) * ndcToWorld).xy;
gl_Position = a_position;
vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform;
worldCoordinates = (vertexPosition2D * ndcToUv * uvToWorld).xy;
gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0);
}

View file

@ -4,12 +4,14 @@ precision mediump float;
#define LIGHT_COUNT {lightCount}
uniform mat3 ndcToWorld;
in vec4 a_position;
uniform mat3 ndcToUv;
uniform mat3 uvToWorld;
in vec4 vertexPosition;
out vec2 worldCoordinates;
out vec2 uvCoordinates;
uniform struct Light {
vec2 center;
float radius;
@ -19,12 +21,14 @@ uniform struct Light {
out vec2[LIGHT_COUNT] directions;
void main() {
worldCoordinates = (vec3(a_position.xy, 1.0) * ndcToWorld).xy;
uvCoordinates = ((a_position.xy + vec2(1.0)) / 2.0).xy;
vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0);
vec3 uvCoordinates1 = vertexPosition2D * ndcToUv;
worldCoordinates = (uvCoordinates1 * uvToWorld).xy;
uvCoordinates = (uvCoordinates1).xy;
for (int i = 0; i < LIGHT_COUNT; i++) {
directions[i] = lights[i].center - worldCoordinates;
}
gl_Position = a_position;
gl_Position = vertexPosition;
}