Add blob and fix some issues

This commit is contained in:
schmelczerandras 2020-08-18 09:40:51 +02:00
parent 5b4e67cbf0
commit edca93fcfe
13 changed files with 237 additions and 67 deletions

View file

@ -0,0 +1,26 @@
import { IDrawable } from './i-drawable';
import { IDrawableDescriptor } from './i-drawable-descriptor';
import { settings } from '../settings';
import { Blob } from '../../shapes/types/blob';
export class DrawableBlob extends Blob implements IDrawable {
public static descriptor: IDrawableDescriptor = {
uniformName: 'blobs',
countMacroName: 'blobCount',
shaderCombinationSteps: settings.shaderCombinations.blobSteps,
};
public serializeToUniforms(uniforms: any): void {
const uniformName = DrawableBlob.descriptor.uniformName;
if (!uniforms.hasOwnProperty(uniformName)) {
uniforms[uniformName] = [];
}
uniforms[uniformName].push({
headCenter: this.head.center,
torsoCenter: this.torso.center,
leftFootCenter: this.leftFoot.center,
rightFootCenter: this.rightFoot.center,
});
}
}

View file

@ -1,11 +1,22 @@
import { FrameBuffer } from './frame-buffer';
import { enableExtension } from '../helper/enable-extension';
export class IntermediateFrameBuffer extends FrameBuffer {
private frameTexture: WebGLTexture;
private floatLinearEnabled = true;
constructor(gl: WebGL2RenderingContext) {
super(gl);
enableExtension(gl, 'EXT_color_buffer_float');
try {
enableExtension(gl, 'OES_texture_float_linear');
} catch {
this.floatLinearEnabled = false;
}
this.frameTexture = this.gl.createTexture();
this.configureTexture();
@ -27,12 +38,12 @@ export class IntermediateFrameBuffer extends FrameBuffer {
this.gl.texImage2D(
this.gl.TEXTURE_2D,
0,
this.gl.RGBA,
this.gl.RG16F,
this.size.x,
this.size.y,
0,
this.gl.RGBA,
this.gl.UNSIGNED_BYTE,
this.gl.RG,
this.gl.FLOAT,
null
);
}
@ -42,12 +53,12 @@ export class IntermediateFrameBuffer extends FrameBuffer {
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_MAG_FILTER,
this.gl.LINEAR
this.floatLinearEnabled ? this.gl.LINEAR : this.gl.NEAREST
);
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_MIN_FILTER,
this.gl.LINEAR
this.floatLinearEnabled ? this.gl.LINEAR : this.gl.NEAREST
);
this.gl.texParameteri(
this.gl.TEXTURE_2D,

View file

@ -0,0 +1,10 @@
export const enableExtension = (
gl: WebGL2RenderingContext,
name: string
): any => {
if (gl.getSupportedExtensions().indexOf(name) == -1) {
throw new Error(`Unsupported extension ${name}`);
}
return gl.getExtension(name);
};

View file

@ -1,4 +1,5 @@
import { InfoText } from '../../../objects/types/info-text';
import { enableExtension } from './enable-extension';
// https://www.khronos.org/registry/webgl/extensions/EXT_disjoint_timer_query_webgl2/
@ -10,15 +11,8 @@ export class WebGlStopwatch {
private resultsInNanoSeconds: number;
constructor(private gl: WebGL2RenderingContext) {
if (
this.gl
.getSupportedExtensions()
.indexOf('EXT_disjoint_timer_query_webgl2') == -1
) {
throw new Error('Unsupported extension');
}
this.timerExtension = this.gl.getExtension(
this.timerExtension = enableExtension(
gl,
'EXT_disjoint_timer_query_webgl2'
);
}

View file

@ -34,7 +34,7 @@ export class UniformArrayAutoScalingProgram implements IProgram {
);
const closest = this.programs.find((p) =>
p.values.every((v, i) => v > values[i])
p.values.every((v, i) => v >= values[i])
);
this.current = closest ? closest.program : last(this.programs).program;

View file

@ -16,6 +16,8 @@ import { FpsAutoscaler } from './fps-autoscaler';
import { RenderingPass } from './rendering-pass';
import { IDrawable } from '../drawables/i-drawable';
import { DrawableTunnel } from '../drawables/drawable-tunnel';
import { enableExtension } from '../graphics-library/helper/enable-extension';
import { DrawableBlob } from '../drawables/drawable-blob';
export class WebGl2Renderer implements IRenderer {
private gl: WebGL2RenderingContext;
@ -50,7 +52,7 @@ export class WebGl2Renderer implements IRenderer {
this.distancePass = new RenderingPass(
this.gl,
[caveVertexShader, caveFragmentShader],
[DrawableTunnel.descriptor],
[DrawableTunnel.descriptor, DrawableBlob.descriptor],
this.distanceFieldFrameBuffer
);

View file

@ -8,10 +8,11 @@ export const settings = {
[0.2, 0.1],
[0.6, 0.1],
[1, 0.3],
[1.25, 0.75],
/*[1.25, 0.75],
[1.5, 1],
[1.75, 1.25],
[1.75, 1.75],
[2, 2],*/
],
startingTargetIndex: 2,
scalingOptions: {
@ -21,13 +22,15 @@ export const settings = {
},
tileMultiplier: 5,
shaderMacros: {
distanceScale: 64,
distanceOffset: 0.0,
edgeSmoothing: 10,
headRadius: 5,
torsoRadius: 10,
footRadius: 4,
},
shaderCombinations: {
lineSteps: [0, 1, 2, 3, 4, 8, 16, 128],
circleLightSteps: [0, 1, 2, 3],
lineSteps: [0, 1, 2, 4, 8, 16, 128],
blobSteps: [0, 1, 2, 8],
circleLightSteps: [0, 1],
pointLightSteps: [0, 1, 2, 3],
},
};

View file

@ -3,10 +3,7 @@
precision mediump float;
#define LINE_COUNT {lineCount}
#define CAVE_COLOR vec3(0.36, 0.38, 0.76)
#define AIR_COLOR vec3(0.7)
#define DISTANCE_SCALE {distanceScale}
#define DISTANCE_OFFSET {distanceOffset}
#define BLOB_COUNT {blobCount}
uniform float maxMinDistance;
@ -17,32 +14,76 @@ uniform float maxMinDistance;
float fromRadius;
float toRadius;
}[LINE_COUNT] lines;
#endif
in vec2 worldCoordinates;
out vec4 fragmentColor;
void main() {
float realDistance = 0.0;
#if LINE_COUNT > 0
float minDistance = maxMinDistance;
void lineMinDistance(vec2 worldCoordinates, inout float minDistance) {
minDistance = maxMinDistance;
for (int i = 0; i < LINE_COUNT; i++) {
vec2 targetFromDelta = worldCoordinates - lines[i].from;
vec2 toFromDelta = lines[i].toFromDelta;
float h = clamp(dot(targetFromDelta, toFromDelta) / dot(toFromDelta, toFromDelta), 0.0, 1.0);
float lineDistance = distance(targetFromDelta, toFromDelta * h) - mix(lines[i].fromRadius, lines[i].toRadius, h);
float h = clamp(
dot(targetFromDelta, toFromDelta) / dot(toFromDelta, toFromDelta),
0.0, 1.0
);
float lineDistance = (
distance(targetFromDelta, toFromDelta * h)
- mix(lines[i].fromRadius, lines[i].toRadius, h)
);
minDistance = min(minDistance, lineDistance);
}
realDistance = -minDistance;
minDistance *= -1.0;
}
#endif
#if BLOB_COUNT > 0
#define headRadius {headRadius}
#define torsoRadius {torsoRadius}
#define footRadius {footRadius}
uniform struct Blob {
vec2 headCenter;
vec2 torsoCenter;
vec2 leftFootCenter;
vec2 rightFootCenter;
}[BLOB_COUNT] blobs;
float circleMinDistance(vec2 worldCoordinates, vec2 circleCenter, float radius) {
return distance(worldCoordinates, circleCenter) - radius;
}
void blobMinDistance(vec2 worldCoordinates, inout float minDistance) {
float k = 1.0;
for (int i = 0; i < BLOB_COUNT; i++) {
float res = exp2(-k * circleMinDistance(worldCoordinates, blobs[i].headCenter, headRadius));
res += exp2(-k * circleMinDistance(worldCoordinates, blobs[i].torsoCenter, torsoRadius));
res += exp2(-k * circleMinDistance(worldCoordinates, blobs[i].leftFootCenter, footRadius));
res += exp2(-k * circleMinDistance(worldCoordinates, blobs[i].rightFootCenter, footRadius));
minDistance = min(minDistance, -log2(res) / k);
}
}
#endif
in vec2 worldCoordinates;
out vec2 fragmentColor;
void main() {
float minDistance = -maxMinDistance;
#if LINE_COUNT > 0
lineMinDistance(worldCoordinates, minDistance);
#endif
fragmentColor = vec4(
mix(CAVE_COLOR, AIR_COLOR, clamp(realDistance, 0.0, 1.0)),
(realDistance + DISTANCE_OFFSET) / DISTANCE_SCALE
#if BLOB_COUNT > 0
blobMinDistance(worldCoordinates, minDistance);
#endif
);
fragmentColor = vec2(minDistance, 0.0);
}

View file

@ -8,21 +8,26 @@ precision mediump float;
#define CIRCLE_LIGHT_COUNT {circleLightCount}
#define POINT_LIGHT_COUNT {pointLightCount}
#define DISTANCE_SCALE {distanceScale}
#define DISTANCE_OFFSET {distanceOffset}
#define EDGE_SMOOTHING {edgeSmoothing}
uniform sampler2D distanceTexture;
uniform vec2 viewBoxSize;
vec3[4] colors = vec3[4](
vec3(0.5),
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
float getDistance(in vec2 target, out vec3 color) {
vec4 values = texture(distanceTexture, target);
color = values.rgb;
return (values.w - DISTANCE_OFFSET) * DISTANCE_SCALE;
color = colors[int(values[1])];
return values[0];
}
float getDistance(in vec2 target) {
return (texture(distanceTexture, target).w - DISTANCE_OFFSET) * DISTANCE_SCALE;
return texture(distanceTexture, target)[0];
}
#if CIRCLE_LIGHT_COUNT > 0
@ -58,10 +63,6 @@ void main() {
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
float lightCenterDistance = distance(circleLights[i].center, worldCoordinates);
/*if (lightCenterDistance < circleLights[i].radius) {
fragmentColor = vec4(1.0, 1.0, 0.0, 1.0);
return;
}*/
vec3 lightColorAtPosition = circleLights[i].value / pow(
lightCenterDistance / LIGHT_DROP + 1.0, 2.0
);
@ -76,7 +77,7 @@ void main() {
rayLength = lightCenterDistance - circleLights[i].radius;
float minDistance = getDistance(uvCoordinates + direction * rayLength);
exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0;
q = min(q, exponentialDecayDistance / rayLength);
q = min(q, minDistance / rayLength);
lighting += lightColorAtPosition * clamp(
q / circleLights[i].radius * (lightCenterDistance + 1.0), 0.0, 1.0
@ -122,5 +123,5 @@ void main() {
}
#endif
fragmentColor = vec4(colorAtPosition * lighting * clamp(startingDistance, 0.0, 1.0), 1.0);
fragmentColor = vec4(colorAtPosition * lighting * step(0.0, startingDistance), 1.0);
}

View file

@ -13,11 +13,10 @@ export class Camera extends GameObject {
private cursorPosition = vec2.create();
private boundingBox: BoundingBox;
constructor(physics: Physics, private light: Lamp) {
constructor() {
super();
this.boundingBox = new BoundingBox(null);
//physics.addDynamicBoundingBox(this.boundingBox);
this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
@ -40,7 +39,6 @@ export class Camera extends GameObject {
private moveTo(c: MoveToCommand) {
this.boundingBox.topLeft = c.position;
this.light.sendCommand(c);
}
private zoom(c: ZoomCommand) {

View file

@ -1,5 +1,4 @@
import { vec2 } from 'gl-matrix';
import { Circle } from '../../shapes/types/circle';
import { KeyDownCommand } from '../../input/commands/key-down';
import { KeyUpCommand } from '../../input/commands/key-up';
import { SwipeCommand } from '../../input/commands/swipe';
@ -10,22 +9,28 @@ import { Physics } from '../../physics/physics';
import { GameObject } from '../game-object';
import { Camera } from './camera';
import { IShape } from '../../shapes/i-shape';
import { rotate90Deg } from '../../helper/rotate-90-deg';
import { max } from 'gl-matrix/src/gl-matrix/vec2';
import { Blob } from '../../shapes/types/blob';
import { RenderCommand } from '../../drawing/commands/render';
import { DrawableBlob } from '../../drawing/drawables/drawable-blob';
import { Lamp } from './lamp';
export class Character extends GameObject {
private keysDown: Set<string> = new Set();
private shape: Circle;
private shape: DrawableBlob;
private static speed = 1.5;
constructor(private physics: Physics, private camera: Camera) {
constructor(
private physics: Physics,
private camera: Camera,
private light: Lamp
) {
super();
this.shape = new Circle();
this.shape.radius = 80;
this.shape = new DrawableBlob(vec2.create());
this.addCommandExecutor(StepCommand, this.stepHandler.bind(this));
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
this.addCommandExecutor(TeleportToCommand, (c) =>
this.setPosition(c.position)
);
@ -38,6 +43,10 @@ export class Character extends GameObject {
});
}
private draw(c: RenderCommand) {
c.renderer.drawShape(this.shape);
}
private tryMoving(delta: vec2, isFirstIteration = true) {
const maxStep = 2;
if (vec2.length(delta) > maxStep) {
@ -95,7 +104,7 @@ export class Character extends GameObject {
}
private getNearShapesTo(
shape: Circle
shape: Blob
): Array<{ shape: IShape; distance: number }> {
return this.physics
.findIntersecting(shape.boundingBox)
@ -108,8 +117,10 @@ export class Character extends GameObject {
}
private setPosition(value: vec2) {
this.shape.center = value;
this.camera.sendCommand(new MoveToCommand(this.shape.center));
this.shape.position = value;
this.camera.sendCommand(new MoveToCommand(value));
vec2.add(value, value, vec2.fromValues(80, 0));
this.light.sendCommand(new MoveToCommand(value));
}
public stepHandler(c: StepCommand) {

View file

@ -17,8 +17,8 @@ export const createCharacter = (
2
);
const camera = new Camera(physics, light);
const character = new Character(physics, camera);
const camera = new Camera();
const character = new Character(physics, camera, light);
objects.addObject(light);
objects.addObject(camera);
objects.addObject(character);

View file

@ -0,0 +1,73 @@
import { vec2 } from 'gl-matrix';
import { IShape } from '../i-shape';
import { BoundingBox } from '../bounding-box';
import { Circle } from './circle';
import { settings } from '../../drawing/settings';
export class Blob implements IShape {
private static readonly boundingCircleRadius = 19;
private static readonly headOffset = vec2.fromValues(0, 15);
private static readonly torsoOffset = vec2.fromValues(0, 0);
private static readonly leftFootOffset = vec2.fromValues(-5, -10);
private static readonly rightFootOffset = vec2.fromValues(5, -10);
public readonly isInverted = false;
protected boundingCircle = new Circle(
vec2.create(),
Blob.boundingCircleRadius
);
protected head = new Circle(vec2.create(), settings.shaderMacros.headRadius);
protected torso = new Circle(
vec2.create(),
settings.shaderMacros.torsoRadius
);
protected leftFoot = new Circle(
vec2.create(),
settings.shaderMacros.footRadius
);
protected rightFoot = new Circle(
vec2.create(),
settings.shaderMacros.footRadius
);
public constructor(center: vec2) {
this.position = center;
}
public set position(value: vec2) {
vec2.copy(this.boundingCircle.center, value);
vec2.add(this.head.center, value, Blob.headOffset);
vec2.add(this.torso.center, value, Blob.torsoOffset);
vec2.add(this.leftFoot.center, value, Blob.leftFootOffset);
vec2.add(this.rightFoot.center, value, Blob.rightFootOffset);
}
public get center(): vec2 {
return this.boundingCircle.center;
}
public get radius(): number {
return this.boundingCircle.radius;
}
public distance(target: vec2): number {
return this.boundingCircle.distance(target);
}
public normal(from: vec2): vec2 {
return this.boundingCircle.normal(from);
}
public get boundingBox(): BoundingBox {
return this.boundingCircle.boundingBox;
}
public isInside(target: vec2): boolean {
return this.distance(target) < 0;
}
public clone(): Blob {
return new Blob(this.boundingCircle.center);
}
}