Files
memory-manager-concurrent/games/3dcity/src/city3d/Pool.js
2026-01-21 18:11:33 +08:00

544 lines
13 KiB
JavaScript

import * as THREE from '../../build/three.module.js'
import { GLTFLoader } from '../jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from '../jsm/loaders/DRACOLoader.js';
import { RGBELoader } from '../jsm/loaders/RGBELoader.js';
export class Pool {
constructor( callback, tileSize=64, normal=true, roughness=false, pixel=false, env=true ) {
this.sky = 'day';
this.callback = callback;
this.tileSize = tileSize
this.isWithNormal = normal
this.isWithRoughness = roughness
this.isPixelStyle = pixel
this.isWithEnv = env
this.loaderGLB = new GLTFLoader();
let dracoLoader = new DRACOLoader().setDecoderPath( './build/draco/' )
this.loaderGLB.setDRACOLoader( dracoLoader )
this.mapPath = './assets/textures/'
this.modelPath = './assets/models/'
this.modelSrc = [ 'cars', 'world' ];
this.imgSrc = ['tiles.png','town.png','building.png', 'cars.png' ];
if( this.isWithNormal ) this.imgSrc.push( 'tiles_n.png', 'building_n.png', 'town_n.png' )
if( this.isWithRoughness ) this.imgSrc.push( 'tiles_r.png', 'building_r.png', 'town_r.png' )
this.imgSrc.push( 'border.jpg', 'border_a.jpg' )
this.imgs = [];
this.num = 0;
this.tiles = {
normal:[],
roughness:[],
texture:[],
}
this.color = {
ground:'#c68564',
normal:'#8080ff',
snow:'#e6f0ff',
white:'#ffffff',
lightGrey:'#CCCCCC',
metal:'#AAAAAA',
sky:'#8397ac',
}
this.textures = {}
this.geos = {}
if (this.isWithEnv) this.loadEnvmap()
else this.loadImages()
}
displayMessage( str ){
if( hub ) hub.message( str )
}
//----------------------------------- ENVMAP
loadEnvmap() {
this.displayMessage( 'Loading envmap ...' )
new RGBELoader().load(
this.mapPath + this.sky + '.hdr',
function ( texture ) {
this.env = texture;
this.loadImages();
}.bind(this),
undefined,
function ( err ) {
console.warn('[3dcity] Failed to load envmap, continuing without it:', err);
this.env = null;
this.loadImages();
}.bind(this)
);
}
//----------------------------------- TEXTURES
loadImages() {
this.displayMessage( 'Loading images ...' )
let n = this.num;
let url = this.imgSrc[n]
let name = url.substring( url.lastIndexOf('/')+1, url.lastIndexOf('.') );
this.imgs[name] = new Image();
this.imgs[name].onload = function(){
this.num++;
if( this.num === this.imgSrc.length ) this.defineCanvas();
else this.loadImages();
}.bind(this);
this.imgs[name].onerror = function(err){
console.warn('[3dcity] Failed to load image:', url, err);
this.num++;
if( this.num === this.imgSrc.length ) this.defineCanvas();
else this.loadImages();
}.bind(this);
this.imgs[name].src = this.mapPath + url;
}
defineCanvas() {
this.num = 0;
this.canvas = {
town: this.makeCanvas( 'town' ),
building: this.makeCanvas( 'building' ),
tiles: this.makeCanvas( 'tiles', true ),
}
if( this.isWithNormal ) this.canvas[ 'tiles_n' ] = this.makeCanvas( 'tiles_n', true )
if( this.isWithRoughness ) {
this.canvas[ 'tiles_r' ] = this.makeCanvas( 'tiles_r', true )
this.canvas[ 'town_r' ] = this.makeCanvas( 'town_r' )
this.canvas[ 'building_r' ] = this.makeCanvas( 'building_r' )
}
this.drawCanvas()
this.makeCarColor()
}
makeCanvas( name, resize ) {
let r = 1;
if (resize) {
if (this.tileSize === 32) r = 0.5;
else if (this.tileSize === 16) r = 0.25;
}
let img = this.imgs[name];
let c = document.createElement("canvas")
c.width = img.width*r
c.height = img.height*r
return c
}
drawCanvas() {
let c, ctx;
// TODO add color effect on canvas
for( let name in this.canvas ){
c = this.canvas[name];
ctx = c.getContext('2d');
ctx.clearRect ( 0 , 0, c.width, c.height );
if( name === 'tiles' || name === 'town' || name === 'building'){
ctx.fillStyle = this.color.ground;
ctx.fillRect( 0, 0, c.width, c.height )
}
if( name === 'tiles_n' ){
ctx.fillStyle = this.color.normal;
ctx.fillRect( 0, 0, c.width, c.height )
}
if( name === 'tiles_r' || name === 'town_r' || name === 'building_r'){
ctx.fillStyle = this.color.lightGrey;
ctx.fillRect( 0, 0, c.width, c.height )
}
ctx.drawImage( this.imgs[ name ], 0, 0, c.width, c.height );
}
this.defineTextures()
}
//this.tint( this.townCanvas, this.imgs[1], this.imgs[4] );
//this.tint( this.buildingCanvas, this.imgs[2], this.imgs[3] );
defineTextures() {
this.makePixelData( 'tiles' )
this.textures['town'] = new THREE.Texture( this.canvas.town );
this.filterTexture( this.textures['town'], { flip:false } )
this.textures['building'] = new THREE.Texture( this.canvas.building );
this.filterTexture( this.textures['building'], { flip:false } )
if( this.isWithNormal ){
this.makePixelData( 'tiles_n' )
this.textures['town_n'] = new THREE.Texture( this.imgs['town_n'] );
this.filterTexture( this.textures['town_n'], { flip:false, normal:true } )
this.textures['building_n'] = new THREE.Texture( this.imgs['building_n'] );
this.filterTexture( this.textures['building_n'], { flip:false, normal:true } )
}
if( this.isWithRoughness ){
this.makePixelData( 'tiles_r' )
this.textures['town_r'] = new THREE.Texture( this.canvas.town_r );
this.filterTexture( this.textures['town_r'], { flip:false, normal:true } )
this.textures['building_r'] = new THREE.Texture( this.canvas.building_r );
this.filterTexture( this.textures['building_r'], { flip:false, normal:true } )
}
this.textures['border'] = new THREE.Texture( this.imgs['border'] );
this.filterTexture( this.textures['border'], { flip:false } )
this.textures['border_a'] = new THREE.Texture( this.imgs['border_a'] );
this.filterTexture( this.textures['border_a'], { flip:false } )
this.loadModel()
}
filterTexture ( texture, o = {} ){
if( !o.normal ) texture.encoding = THREE.sRGBEncoding
if( o.flip !== undefined ) texture.flipY = o.flip
if( o.midmap !== undefined ) texture.generateMipmaps = o.midmap;
if( o.alpha !== undefined ) texture.premultiplyAlpha = o.alpha;
if( this.isPixelStyle ){
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
} else {
texture.magFilter = THREE.LinearFilter;
texture.minFilter = THREE.LinearMipmapLinearFilter;
}
//texture.anisotropy = this.anisotropy;
texture.needsUpdate = true;
}
makePixelData( name ) {
let ctx = this.canvas[ name ].getContext('2d')
let pix = this.tileSize, x, y
for ( let i = 0; i < 240; i++ ){
x = ( i % 32 ) * pix;
y = Math.floor( i / 32 ) * pix;
let data = ctx.getImageData(x, y, pix, pix).data;
if ( name === 'tiles_n' ) this.tiles.normal[i] = new THREE.DataTexture( data, pix, pix );
else if ( name === 'tiles_r' ) this.tiles.roughness[i] = new THREE.DataTexture( data, pix, pix );
else this.tiles.texture[i] = new THREE.DataTexture( data, pix, pix );
}
}
tint( canvas, image, supImage ) {
let data, i, n;
let pixels = canvas.width*canvas.height;
let ctx = canvas.getContext('2d');
// draw windows
let topData = null;
let newImg = null;
if(supImage && this.dayTime!==0 && this.dayTime!==1){
ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
ctx.drawImage(supImage, 0, 0);
topData = ctx.getImageData(0, 0, canvas.width, canvas.height);
data = topData.data;
i = pixels;
while(i--){
n = i<<2;
if(data[n+3] !== 0){
if(data[n+0]==0 && data[n+1]==0 && data[n+2]==0){// black
data[n+3]=60;
}
if(data[n+1]==0){
//if(data[n+0]==255 && data[n+1]==0 && data[n+2]==0){// red
if(this.dayTime==3) data[n+1]=255;
if(this.dayTime==2) {data[n+0]=0; data[n+3]=60;}
}
}
}
ctx.putImageData(topData, 0, 0);
newImg = document.createElement('img');
newImg.src = canvas.toDataURL("image/png");
}
if(image){
ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
ctx.drawImage(image, 0, 0);
} else {
ctx.drawImage(this.skyCanvasBasic, 0, 0);
}
if( this.dayTime!==0 ){
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
data = imageData.data;
i = pixels;
let c = this.tcolor;
while(i--){
n = i<<2;//i*4;
data[n+0] = data[n+0] * (1-c.a) + (c.r*c.a);
data[n+1] = data[n+1] * (1-c.a) + (c.g*c.a);
data[n+2] = data[n+2] * (1-c.a) + (c.b*c.a);
}
ctx.putImageData(imageData, 0, 0);
if(newImg){
ctx.drawImage(newImg, 0, 0);
}
}
}
//----------------------------------- TITLE
rand ( low, high ) { return low + Math.random() * ( high - low ); }
makeTitleTexture ( n = 0 ) {
let color = [ this.color.metal, '#fff' ]
if(n===1) color = [ '#333333', '#999999' ]
if(n===2) color = [ '#000', '#999999' ]
let s = 0.25
let c = document.createElement( 'canvas' );
c.width = c.height = 1024*s;
let ctx = c.getContext('2d');
ctx.beginPath();
ctx.fillStyle = color[0];
ctx.rect(0, 0, 1024*s, 1024*s);
ctx.fill();
let i = 8, r1, r2
while(i--){
r1 = this.rand( 150, 255 )
r2 = this.rand( r1-60, r1-20 )
ctx.beginPath();
ctx.fillStyle = n!==1 ? 'rgb('+r1+','+r1+','+r2+')': color[1];
ctx.rect( i*146*s, 0, 146*s, 200*s);
ctx.fill();
}
let t = new THREE.Texture( c )
this.filterTexture( t, { flip:false } )
return t;
}
//----------------------------------- CARS
makeCarColor () {
let c = document.createElement( 'canvas' );
c.width = c.height = 1024;
let ctx = c.getContext('2d');
let i, n=0, j=0, k = 3;
while(k--){
ctx.clearRect ( 0 , 0, c.width, c.height );
for( i=0; i<16; i++ ){
ctx.beginPath();
if(i!==11 && i!==15) ctx.fillStyle = this.carColor();
ctx.rect(n*256, j*256, 256, 256);
ctx.fill();
n++
if(n==4){ n=0; j++; }
}
ctx.drawImage( this.imgs.cars, 0, 0 );
let name = 'cars_' + k
this.textures[name] = new THREE.Texture( c );
this.filterTexture( this.textures[name], { flip:false } )
}
}
carColor () {
let carcolors = [
[0xFFFFFF, 0xD0D1D3, 0XEFEFEF, 0xEEEEEE],//white
[0x252122, 0x302A2B, 0x27362B, 0x2F312B],//black
[0x8D9495, 0xC1C0BC, 0xCED4D4, 0xBEC4C4],//silver
[0x939599, 0x424242, 0x5A5A5A, 0x747675],//gray
[0xC44920, 0xFF4421, 0x600309, 0xD9141E],//red
[0x4AD1FB, 0x275A63, 0x118DDC, 0x2994A6],//blue
[0xA67936, 0x874921, 0xD7A56B, 0x550007],//brown
[0x5FF12C, 0x188047, 0x8DAE29, 0x1AB619],//green
[0xFFF10A, 0xFFFFBD, 0xFCFADF, 0xFFBD0A],//yellow/gold
[0xB92968, 0x5C1A4F, 0x001255, 0xFFB7E7]//other
];
let l = this.randInt(0,9), n = this.randInt(0,3);
let base = carcolors[l][n];
let resl = base.toString(16);
if(resl.length<6) resl = '#0'+resl;
else resl = '#'+resl;
return resl;
}
randInt( low, high ) { return low + Math.floor( Math.random() * ( high - low + 1 ) ); }
tile( type, id ) {
return this.tiles[type][id];
}
texture( name ) {
return this.textures[name];
}
//----------------------------------- 3D MODEL
loadModel() {
this.displayMessage( 'Loading 3d model ...' )
let n = this.num;
let name = this.modelSrc[n]
this.loaderGLB.load( this.modelPath + name + '.glb', function ( gltf ) {
let o = {}, b1, b2, t;
gltf.scene.traverse( function ( node ) {
if( node.name === 'title' ) t = node;
if( node.name === 'border' ) b1 = node;
if( node.name === 'border_min' ) b2 = node;
if( node.isMesh && !o[node.name] ) o[node.name] = node.geometry;
})
if(b1) this.border = b1;
if(b2) this.border_min = b2;
if(t) this.title = t;
this.defineGeometry( o, name )
this.num++;
if( this.num === this.modelSrc.length ){
this.displayMessage( '...' )
this.callback()
} else {
this.loadModel()
}
}.bind(this))
}
defineGeometry ( o, name ){
let g, n;
switch( name ){
case 'cars':
g = { cars:[] }
for( let c in o ){
n = Number( c.substring(4) )
g.cars[n] = o[c]
}
break;
case 'world':
g = {
town:[
null, null, null, null,
o.police, o.park_1, o.park_2, o.fire,
o.coal, o.nuclear, o.port, o.stadium, o.airport
],
tree:[
o.ttt3, o.ttt3, o.ttt4, o.ttt4,
o.ttt0, o.ttt1, o.ttt2, o.ttt5
],
sprite:[
o.train, o.elico.clone(), o.plane.clone()
],
residential:[],
commercial:[],
industrial:[],
house:[]
}
// BASIC
let i = 9;
while(i--) g.industrial[i] = o['i_0'+i]
i = 19;
while(i--) g.residential[i] = i<10 ? o['r_0'+i] : o['r_'+i]
i = 21;
while(i--) g.commercial[i] = i<10 ? o['c_0'+i] : o['c_'+i]
i = 12;
while(i--) g.house[i] = i<10 ? o['rh_0'+i] : o['rh_'+i]
break;
}
// ADD TO GEOS POOL
this.geos = { ...this.geos, ...g }
}
geo ( type, id ){
return this.geos[type][id] || null;
}
}