just leaving it in case i need it has backup or anyone wants to use
# vertex shader:
v_shader = '''#version 330
struct p3d_DirectionalLightParameters {
vec4 color;
vec3 direction;
sampler2DShadow shadowMap;
mat4 shadowViewMatrix;
};
uniform p3d_DirectionalLightParameters my_directional_light;
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform mat3 p3d_NormalMatrix;
uniform mat4 p3d_ModelViewMatrix;
in vec4 p3d_Vertex;
in vec3 p3d_Normal;
in vec2 p3d_MultiTexCoord0;
in vec4 offset;
in vec4 rotation; // Heading, Pitch, Roll
in vec4 scale;
out vec2 uv;
out vec4 shadow_uv;
out vec3 normal;
out vec4 fragPos;
mat4 quatToMat4(vec4 q) {
float x = q.x, y = q.y, z = q.z, w = q.w;
float x2 = x * x, y2 = y * y, z2 = z * z;
float xy = x * y, xz = x * z, yz = y * z;
float wx = w * x, wy = w * y, wz = w * z;
return mat4(
1.0 - 2.0 * (y2 + z2), 2.0 * (xy - wz), 2.0 * (xz + wy), 0.0,
2.0 * (xy + wz), 1.0 - 2.0 * (x2 + z2), 2.0 * (yz - wx), 0.0,
2.0 * (xz - wy), 2.0 * (yz + wx), 1.0 - 2.0 * (x2 + y2), 0.0,
0.0, 0.0, 0.0, 1.0
);
}
mat4 rotationMatrixX(float angle) {
float c = cos(angle);
float s = sin(angle);
return mat4(
1.0, 0.0, 0.0, 0.0,
0.0, c, -s, 0.0,
0.0, s, c, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
mat4 rotationMatrixY(float angle) {
float c = cos(angle);
float s = sin(angle);
return mat4(
c, 0.0, s, 0.0,
0.0, 1.0, 0.0, 0.0,
-s, 0.0, c, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
mat4 rotationMatrixZ(float angle) {
float c = cos(angle);
float s = sin(angle);
return mat4(
c, -s, 0.0, 0.0,
s, c, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
void main() {
vec4 vertexPosition = p3d_Vertex;
vec3 transformedNormal = p3d_Normal;
// Apply uniform scale
vertexPosition *= scale;
// Convert quaternion to rotation matrix
mat4 rotationMatrix = quatToMat4(rotation);
vertexPosition = rotationMatrix * vertexPosition;
transformedNormal = normalize(mat3(rotationMatrix) * p3d_Normal);
// Apply offset
vertexPosition += offset;
// Position
gl_Position = p3d_ModelViewProjectionMatrix * vertexPosition;
// Normal
normal = p3d_NormalMatrix * transformedNormal;
// UV
uv = p3d_MultiTexCoord0;
// Shadows
shadow_uv = my_directional_light.shadowViewMatrix * (p3d_ModelViewMatrix * vertexPosition);
// Frag position
fragPos = p3d_ModelViewMatrix * vertexPosition;
}'''
# fragment shader
f_shader = '''#version 330
struct p3d_DirectionalLightParameters {
vec4 color;
vec3 direction;
sampler2DShadow shadowMap;
mat4 shadowViewMatrix;
};
struct p3d_PointLightParameters {
vec4 color;
vec3 position;
samplerCube shadowMap;
vec3 attenuation;
};
struct p3d_SpotLightParameters {
vec4 color;
vec3 position;
vec3 spotDirection;
sampler2DShadow shadowMap;
mat4 shadowViewMatrix;
vec3 attenuation;
};
const int MAX_POINT_LIGHTS = 4;
const int MAX_SPOT_LIGHTS = 4;
uniform p3d_DirectionalLightParameters my_directional_light;
uniform p3d_PointLightParameters point_lights[MAX_POINT_LIGHTS];
uniform p3d_SpotLightParameters spot_lights[MAX_SPOT_LIGHTS];
uniform sampler2D p3d_Texture0;
uniform vec3 camera_pos;
uniform float shadow_blur;
uniform vec4 ambientLightColor;
uniform vec4 fogColor;
uniform float fogStart;
uniform float fogEnd;
uniform vec3 player_pos;
uniform bool enable_transparency;
uniform vec4 horizonColorb;
uniform int num_point_lights;
uniform int num_spot_lights;
in vec2 uv;
in vec4 shadow_uv;
in vec3 normal;
in vec4 fragPos;
out vec4 color;
float textureProjSoft(sampler2DShadow tex, vec4 uv, float bias, float blur) {
float result = textureProj(tex, uv, bias);
result += textureProj(tex, vec4(uv.xy + vec2(-0.326212, -0.405805) * blur, uv.z - bias, uv.w));
result += textureProj(tex, vec4(uv.xy + vec2(-0.840144, -0.073580) * blur, uv.z - bias, uv.w));
result += textureProj(tex, vec4(uv.xy + vec2(-0.695914, 0.457137) * blur, uv.z - bias, uv.w));
result += textureProj(tex, vec4(uv.xy + vec2(-0.203345, 0.620716) * blur, uv.z - bias, uv.w));
return result / 5.0; // Reduced number of samples
}
float calculatePointLightShadow(vec3 fragPos, vec3 lightPos, samplerCube shadowMap) {
vec3 lightToFrag = fragPos - lightPos;
float currentDepth = length(lightToFrag);
float shadow = texture(shadowMap, lightToFrag).r;
float bias = 0.05; // Adjust bias as needed
return currentDepth - bias > shadow ? 0.5 : 1.0;
}
void main() {
// Base color
vec3 ambient = ambientLightColor.rgb;
vec4 tex = texture(p3d_Texture0, uv);
// Calculate directional light contribution
vec3 dirLight = my_directional_light.color.rgb * max(dot(normalize(normal), my_directional_light.direction), 0.0);
float dirLightShadow = textureProjSoft(my_directional_light.shadowMap, shadow_uv, 0.0001, shadow_blur);
dirLightShadow = 0.5 + dirLightShadow * 0.5;
dirLight *= dirLightShadow;
// Calculate point light contributions with attenuation
vec3 totalPointLight = vec3(0.0);
for (int i = 0; i < num_point_lights; i++) {
vec3 lightDir = point_lights[i].position - fragPos.xyz;
float distance = length(lightDir);
vec3 attenuationFactors = point_lights[i].attenuation; // Fetch attenuation from struct
float attenuation = 1.0 / (attenuationFactors.x + attenuationFactors.y * distance + attenuationFactors.z * (distance * distance));
vec3 pointLight = point_lights[i].color.rgb * max(dot(normalize(normal), normalize(lightDir)), 0.0);
pointLight *= attenuation;
pointLight *= calculatePointLightShadow(fragPos.xyz, point_lights[i].position, point_lights[i].shadowMap);
totalPointLight += pointLight;
}
// Calculate spotlight contributions
vec3 totalSpotLight = vec3(0.0);
for (int i = 0; i < num_spot_lights; i++) {
vec3 spotDirection = normalize(spot_lights[i].spotDirection);
vec3 lightDir = spot_lights[i].position - fragPos.xyz;
float distance = length(lightDir);
vec3 attenuationFactors = spot_lights[i].attenuation;
float attenuation = 1.0 / (attenuationFactors.x + attenuationFactors.y * distance + attenuationFactors.z * (distance * distance)); // Use attenuation from struct
vec3 spotLight = spot_lights[i].color.rgb * max(dot(normalize(normal), -spotDirection), 0.0);
float theta = dot(normalize(fragPos.xyz - spot_lights[i].position), spotDirection);
float intensity = max(pow(theta, 10.0), 0.0); // Adjust the exponent to control the spotlight focus
spotLight *= intensity * attenuation;
totalSpotLight += spotLight;
}
// Combine all lighting
vec3 finalLight = dirLight + ambient + totalPointLight + totalSpotLight;
// Precompute fog factor
float heightFogFactor = clamp((fogEnd - length(fragPos.xyz.y)) / (fogEnd - fogStart), 0.0, 1.0);
float depthFogFactor = clamp((fogEnd - length(fragPos.xyz)) / (fogEnd - fogStart), 0.0, 1.0);
float fogFactor = min(heightFogFactor, depthFogFactor);
// Blend fog color with skybox color at the horizon
vec4 horizonColor = mix(fogColor, horizonColorb, 0.5);
vec4 foggedColor = mix(horizonColor, vec4(tex.rgb * finalLight, tex.a), fogFactor);
// Calculate distance from player position
float distance = length(fragPos.xyz - player_pos);
// Define a threshold for alpha
float alphaThreshold = 0.5;
// Adjust alpha based on distance with a hard cut-off
float alpha = enable_transparency ? (distance < 24.0 ? 0.0 : (tex.a > alphaThreshold ? 1.0 : 0.0)) : (tex.a > alphaThreshold ? 1.0 : 0.0);
// Apply the alpha to the fogged color
color = vec4(foggedColor.rgb, alpha);
}'''
import random
from direct.showbase.ShowBase import ShowBase
from panda3d.core import *
import time
import math
import numpy as np
from direct.actor.Actor import Actor
from direct.stdpy import threading
from direct.task import Task
from dataclasses import dataclass
# Set the dimensions of the height map
height, width = 4486, 4486
# Generate the noise height map
noise_height_map = np.random.rand(height, width)
@dataclass
class NPCData:
pos: Vec3
target: Vec3
heading: float
rot:Vec3
scale: Vec3 = Vec3(1, 1, 1)
physics_np: NodePath = None # Reference to physics body NodePath
import math
class InstanceShell:
def __init__(self, model_node):
self.node = model_node # The GeomNode or NodePath to instance
self.instance_count = 0
self.poswriter = None
self.rotwriter = None
self.scalewriter = None
self.instances = [] # List of logical instances (e.g., NodePaths)
self.setup_instance_buffers()
def setup_instance_buffers(self):
self.instance_count, self.poswriter, self.rotwriter, self.scalewriter = self.add_instance_shell(
nodefunc=self.node,
poswriter=self.poswriter,
rotwriter=self.rotwriter,
scalewriter=self.scalewriter
)
def create_instance_format(self,base_format):
format = GeomVertexFormat(base_format)
# Add instancing array
instance_array = GeomVertexArrayFormat()
instance_array.setDivisor(1)
instance_array.addColumn("offset", 4, Geom.NT_stdfloat, Geom.C_other)
instance_array.addColumn("rotation", 4, Geom.NT_stdfloat, Geom.C_other)
instance_array.addColumn("scale", 4, Geom.NT_stdfloat, Geom.C_other)
format.addArray(instance_array)
return GeomVertexFormat.registerFormat(format)
def add_instance_shell(self, nodefunc, poswriter=None, rotwriter=None, scalewriter=None):
# Setup format if not already set
gnode = nodefunc.find("**/+GeomNode").node()
geom = gnode.modifyGeom(0)
vdata = geom.modifyVertexData()
# Preserve the original format
base_format = vdata.getFormat()
extended_format = self.create_instance_format(base_format)
vdata.setFormat(extended_format)
vdata.setNumRows(90000)#< maybe does something
# Initialize writers if they are None
if poswriter is None:
poswriter = GeomVertexWriter(vdata, "offset")
rotwriter = GeomVertexWriter(vdata, "rotation")
scalewriter = GeomVertexWriter(vdata, "scale")
scale = 10
# Update position writer
poswriter.add_data3(5, 0, 0)
# Update rotation writer
rotwriter.add_data4(0, 0, 0, 1)
# Scale the instance
scalewriter.add_data4(scale, scale, scale, 1)
# Increment total instances and update node
self.instance_count += 1
nodefunc.setInstanceCount(self.instance_count)
# Ensure proper bounding
nodefunc.node().setBounds(OmniBoundingVolume())
nodefunc.node().setFinal(True)
return self.instance_count, poswriter, rotwriter, scalewriter
def add_instance(self, npc_data: NPCData):
self.instances.append(npc_data)
self.instance_count = len(self.instances)
self.node.setInstanceCount(self.instance_count)
def sync_transforms(self):
for i, npc in enumerate(self.instances):
self.poswriter.setRow(i)
# print(npc.pos)
self.poswriter.setData3f(npc.pos)
self.rotwriter.setRow(i)
# print(npc.rot[0], npc.rot[1], npc.rot[2])
# self.rotwriter.setData4f(npc.rot[0], npc.rot[1], npc.rot[2],1)
quat = npc.physics_np.getQuat()
self.rotwriter.setData4f(quat.get_z(), -quat.get_y(), quat.get_x(), quat.get_w())
self.scalewriter.setRow(i)
# print(npc.scale)
self.scalewriter.setData4f(npc.scale.x, npc.scale.y, npc.scale.z, 1)
loadPrcFileData("","""
cursor-hidden 1
support-threads #t
clock-frame-rate 60
show-frame-rate-meter true
""")
from panda3d.bullet import BulletWorld, BulletRigidBodyNode, BulletBoxShape, BulletPlaneShape
from panda3d.bullet import BulletBoxShape,BulletHelper,BulletGhostNode,BulletDebugNode,BulletVehicle,Z_up,BulletTriangleMesh,BulletTriangleMeshShape,BulletHingeConstraint,BulletSphericalConstraint
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
base.trackball.node().set_pos(4.7, 112.7, -9.7)
base.trackball.node().set_hpr(61.5281, 12.0915, -18.2124)
# self.node = Actor('panda-model', {'walk' : 'panda-walk4'})
# self.node.loop('walk')
# self.node.setScale(0.01)
# self.node.reparentTo(render)
# self.node.setPos(0,0,0)
#lighting
self.sun = DirectionalLight("Spot")
# print(dir(self.sun),'attributes')
self.sun_path = self.render.attachNewNode(self.sun)
self.sun_path.node().set_shadow_caster(True, 4096, 4096)
self.sun_path.node().set_color((0.9, 0.9, 0.8, 1.0))
# self.sun_path.node().showFrustum()
self.sun_path.node().get_lens().set_fov(40)
# self.sun_path.node().attenuation = (1, 0.001, 0.0001)
self.sun_path.node().get_lens().set_near_far(-400, 400)
self.sun_path.node().get_lens().set_film_size(400)
self.pivot = self.render.attachNewNode("pivot")
self.pivot.setPos(0, 0, 0) # Set the position of the pivot point
self.sun_path.setHpr(0, 0, 0)
self.sun_path.reparentTo(self.pivot)
self.render.setLight(self.sun_path)
#sun shader settings
self.render.set_shader_input('my_directional_light',self.sun_path)
self.render.set_shader_input("my_directional_light.direction", self.sun.getDirection())
self.instanceshader = Shader.make(Shader.SL_GLSL,v_shader, f_shader)
# self.node.setShader(self.instanceshader)
# self.render.set_shader_input('my_point_light',self.point_path)
self.spotlight1 = Spotlight("spotlight1")
self.spotlight1.setColor((10, 10, 10, 1)) # Brighter light (RGB values higher than 1)
self.spotlight1.setAttenuation((1.0, 0.09, 0.032))
self.spotlight1.setMaxDistance(10)
self.spotlight1_path = self.render.attachNewNode(self.spotlight1)
# self.spotlight1_path.setPos(-16, -31, 28) # Example position
self.render.setLight(self.spotlight1_path)
# self.spotlight1.showFrustum()
self.spotlight2 = Spotlight("spotlight2")
self.spotlight2.setAttenuation((1.0, 0.09, 0.032))
self.spotlight2.setMaxDistance(10)
self.spotlight2_path = self.render.attachNewNode(self.spotlight2)
self.spotlight2_path.setPos(-22, -22, 26) # Example position
self.render.setLight(self.spotlight2_path)
# self.spotlight2.showFrustum()
self.render.set_shader_input('num_spot_lights', 2)
self.render.set_shader_input('my_spot_light', self.spotlight1_path)
self.render.set_shader_input('spot_lights[0]', self.spotlight1_path)
self.render.set_shader_input('spot_lights[1]', self.spotlight2_path)
# self.spotlight1_path.reparentTo(self.point_path)
self.render.set_shader_input(f'spot_lights[1].color', LVecBase4(0, 0, 0, 0))#off
self.render.set_shader_input(f'spot_lights[0].color', LVecBase4(0, 0, 0, 0))#off
for i in range(2,4):
self.render.set_shader_input(f'spot_lights[{i}]',self.spotlight1_path)
self.render.set_shader_input(f'spot_lights[{i}].color', LVecBase4(0, 0, 0, 0))#off
# Initialize point lights
self.point1 = PointLight("Point1")
self.point1.setAttenuation((1.0, 0.09, 0.032))
self.point1.setMaxDistance(50)
self.point1_path = self.render.attachNewNode(self.point1)
self.point1_path.setPos(-16, -31, 29) # Example position
self.render.setLight(self.point1_path)
self.point2 = PointLight("Point2")
self.point2.setAttenuation((0.1, 0.43, 0.044))
self.point2.setMaxDistance(2)
self.point2_path = self.render.attachNewNode(self.point2)
self.point2_path.setPos(-22, -22, 29) # Example position
self.render.setLight(self.point2_path)
#point light
self.point = PointLight("Point")
self.point.setAttenuation((1.0, 0.09, 0.032)) # Adjust these values to control the attenuation
self.point.setMaxDistance(50) # Set the maximum distance of the light's influence
# self.point.showFrustum()
self.point_path = self.render.attachNewNode(self.point)
self.render.set_shader_input('num_point_lights', 2)
# Set shader inputs for existing lights
self.render.set_shader_input('point_lights[0]',self.point_path)
self.render.set_shader_input('point_lights[1]',self.point2_path)
self.render.set_shader_input('point_lights[1].color', LVecBase4(0, 0, 0, 0))#off
self.render.set_shader_input('point_lights[0].color', LVecBase4(0, 0, 0, 0))#off
for i in range(2,4):
self.render.set_shader_input(f'point_lights[{i}]',self.point1_path)
self.render.set_shader_input(f'point_lights[{i}].color', LVecBase4(0, 0, 0, 0))#off
self.render.set_shader_input('shadow_blur',0.0005)
self.render.set_shader_input('player_pos',(0,0,0))
self.render.set_shader_input('enable_transparency',False)
self.render.setShaderInput("fogColor", (0.5, 0.5, 0.5, 1.0)) # Set the fog color
self.render.setShaderInput("fogStart", 300.0) # Set the fog start distance
self.render.setShaderInput("fogEnd", 410.0) # Set the fog end distance
self.render.setShaderInput("ambientLightColor", (0.1, 0.1, 0.1, 1.0))
self.horizon_colorday = Vec4(0.529, 0.808, 0.980, 1)
self.render.setShaderInput("horizonColorb", self.horizon_colorday)
self.camera = base.cam
lens = self.camera.node().getLens()
lens.setNear(1)
lens.setFar(700.0)
self.panda = Actor('panda-model', {'walk' : 'panda-walk4'})
self.panda.loop('walk')
# self.panda = self.loader.loadModel('panda-model')
self.panda.setScale(0.001)
self.panda.reparentTo(render)
self.panda.setShader(self.instanceshader)
self.shells = {
# "bush": InstanceShell(self.npc_actor),
"rock": InstanceShell(self.panda),
}
self.npc_nodes = [] # List to store NPC NodePaths
self.npc_targets = [
self.generate_random_point((-1000, -1000, 0), (1000, 1000, 10)) # Random point within boundaries
for _ in range(100000)
]
self.world = BulletWorld()
self.world.set_gravity(0, 0, -9.81) # Standard gravity
debugNode = BulletDebugNode("Debug")
debugNode.showWireframe(True)
debugNode.showConstraints(True)
debugNode.showBoundingBoxes(False)
debugNode.showNormals(True)
self.debugNP = render.attachNewNode(debugNode)
self.debugNP.show()
self.world.setDebugNode(debugNode)
for i in range(200):
x = random.uniform(-100, 100)
y = random.uniform(-100, 100)
z = 10 + random.uniform(-20, 20)
self.add_instance(model_type="rock", position=(x, y, z))
self.accept("i", self.dele)
floor_shape = BulletPlaneShape(Vec3(0, 0, 1), 0)
floor_node = BulletRigidBodyNode('floor')
floor_node.addShape(floor_shape)
floor_np = render.attachNewNode(floor_node)
self.world.attach(floor_node)
self.timeb=0
# box_shape = BulletBoxShape(Vec3(0.5, 0.5, 0.5)) # Half-extents
# box_node = BulletRigidBodyNode('box')
# box_node.setMass(1.0) # Dynamic body needs mass
# box_node.addShape(box_shape)
# box_np = render.attachNewNode(box_node)
# box_np.setPos(0, 0, 10) # Start above floor
# self.world.attach(box_node)
# Add physics update task
# self.taskMgr.add(self.update_physics, 'update_physics')
# base.accept('m', self.addtask)
self.taskMgr.add(self.updateTask, "update")
threading.Thread(target = self.update_physics_thread).start()
# def addtask(self):
# self.taskMgr.add(self.updateTask, "update")
def dele(self):
shell = self.shells["rock"]
if shell.instances:
npc = shell.instances[0]
self.remove_instance(npc,shell)
def updateTask(self, task):
self.move_npcs_to_targets(task)
# self.timeb+=1
# # print(self.instanceB)
# if self.instanceB is not None:
# self.instanceB.setR(self.timeb)
# self.instanceB.setP(self.timeb)
# self.instanceB.setH(self.timeb)
# print("g")
return task.cont
def remove_instance(self, npc_data: NPCData,shell):
# Remove from instances list
if npc_data in shell.instances:
shell.instances.remove(npc_data)
# Update instance count
shell.instance_count = len(shell.instances)
# Update the node's instance count
shell.node.setInstanceCount(shell.instance_count)
# Clean up physics and scene graph
if npc_data.physics_np:
physics_node = npc_data.physics_np.node()
self.world.remove(physics_node)
npc_data.physics_np.remove_node()
def add_instance(self, model_type, position):#main class
shell = self.shells[model_type]
target = self.generate_random_point((-1000, -1000, 0), (1000, 1000, 10))
# Create physics body
physics_node = BulletRigidBodyNode('npc_physics')
physics_node.setMass(1.0)
physics_node.addShape(BulletBoxShape(Vec3(2.5, 2.5, 2.5)))
physics_np = render.attachNewNode(physics_node)
physics_np.setPos(*position)
# physics_np.setHpr((0,30,0))
self.world.attach(physics_node)
self.instanceB=physics_np
# print(self.instanceB)
npc = NPCData(pos=Vec3(*position), target=target, heading=0,
scale=Vec3(10,10,10), physics_np=physics_np,rot=Vec3(10,10,10))
shell.add_instance(npc)
def update_physics_thread(self):#threading.Thread(target = self.update_physics_thread).start()
framerate = 1/60
while True:
dt = globalClock.getDt()
self.world.doPhysics(dt, 10, 1.0/180.0)
time.sleep(framerate)
def move_npcs_to_targets(self, task):
delta_time = globalClock.getDt()
# Step physics simulation first
# self.world.do_physics(delta_time)
# self.panda.update()
for shell in self.shells.values():
for npc in shell.instances:
if npc.physics_np:
# Sync position from physics body to NPCData
npc.pos = npc.physics_np.getPos()*1000
# npc.rot = npc.physics_np.getHpr()
shell.sync_transforms()
return task.cont
def add_npc(self, position=(0, 0, 0)):
pos = Vec3(*position)
target = self.generate_random_point((-1000, -1000, 0), (1000, 1000, 10))
npc = NPCData(pos=pos, target=target, heading=0)
self.shell.add_instance(npc) # Add to InstanceShell
def generate_random_point(self, boundary_min, boundary_max):
x = random.uniform(boundary_min[0], boundary_max[0])
y = random.uniform(boundary_min[1], boundary_max[1])
z = random.uniform(boundary_min[2], boundary_max[2])
return Vec3(x, y, z)
def position_gen(self, value=1000, seed=29, area_width=486, area_height=486, height_map=None, min_distance=10):
random.seed(seed)
grid_size = min_distance
grid_width = area_width // grid_size
grid_height = area_height // grid_size
occupied_positions = []
for i in range(value):
while True:
grid_x = random.randint(0, grid_width - 1)
grid_y = random.randint(0, grid_height - 1)
x = grid_x * grid_size + random.uniform(0, grid_size)
y = grid_y * grid_size + random.uniform(0, grid_size)
z = height_map[int(x)][int(y)]
pos = (int(x), int(y), z)
if pos not in occupied_positions:
occupied_positions.append(pos)
break
return occupied_positions
app = MyApp()
app.run()