Created
March 7, 2022 09:22
-
-
Save nrkn/9a71bc5255833265e4e5d827e61a2560 to your computer and use it in GitHub Desktop.
Kowloon Walled City Pixel Mapper for Scapeshop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const firstFloor = 0 | |
const maxFloors = 12 | |
const groundDensity = 0.7 | |
const roofDensity = 0.8 | |
export const pixelMapper = ({ r, g, b }) => { | |
if (r === 0 && g === 0 && b === 0) return [] | |
const floor = r / 255 | |
const middleDensity = g / 255 | |
const height = b / 255 | |
let floors = Math.floor(height * maxFloors) + 1 | |
const voxels = [] | |
for (let i = 0; i < floors; i++) { | |
const h = i + firstFloor | |
const isBase = h === 0 | |
const isGroundFloor = h > 0 && h <= 2 | |
const isRoof = i === floors - 1 | |
const isMiddle = h > 2 && !isRoof | |
if (isBase) { | |
if (floor > 0) { | |
voxels.push({ t: 15, h: 0 }) | |
} | |
continue | |
} | |
const district = Math.floor(height * (colorSchemes.length - 1)) | |
const t = getColor(district) | |
if (isGroundFloor) { | |
const isBuilding = floor > 0.66 | |
if (isBuilding) { | |
if (h > 1 && Math.random() > groundDensity) continue | |
voxels.push({ t, h }) | |
} | |
continue | |
} | |
if (isMiddle) { | |
if (middleDensity === 0) continue | |
if (Math.random() > middleDensity) continue | |
voxels.push({ t, h }) | |
continue | |
} | |
if (isRoof) { | |
if (Math.random() > roofDensity) continue | |
voxels.push({ t, h }) | |
continue | |
} | |
} | |
return voxels | |
} | |
// color schemes | |
const ANY = -1 | |
const ORANGE_D = 0 | |
const ORANGE = 1 | |
const ORANGE_B = 2 | |
const YELLOW_D = 2 | |
const YELLOW = 3 | |
const YELLOW_B = 4 | |
const GREEN_D = 5 | |
const GREEN = 6 | |
const GREEN_B = 7 | |
const BLUE_D = 8 | |
const BLUE = 9 | |
const BLUE_B = 10 | |
const TEAL_D = 6 | |
const TEAL = 7 | |
const TEAL_B = 8 | |
const WHITE_D = 12 | |
const WHITE = 13 | |
const WHITE_B = 14 | |
const cs = (value, size) => ({ value, size }) | |
const standardMix = (color, dark, bright) => [ | |
cs(color, 6), | |
cs(dark, 1), | |
cs(bright, 1) | |
] | |
const repeatStop = ({ value, size }, repeat) => ({ value, size: size * repeat }) | |
const repeatStops = (stops, repeat) => stops.map(s => repeatStop(s, repeat)) | |
const combineStops = (...stops) => { | |
const map = new Map() | |
for (const colorMix of stops) { | |
for (const mix of colorMix) { | |
const { value, size } = mix | |
let c = map.get(value) | |
if (c === undefined) { | |
c = 0 | |
map.set(value, 0) | |
} | |
map.set(value, size + c) | |
} | |
} | |
return [...map.entries()].map(([value, size]) => ({ value, size })) | |
} | |
const sumSize = (size, stop) => size + stop.size | |
const totalSize = (stops) => stops.reduce(sumSize, 0) | |
const sample = (stops, x) => { | |
if (stops.length === 0) | |
throw Error('Empty array') | |
const total = totalSize(stops) | |
const target = total * x | |
// 0..1 | |
x = Math.min(Math.max(x, 0), 1) | |
let size = 0 | |
let i | |
for (i = 0; i < stops.length; i++) { | |
if (size >= target && i > 0) { | |
break | |
} | |
size += stops[i].size | |
} | |
const targetItem = stops[i - 1] | |
if (targetItem) { | |
return targetItem.value | |
} | |
throw Error(`Expected target, ${JSON.stringify({ total, target, size, i })}`) | |
} | |
const getColor = district => { | |
if (Math.random() < 0.02) return Math.floor(Math.random() * 15) | |
return sample(colorSchemes[district], Math.random()) | |
} | |
const whiteMix = standardMix(WHITE, WHITE_D, WHITE_B) | |
const tealMix = standardMix(TEAL, TEAL_D, TEAL_B) | |
const orangeMix = standardMix(ORANGE, ORANGE_D, ORANGE_B) | |
const greenMix = standardMix(GREEN, GREEN_D, GREEN_B) | |
const yellowMix = standardMix(YELLOW, YELLOW_D, YELLOW_B) | |
const blueOrangeMix = [ | |
{ value: BLUE, size: 1 }, | |
{ value: BLUE_D, size: 1 }, | |
{ value: ORANGE, size: 2 } | |
] | |
const strongMix = (a, b, c) => combineStops( | |
repeatStops(a, 80), | |
repeatStops(b, 15), | |
repeatStops(c, 5) | |
) | |
const midMix = (a, b, c) => combineStops( | |
repeatStops(a, 60), | |
repeatStops(b, 30), | |
repeatStops(c, 10) | |
) | |
const whiteStrongMix = strongMix(whiteMix, tealMix, orangeMix) | |
const whiteMidMix = midMix(whiteMix, tealMix, orangeMix) | |
const tealStrongMix = strongMix(tealMix, greenMix, whiteMix) | |
const tealMidMix = midMix(tealMix, greenMix, whiteMix) | |
const orangeStrongMix = strongMix(orangeMix, yellowMix, whiteMix) | |
const orangeMidMix = midMix(orangeMix, yellowMix, whiteMix) | |
const greenStrongMix = strongMix(greenMix, tealMix, whiteMix) | |
const greenMidMix = midMix(greenMix, tealMix, whiteMix) | |
const yellowStrongMix = strongMix(yellowMix, orangeMix, whiteMix) | |
const yellowMidMix = midMix(yellowMix, orangeMix, whiteMix) | |
const blueOrangeStrongMix = strongMix(blueOrangeMix, tealMix, orangeMix) | |
const blueOrangeMidMix = midMix(blueOrangeMix, tealMix, orangeMix) | |
const colorSchemes = [ | |
whiteStrongMix, | |
whiteMidMix, | |
tealStrongMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
tealMidMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
orangeMidMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
orangeStrongMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
greenMidMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
greenStrongMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
yellowMidMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
yellowStrongMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
blueOrangeMidMix, | |
whiteMidMix, | |
whiteStrongMix, | |
whiteMidMix, | |
blueOrangeStrongMix, | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment