2024-09-12 22:51:39 +02:00
|
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
|
|
# ORTHOGRAPHIC
|
|
|
|
# Your personal aerial satellite. Always on. At any altitude.*
|
|
|
|
# Developed by MarStrMind
|
|
|
|
# License: Open Software License 3.0
|
|
|
|
# Up to date version always on marstr.online
|
|
|
|
# -------------------------------------------------------------------
|
|
|
|
# xp_normalmap.py
|
|
|
|
# For some geographical features, we will add a normal map for
|
|
|
|
# additional realism in the orthos, without using additional meshes
|
|
|
|
# or geometry.
|
|
|
|
#
|
|
|
|
# Source converted to Python from this C++ StackOverflow:
|
|
|
|
# https://stackoverflow.com/a/2368794
|
|
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
import math
|
|
|
|
import os
|
|
|
|
from PIL import Image, ImageFilter
|
|
|
|
from defines import *
|
|
|
|
from log import *
|
|
|
|
|
|
|
|
|
|
|
|
class mstr_xp_normalmap:
|
|
|
|
|
|
|
|
# Only a few params
|
|
|
|
def __init__(self, lat, lng, tag, value, tv, th, latlngfld):
|
|
|
|
self._lat = lat
|
|
|
|
self._lng = lng
|
|
|
|
self._tag = tag
|
|
|
|
self._value = value
|
|
|
|
self._latlngfld = latlngfld
|
|
|
|
self._tv = tv
|
|
|
|
self._th = th
|
|
|
|
mstr_msg("xp_normalmap", "[X-Plane] Normal Map generator initialized")
|
|
|
|
|
|
|
|
|
|
|
|
# Load the layer image and resize it to 1/4th its size -
|
|
|
|
# then provide it
|
|
|
|
def load_layer(self):
|
|
|
|
qtr = int(mstr_photores / 4)
|
|
|
|
image = Image.open(mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._tv) + "_" + str(self._lng) + "-" + str(self._th) + "_" + self._tag + "-" + self._value + "_layer.png")
|
|
|
|
image = image.resize((qtr,qtr), Image.Resampling.LANCZOS)
|
|
|
|
mstr_msg("xp_normalmap", "[X-Plane] Layer image loaded")
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
# A few mathematical calls we need
|
|
|
|
# --------------------------------------------------------
|
|
|
|
def intensity(self, pixel):
|
|
|
|
avg = (pixel[0] + pixel[1] + pixel[2]) / 3
|
|
|
|
if avg > 0:
|
|
|
|
pavg = 255.0 / avg
|
|
|
|
else:
|
|
|
|
pavg = 0
|
|
|
|
return pavg
|
|
|
|
|
|
|
|
|
|
|
|
def clamp(self, px, mpx):
|
|
|
|
if px > mpx-1:
|
|
|
|
return mpx-1
|
|
|
|
else:
|
|
|
|
if px < 0:
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return px
|
|
|
|
|
|
|
|
|
|
|
|
def map_component(self, px):
|
|
|
|
return (px + 1.0) * (255.0 / 2.0)
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_vector(self, v):
|
|
|
|
vc = np.array([v[0], v[1], v[2]])
|
|
|
|
norm = np.linalg.norm(vc)
|
|
|
|
nv = vc / norm
|
|
|
|
return nv
|
|
|
|
# --------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
# The Big Mac. Generate the normal map
|
|
|
|
def generate_normal_map_for_layer(self, image):
|
|
|
|
mstr_msg("xp_normalmap", "[X-Plane] Beginning normal map generation")
|
2024-09-24 23:20:36 +02:00
|
|
|
# No specularity, no reflectivity - but standard color
|
|
|
|
# Blue (reflectivity) and alpha (specularity) need to be 1 - but can be adjusted as needed
|
|
|
|
nmp = Image.new("RGBA", (image.width, image.height), (128,128,1,1))
|
2024-09-12 22:51:39 +02:00
|
|
|
org = image.load()
|
|
|
|
nmp_pix = nmp.load()
|
|
|
|
|
|
|
|
# Let's try some shenanigans
|
|
|
|
w = image.width
|
|
|
|
h = image.height
|
|
|
|
for y in range(h):
|
|
|
|
for x in range(w):
|
|
|
|
p = org[x,y]
|
|
|
|
if p[3] > 0: # Only do something if there is something to do in layer
|
|
|
|
# Neighboring pixels
|
|
|
|
px_t = org[ self.clamp(x, w), self.clamp(y+1, h) ]
|
|
|
|
px_tr = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
|
|
|
|
px_r = org[ self.clamp(x+1, w), self.clamp(y, h) ]
|
|
|
|
px_br = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
|
|
|
|
px_b = org[ self.clamp(x, w), self.clamp(y-1, h) ]
|
|
|
|
px_bl = org[ self.clamp(x-1, w), self.clamp(y-1, h) ]
|
|
|
|
px_l = org[ self.clamp(x-1, w), self.clamp(y, h) ]
|
|
|
|
px_tl = org[ self.clamp(x-1, w), self.clamp(y+1, h) ]
|
|
|
|
|
|
|
|
# Intensities of pixels
|
|
|
|
it_t = self.intensity(px_t)
|
|
|
|
it_tr = self.intensity(px_tr)
|
|
|
|
it_r = self.intensity(px_r)
|
|
|
|
it_br = self.intensity(px_br)
|
|
|
|
it_b = self.intensity(px_b)
|
|
|
|
it_bl = self.intensity(px_bl)
|
|
|
|
it_l = self.intensity(px_l)
|
|
|
|
it_tl = self.intensity(px_tl)
|
|
|
|
|
|
|
|
# Sobel filter
|
|
|
|
dx = (it_tr + 2.0 * it_r + it_br) - (it_tl + 2.0 * it_l + it_bl)
|
|
|
|
dy = (it_bl + 2.0 * it_b + it_br) - (it_tl + 2.0 * it_t + it_tr)
|
|
|
|
dz = 10 # This is usually a good value for strength
|
|
|
|
v = (dx, dy, dz)
|
|
|
|
nrm = self.normalize_vector(v)
|
|
|
|
|
2024-09-24 23:20:36 +02:00
|
|
|
if nrm[1] > 0:
|
|
|
|
nrm[1] = 0 - (abs(nrm[1]))
|
|
|
|
else:
|
|
|
|
nrm[1] = abs(nrm[1])
|
2024-09-12 22:51:39 +02:00
|
|
|
|
|
|
|
# Set pixel
|
2024-09-29 13:19:52 +02:00
|
|
|
nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), 255 - int(self.map_component(nrm[2])), 1)
|
2024-09-12 22:51:39 +02:00
|
|
|
|
|
|
|
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
|
|
|
|
return nmp
|
|
|
|
|
|
|
|
|
|
|
|
# The funnction to call. Blends with the existing map, or creates a new one
|
|
|
|
def build_normalmap(self):
|
|
|
|
mstr_msg("xp_normalmap", "[X-Plane] Building normal map")
|
|
|
|
# The layer image
|
|
|
|
lyr = self.load_layer()
|
|
|
|
|
|
|
|
# Make the normal map for the layer
|
|
|
|
nrm = self.generate_normal_map_for_layer(lyr)
|
|
|
|
|
|
|
|
# Normal map final file name
|
2024-09-24 23:20:36 +02:00
|
|
|
nrmfln = mstr_datafolder + "z_orthographic/normals/" + self._latlngfld + "/" + str(self._tv) + "_" + str(self._th) + ".png"
|
2024-09-12 22:51:39 +02:00
|
|
|
|
|
|
|
# Check for existence of normal map file
|
|
|
|
ex = os.path.isfile(nrmfln)
|
|
|
|
|
|
|
|
# Does not exist? Just save
|
|
|
|
if ex == False:
|
|
|
|
nrm.save(nrmfln)
|
|
|
|
|
|
|
|
# Exists? Open it, composite both, save
|
|
|
|
if ex == True:
|
|
|
|
nrmmap = Image.open(nrmfln)
|
|
|
|
nrmmap.alpha_composite(nrm)
|
2024-09-24 23:20:36 +02:00
|
|
|
|
|
|
|
# Specularity blending correction
|
|
|
|
nrmmap_pix = nrmmap.load()
|
|
|
|
for y in range(nrmmap.height):
|
|
|
|
for x in range(nrmmap.width):
|
|
|
|
c = nrmmap_pix[x,y]
|
|
|
|
nrmmap_pix[x,y] = (c[0], c[1], c[2], 1)
|
2024-09-12 22:51:39 +02:00
|
|
|
nrmmap.save(nrmfln)
|
2024-09-24 23:20:36 +02:00
|
|
|
|
2024-09-12 22:51:39 +02:00
|
|
|
mstr_msg("xp_normalmap", "[X-Plane] Normal map saved")
|