Finalize new lowres tiles implementation

This commit is contained in:
Lukas Rieger (Blue) 2022-08-12 18:06:41 +02:00
parent d12c48275f
commit d9ffe6453f
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
10 changed files with 192 additions and 86 deletions

View File

@ -27,7 +27,7 @@ import {Map} from "./map/Map";
import {SkyboxScene} from "./skybox/SkyboxScene";
import {ControlsManager} from "./controls/ControlsManager";
import Stats from "./util/Stats";
import {alert, dispatchEvent, elementOffset, generateCacheHash, htmlToElement} from "./util/Utils";
import {alert, dispatchEvent, elementOffset, generateCacheHash, htmlToElement, softClamp} from "./util/Utils";
import {TileManager} from "./map/TileManager";
import {HIRES_VERTEX_SHADER} from "./map/hires/HiresVertexShader";
import {HIRES_FRAGMENT_SHADER} from "./map/hires/HiresFragmentShader";
@ -177,7 +177,14 @@ export class MapViewer {
this.raycaster.setFromCamera(normalizedScreenPos, this.camera);
// check Object3D interactions
let intersects = this.raycaster.intersectObjects([this.map.hiresTileManager.scene, this.map.lowresTileManager.scene, this.markers], true);
/*
const intersectScenes = [this.map.hiresTileManager.scene, this.markers];
for (let i = 0; i < this.map.lowresTileManager.length; i++) {
intersectScenes.push(this.map.lowresTileManager[i].scene);
}
*/
let intersects = this.raycaster.intersectObjects([this.map.hiresTileManager.scene, this.markers], true);
let hit = null;
let lowresHit = null;
let hiresHit = null;
@ -201,9 +208,11 @@ export class MapViewer {
let parentRoot = object;
while(parentRoot.parent) parentRoot = parentRoot.parent;
/*
if (parentRoot === this.map.lowresTileManager.scene) {
if (!lowresHit) lowresHit = intersects[i];
}
*/
if (parentRoot === this.map.hiresTileManager.scene) {
if (!hiresHit) hiresHit = intersects[i];
@ -216,9 +225,9 @@ export class MapViewer {
})) return;
}
if (parentRoot !== this.map.lowresTileManager.scene) {
//if (parentRoot !== this.map.lowresTileManager.scene) {
covered = true;
}
//}
}
}
}
@ -288,8 +297,13 @@ export class MapViewer {
//update uniforms
this.data.uniforms.hiresTileMap.value.pos.copy(this.map.hiresTileManager.centerTile);
this.renderer.render(this.map.lowresTileManager.scene, this.camera);
this.renderer.clearDepth();
const highestLod = this.map.lowresTileManager.length - 1;
for (let i = this.map.lowresTileManager.length - 1; i >= 0; i--) {
if (i === highestLod || this.controlsManager.distance < 1000 * Math.pow(this.map.data.lowres.lodFactor, i + 1)) {
this.renderer.render(this.map.lowresTileManager[i].scene, this.camera);
this.renderer.clearDepth();
}
}
if (this.controlsManager.distance < 1000) {
this.renderer.render(this.map.hiresTileManager.scene, this.camera);
@ -363,7 +377,9 @@ export class MapViewer {
this.tileCacheHash = newTileCacheHash;
if (this.map) {
this.map.lowresTileManager.tileLoader.tileCacheHash = this.tileCacheHash;
for (let i = 0; i < this.map.lowresTileManager.length; i++) {
this.map.lowresTileManager[i].tileLoader.tileCacheHash = this.tileCacheHash;
}
this.map.hiresTileManager.tileLoader.tileCacheHash = this.tileCacheHash;
}
}

View File

@ -83,6 +83,9 @@ export class MapControls {
this.lastTap = -1;
this.lastTapCenter = null;
this.minDistance = 5;
this.maxDistance = 100000;
}
/**
@ -164,7 +167,7 @@ export class MapControls {
this.keyZoom.update(delta, map);
this.touchZoom.update(delta, map);
this.manager.distance = softClamp(this.manager.distance, 5, 10000, 0.8);
this.manager.distance = softClamp(this.manager.distance, this.minDistance, this.maxDistance, 0.8);
// max angle for current distance
let maxAngleForZoom = this.getMaxPerspectiveAngleForDistance(this.manager.distance);

View File

@ -33,34 +33,37 @@ import {
NearestFilter,
ClampToEdgeWrapping,
NearestMipMapLinearFilter,
RepeatWrapping,
Vector2
} from "three";
export class LowresTileLoader {
constructor(tilePath, vertexShader, fragmentShader, tileCacheHash = 0, layer = 0) {
constructor(tilePath, tileSettings, lod, vertexShader, fragmentShader, uniforms, tileCacheHash = 0, layer = 0) {
Object.defineProperty( this, 'isLowresTileLoader', { value: true } );
this.tilePath = tilePath;
this.tileSettings = tileSettings;
this.lod = lod;
this.layer = layer;
this.tileCacheHash = tileCacheHash;
this.vertexShader = vertexShader;
this.fragmentShader = fragmentShader;
this.uniforms = uniforms;
this.textureLoader = new TextureLoader();
this.geometry = new PlaneBufferGeometry(
500, 500,
100, 100
tileSettings.tileSize.x + 1, tileSettings.tileSize.z + 1,
Math.ceil(100 / (lod * 2)), Math.ceil(100 / (lod * 2))
);
this.geometry.deleteAttribute('normal');
this.geometry.deleteAttribute('uv');
this.geometry.rotateX(-Math.PI / 2);
this.geometry.translate(250, 0, 250);
this.geometry.translate(tileSettings.tileSize.x / 2 + 1, 0, tileSettings.tileSize.x / 2 + 1);
}
load = (tileX, tileZ) => {
let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.png';
let tileUrl = this.tilePath + this.lod + "/" + pathFromCoords(tileX, tileZ) + '.png';
return new Promise((resolve, reject) => {
this.textureLoader.load(tileUrl + '?' + this.tileCacheHash,
@ -76,6 +79,13 @@ export class LowresTileLoader {
let material = new ShaderMaterial({
uniforms: {
...this.uniforms,
tileSize: {
value: new Vector2(this.tileSettings.tileSize.x, this.tileSettings.tileSize.z)
},
textureSize: {
value: new Vector2(texture.image.width, texture.image.height)
},
textureImage: {
type: 't',
value: texture
@ -94,13 +104,12 @@ export class LowresTileLoader {
let object = new Mesh(this.geometry, material);
if (this.layer) object.layers.set(this.layer);
let tileSize = {x:500, z:500};
let translate = {x:0, z:0};
let scale = {x:1, z:1};
object.position.set(tileX * tileSize.x + translate.x, 0, tileZ * tileSize.z + translate.z);
object.scale.set(scale.x, 1, scale.z);
const scale = Math.pow(this.tileSettings.lodFactor, this.lod - 1);
object.position.set(tileX * this.tileSettings.tileSize.x * scale, 0, tileZ * this.tileSettings.tileSize.z * scale);
object.scale.set(scale, 1, scale);
object.userData.tileUrl = tileUrl;
object.userData.tileType = "lowres";
object.updateMatrixWorld(true);

View File

@ -36,7 +36,7 @@ import {
Vector3,
VertexColors
} from "three";
import {alert, dispatchEvent, generateCacheHash, hashTile, stringToImage, vecArrToObj} from "../util/Utils";
import {alert, dispatchEvent, generateCacheHash, getPixel, hashTile, stringToImage, vecArrToObj} from "../util/Utils";
import {TileManager} from "./TileManager";
import {TileLoader} from "./TileLoader";
import {LowresTileLoader} from "./LowresTileLoader";
@ -70,8 +70,8 @@ export class Map {
},
lowres: {
tileSize: {x: 32, z: 32},
scale: {x: 1, z: 1},
translate: {x: 2, z: 2}
lodFactor: 5,
lodCount: 3
}
};
@ -86,7 +86,7 @@ export class Map {
/** @type {TileManager} */
this.hiresTileManager = null;
/** @type {TileManager} */
/** @type {TileManager[]} */
this.lowresTileManager = null;
}
@ -116,10 +116,13 @@ export class Map {
this.hiresMaterial = this.createHiresMaterial(hiresVertexShader, hiresFragmentShader, uniforms, textures);
this.hiresTileManager = new TileManager(new Scene(), new TileLoader(`${this.data.dataUrl}tiles/0/`, this.hiresMaterial, this.data.hires, tileCacheHash), this.onTileLoad("hires"), this.onTileUnload("hires"), this.events);
this.lowresTileManager = new TileManager(new Scene(), new LowresTileLoader(`${this.data.dataUrl}tiles/1/`, lowresVertexShader, lowresFragmentShader, tileCacheHash), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events);
this.hiresTileManager.scene.autoUpdate = false;
this.hiresTileManager.scene.autoUpdate = false;
this.lowresTileManager.scene.autoUpdate = false;
this.lowresTileManager = [];
for (let i = 0; i < this.data.lowres.lodCount; i++) {
this.lowresTileManager[i] = new TileManager(new Scene(), new LowresTileLoader(`${this.data.dataUrl}tiles/`, this.data.lowres, i + 1, lowresVertexShader, lowresFragmentShader, uniforms, tileCacheHash), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events);
this.lowresTileManager[i].scene.autoUpdate = false;
}
alert(this.events, `Map '${this.data.id}' is loaded.`, "fine");
});
@ -158,8 +161,8 @@ export class Map {
};
this.data.lowres = {
tileSize: {...this.data.lowres.tileSize, ...vecArrToObj(worldSettings.lowres.tileSize, true)},
scale: {...this.data.lowres.scale, ...vecArrToObj(worldSettings.lowres.scale, true)},
translate: {...this.data.lowres.translate, ...vecArrToObj(worldSettings.lowres.translate, true)}
lodFactor: worldSettings.lowres.lodFactor !== undefined ? worldSettings.lowres.lodFactor : this.data.lowres.lodFactor,
lodCount: worldSettings.lowres.lodCount !== undefined ? worldSettings.lowres.lodCount : this.data.lowres.lodCount
};
alert(this.events, `Settings for map '${this.data.id}' loaded.`, "fine");
@ -189,18 +192,21 @@ export class Map {
loadMapArea(x, z, hiresViewDistance, lowresViewDistance) {
if (!this.isLoaded) return;
let hiresX = Math.floor((x - this.data.hires.translate.x) / this.data.hires.tileSize.x);
let hiresZ = Math.floor((z - this.data.hires.translate.z) / this.data.hires.tileSize.z);
let hiresViewX = Math.floor(hiresViewDistance / this.data.hires.tileSize.x);
let hiresViewZ = Math.floor(hiresViewDistance / this.data.hires.tileSize.z);
let lowresX = Math.floor((x - this.data.lowres.translate.x) / this.data.lowres.tileSize.x);
let lowresZ = Math.floor((z - this.data.lowres.translate.z) / this.data.lowres.tileSize.z);
let lowresViewX = Math.floor(lowresViewDistance / this.data.lowres.tileSize.x);
let lowresViewZ = Math.floor(lowresViewDistance / this.data.lowres.tileSize.z);
const hiresX = Math.floor((x - this.data.hires.translate.x) / this.data.hires.tileSize.x);
const hiresZ = Math.floor((z - this.data.hires.translate.z) / this.data.hires.tileSize.z);
const hiresViewX = Math.floor(hiresViewDistance / this.data.hires.tileSize.x);
const hiresViewZ = Math.floor(hiresViewDistance / this.data.hires.tileSize.z);
this.hiresTileManager.loadAroundTile(hiresX, hiresZ, hiresViewX, hiresViewZ);
this.lowresTileManager.loadAroundTile(lowresX, lowresZ, lowresViewX, lowresViewZ);
for (let i = 0; i < this.lowresTileManager.length; i++) {
const lod = i + 1;
const scale = Math.pow(this.data.lowres.lodFactor, lod - 1);
const lowresX = Math.floor(x / (this.data.lowres.tileSize.x * scale));
const lowresZ = Math.floor(z / (this.data.lowres.tileSize.z * scale));
const lowresViewX = Math.floor(lowresViewDistance / this.data.lowres.tileSize.x);
const lowresViewZ = Math.floor(lowresViewDistance / this.data.lowres.tileSize.z);
this.lowresTileManager[i].loadAroundTile(lowresX, lowresZ, lowresViewX, lowresViewZ);
}
}
/**
@ -332,8 +338,12 @@ export class Map {
if (this.hiresTileManager) this.hiresTileManager.unload();
this.hiresTileManager = null;
if (this.lowresTileManager) this.lowresTileManager.unload();
this.lowresTileManager = null;
if (this.lowresTileManager) {
for (let i = 0; i < this.lowresTileManager.length; i++) {
this.lowresTileManager[i].unload();
}
this.lowresTileManager = null;
}
if (this.hiresMaterial) this.hiresMaterial.forEach(material => material.dispose());
this.hiresMaterial = null;
@ -364,23 +374,40 @@ export class Map {
let hiresTileHash = hashTile(Math.floor((x - this.data.hires.translate.x) / this.data.hires.tileSize.x), Math.floor((z - this.data.hires.translate.z) / this.data.hires.tileSize.z));
let tile = this.hiresTileManager.tiles.get(hiresTileHash);
if (!tile || !tile.model) {
let lowresTileHash = hashTile(Math.floor((x - this.data.lowres.translate.x) / this.data.lowres.tileSize.x), Math.floor((z - this.data.lowres.translate.z) / this.data.lowres.tileSize.z));
tile = this.lowresTileManager.tiles.get(lowresTileHash);
}
if (!tile || !tile.model){
return false;
}
try {
let intersects = this.raycaster.intersectObjects([tile.model]);
if (intersects.length > 0) {
return intersects[0].point.y;
if (tile?.model) {
try {
let intersects = this.raycaster.intersectObjects([tile.model]);
if (intersects.length > 0) {
return intersects[0].point.y;
}
} catch (ignore) {
//empty
}
} catch (err) {
return false;
}
for (let i = 0; i < this.lowresTileManager.length; i++) {
const lod = i + 1;
const scale = Math.pow(this.data.lowres.lodFactor, lod - 1);
const scaledTileSize = {
x: this.data.lowres.tileSize.x * scale,
z: this.data.lowres.tileSize.z * scale
}
const tileX = Math.floor(x / scaledTileSize.x);
const tileZ = Math.floor(z / scaledTileSize.z);
let lowresTileHash = hashTile(tileX, tileZ);
tile = this.lowresTileManager[i].tiles.get(lowresTileHash);
if (!tile || !tile.model) continue;
const texture = tile.model.material.uniforms?.textureImage?.value?.image;
if (texture == null) continue;
const color = getPixel(texture, x - tileX * scaledTileSize.x, z - tileZ * scaledTileSize.z + this.data.lowres.tileSize.z);
return color[2];
}
return false;
}
dispose() {

View File

@ -60,7 +60,7 @@ export class Tile {
return tileLoader.load(this.x, this.z)
.then(model => {
if (this.unloaded){
model.geometry.dispose();
Tile.disposeModel(model);
return;
}
@ -77,11 +77,23 @@ export class Tile {
if (this.model) {
this.onUnload(this);
this.model.geometry.dispose();
Tile.disposeModel(this.model);
this.model = null;
}
}
static disposeModel(model) {
if (model.userData?.tileType === "hires") {
model.geometry.dispose();
}
else if (model.userData?.tileType === "lowres") {
model.material.uniforms.textureImage.value.dispose();
model.material.dispose();
}
}
/**
* @returns {boolean}
*/

View File

@ -79,6 +79,7 @@ export class TileLoader {
object.scale.set(scale.x, 1, scale.z);
object.userData.tileUrl = tileUrl;
object.userData.tileType = "hires";
object.updateMatrixWorld(true);

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import {ClampToEdgeWrapping, LinearFilter, Texture} from "three";
import {ClampToEdgeWrapping, LinearFilter, NearestFilter, Texture} from "three";
export class TileMap {

View File

@ -39,41 +39,59 @@ struct TileMap {
vec2 pos;
};
//uniform float sunlightStrength;
//uniform float ambientLight;
//uniform TileMap hiresTileMap;
uniform float sunlightStrength;
uniform float ambientLight;
uniform TileMap hiresTileMap;
uniform sampler2D textureImage;
uniform vec2 tileSize;
uniform vec2 textureSize;
varying vec3 vPosition;
varying vec3 vWorldPosition;
varying vec3 vNormal;
varying vec2 vUv;
varying vec3 vColor;
varying float vDistance;
float metaToHeight(vec4 meta) {
float heightUnsigned = meta.g * 65280.0 + meta.b * 255.0;
if (heightUnsigned >= 32768.0) {
return -(65535.0 - heightUnsigned);
} else {
return heightUnsigned;
}
}
float metaToLight(vec4 meta) {
return meta.r * 255.0;
}
vec2 posToColorUV(vec2 pos) {
return vec2(pos.x / textureSize.x, min(pos.y, tileSize.y) / textureSize.y);
}
vec2 posToMetaUV(vec2 pos) {
return vec2(pos.x / textureSize.x, pos.y / textureSize.y + 0.5);
}
void main() {
//discard if hires tile is loaded at that position
//if (vDistance < 1900.0 && texture(hiresTileMap.map, ((vWorldPosition.xz - hiresTileMap.translate) / hiresTileMap.scale - hiresTileMap.pos) / hiresTileMap.size + 0.5).r >= 1.0) discard;
//if (vDistance < 900.0 && texture(hiresTileMap.map, ((vWorldPosition.xz - hiresTileMap.translate) / hiresTileMap.scale - hiresTileMap.pos) / hiresTileMap.size + 0.5).r > 0.75) discard;
//vec4 color = vec4(vColor, 1.0);
//float diff = max(dot(vNormal, vec3(0.3637, 0.7274, 0.5819)), 0.0) * 0.3 + 0.7;
//color *= diff;
//color *= mix(sunlightStrength, 1.0, ambientLight);
//vec4 color = vec4(0.3637, 0.7274, 0.5819, 1.0);
vec4 color = texture(textureImage, vec2(vPosition.x / 500.0, vPosition.z / 1000.0));
vec4 color = texture(textureImage, posToColorUV(vPosition.xz));
vec4 meta = texture(textureImage, posToMetaUV(vPosition.xz));
float height = texture(textureImage, vec2(vPosition.x / 500.0, (vPosition.z + 500.0) / 1000.0)).b * 255.0;
float heightX = texture(textureImage, vec2((vPosition.x + 1.0) / 500.0, (vPosition.z + 500.0) / 1000.0)).b * 255.0;
float heightZ = texture(textureImage, vec2(vPosition.x / 500.0, (vPosition.z + 501.0) / 1000.0)).b * 255.0;
float height = metaToHeight(meta);
float heightX = metaToHeight(texture(textureImage, posToMetaUV(vPosition.xz + vec2(1.0, 0.0))));
float heightZ = metaToHeight(texture(textureImage, posToMetaUV(vPosition.xz + vec2(0.0, 1.0))));
float diff = (height - heightX) + (height - heightZ);
color.rgb += clamp(diff * 0.04, -0.2, 0.04);
float blockLight = metaToLight(meta);
float light = mix(blockLight, 15.0, sunlightStrength);
color.rgb *= mix(ambientLight, 1.0, light / 15.0);
gl_FragColor = color;
${ShaderChunk.logdepthbuf_fragment}
}
`;

View File

@ -29,31 +29,41 @@ export const LOWRES_VERTEX_SHADER = `
${ShaderChunk.logdepthbuf_pars_vertex}
uniform sampler2D textureImage;
uniform vec2 tileSize;
uniform vec2 textureSize;
varying vec3 vPosition;
varying vec3 vWorldPosition;
varying vec3 vNormal;
varying vec2 vUv;
varying vec3 vColor;
varying float vDistance;
float metaToHeight(vec4 meta) {
float heightUnsigned = meta.g * 65280.0 + meta.b * 255.0;
if (heightUnsigned >= 32768.0) {
return -(65535.0 - heightUnsigned);
} else {
return heightUnsigned;
}
}
vec2 posToMetaUV(vec2 pos) {
return vec2(pos.x / textureSize.x, pos.y / textureSize.y + 0.5);
}
void main() {
vPosition = position;
vec4 color = texture(textureImage, vec2(position.x / 500.0, (position.z + 500.0) / 1000.0));
vPosition.y += color.b * 255.0;
vec4 meta = texture(textureImage, posToMetaUV(position.xz));
vPosition.y += metaToHeight(meta);
vec4 worldPos = modelMatrix * vec4(vPosition, 1);
vec4 viewPos = viewMatrix * worldPos;
vWorldPosition = worldPos.xyz;
//vNormal = normal;
//vUv = uv;
//vColor = color;
vDistance = -viewPos.z;
gl_Position = projectionMatrix * viewPos;
${ShaderChunk.logdepthbuf_vertex}
}
`;

View File

@ -380,3 +380,13 @@ export const vecArrToObj = (val, useZ = false) => {
}
return {};
}
const pixel = document.createElement('canvas');
pixel.width = 1;
pixel.height = 1;
const pixelContext = pixel.getContext('2d');
export const getPixel = (img, x, y) => {
pixelContext.drawImage(img, x, y, 1, 1, 0, 0, 1, 1);
return pixelContext.getImageData(0, 0, 1, 1).data;
}