Add blob and fix some issues
This commit is contained in:
parent
5b4e67cbf0
commit
edca93fcfe
13 changed files with 237 additions and 67 deletions
26
frontend/src/scripts/drawing/drawables/drawable-blob.ts
Normal file
26
frontend/src/scripts/drawing/drawables/drawable-blob.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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'
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
73
frontend/src/scripts/shapes/types/blob.ts
Normal file
73
frontend/src/scripts/shapes/types/blob.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue