Instancing on bullet physics objects

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()
2 Likes