Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Objects fall through each other right after page load, but are fine later. #483

Open
ThomasP850 opened this issue Nov 9, 2022 · 0 comments

Comments

@ThomasP850
Copy link

ThomasP850 commented Nov 9, 2022

The syntax is a bit weird because I'm using cannon.js through the Brython library for python, but you can get the idea. (It looks like github actually interpreted some of my code as markdown :/)
`import math, time, traceback
from browser import document, window, load, timer
load('https://cdn.jsdelivr.net/npm/three/build/three.min.js')
load('https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js')
load('https://cdn.jsdelivr.net/npm/[email protected]/examples/js/lights/RectAreaLightUniformsLib.js')
load('https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/OBJLoader.js')
load('https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js')

Make libraries more accessable

three = window.THREE
Cannon = window.CANNON

#-----------------------------------------

Functions used in setup

#-----------------------------------------

def setup_document(doc):
doc.body.innerHTML = ''
doc.body.style.margin = '0'
doc.body.style.overflow = 'hidden'

def cannon_to_three_vector(cannon_vec):
return three.Vector3.new(cannon_vec.x, cannon_vec.y, cannon_vec.z)

def three_to_cannon_vector(three_vec):
return Cannon.Vec3.new(three_vec.x, three_vec.y, three_vec.z)

#-----------------------------------------

Setting up the three.js graphics environment

#-----------------------------------------

setup_document(document)

FPS = 60
LOAD_TIME = 3 # Loading time in seconds

renderer = three.WebGLRenderer.new()
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
renderer.setSize(window.innerWidth * 4, window.innerHeight * 4, False)
renderer.shadowMap.enabled = True
document.body.appendChild(renderer.domElement)

scene = three.Scene.new()

camera = three.PerspectiveCamera.new(45, window.innerWidth /
window.innerHeight, 1, 10000)
camera.position.set(10, 15, 30)
camera.quaternion.setFromEuler(three.Euler.new(math.radians(-15), 0, 0))

controls = three.OrbitControls.new(camera, renderer.domElement)
controls.enablePan = False
controls.enableZoom = False

clock = three.Clock.new()

npc_row_animation_group = three.AnimationObjectGroup.new()
npc_row_mixer = three.AnimationMixer.new(npc_row_animation_group)

three.RectAreaLightUniformsLib.init();

#-----------------------------------------

Setting up the Cannon.js physics environment

#-----------------------------------------

world = Cannon.World.new()
world.gravity.set(0, -3, 0)
world.broadphase = Cannon.NaiveBroadphase.new()
world.broadphase.useBoundingBoxes = True
world.solver.iterations = 10

Dictionary to access the physics body for a three.js mesh

physics_objects = []

def update_physics(delta_time):
world.step(delta_time)
for mesh in physics_objects:
body = mesh.userData.body
mesh.position.copy(cannon_to_three_vector(body.position))
mesh.quaternion.copy(body.quaternion)

def init_physics_box(mesh, mass):
"""
Initialize a three.js mesh as a cube in the Cannon.js world

Arguments:
mesh -- The three.js mesh
mass -- The mass for the physics object in kg

Returns: The Cannon.js physics body
"""
bounding_box = three.Box3.new().setFromObject(mesh)
size = three.Vector3.new()
bounding_box.getSize(size)
rot_transform = mesh.quaternion.clone()
rot_transform.invert()
size.applyQuaternion(rot_transform)
size.set(abs(size.x), abs(size.y), abs(size.z))
body = Cannon.Body.new({
    'mass': mass,
    'position': mesh.position.clone(),
    'quaternion': mesh.quaternion.clone(),
    'shape': Cannon.Box.new(three_to_cannon_vector(size).scale(0.5)),
})
world.addBody(body)
mesh.userData.body = body;
physics_objects.append(mesh)
return body

#-----------------------------------------

Classes

#-----------------------------------------
class NpcRow:
"""
A graphical colored track that has a train of cube npcs that
move across it
"""
row_length = 300
npc_spacing = 3

def __init__(self, color, numNpcs, x, y, z):
    """
    Initialize an NpcRow
    
    Arguments:
    color -- The color for the backpane of the row
    numNpcs -- The number of npc cubes this row should have
    x -- The x position for this row
    y -- The y position for this row
    z -- The z position for this row
    """
    self.color = color
    self.numNpcs = numNpcs
    self.x = x
    self.y = y
    self.z = z
    
    # This group will contain all the game objects associated with this row
    self.group = three.Group.new()
    self.group.position.set(self.x, self.y, self.z)
    # This group will contain the npcs
    self.npc_group = three.Group.new()
    self.group.add(self.npc_group)
    self.__setup_objects()
    self.__setup_animations()

def __setup_objects(self):
    """
    Setup the individual three.js objects for this row
    """
    self.back_pane = three.Mesh.new(
        three.BoxGeometry.new(1, NpcRow.row_length, 0.1),
        three.MeshStandardMaterial.new({'color': self.color, 'side': three.DoubleSide})
    )
    self.back_pane.position.set(0, 0, -1)
    self.group.add(self.back_pane)
    
    rect_light = three.RectAreaLight.new(self.color, 2, 1, NpcRow.row_length)
    self.group.add(rect_light)
    
    for i in range(self.numNpcs):
        npc = self.__make_npc_object()
        npc.position.y = NpcRow.npc_spacing * i;
        self.npc_group.add(npc)
    
    self.npc_group.position.set(0, -NpcRow.row_length / 2, 0)
    
def __make_npc_object(self):
    """
    Construct an individual NpcCube containing a cube and a light behind it
    
    Returns: The constructed object
    """
    box = three.Mesh.new(
        three.BoxGeometry.new(1, 1, 1),
        three.MeshStandardMaterial.new({'color': '#ffffff'})
    )
    box.position.set(0, 0, 0)
    
    light = three.PointLight.new(self.color, 1, 100, 1)
    light.position.set(0, 0, -0.6)
    
    group = three.Group.new()
    group.add(box)
    group.add(light)
    return group
    
def __setup_animations(self):
    """
    Add this NpcRow to the animation group so it can be animated
    """
    npc_row_animation_group.add(self.npc_group)

def begin_animation():
    """
    Begin animation for all npc_row objects.
    
    This only needs to be called once per three.js scene in order to
    start animation for all NpcRow objects
    """
    keyframes = three.VectorKeyframeTrack.new(
        '.position',
        [0, 3],
        [0, -NpcRow.row_length / 2, 0, 0, NpcRow.row_length / 2, 0]
    )
    
    clip = three.AnimationClip.new('anim', 3, [keyframes])
    action = npc_row_mixer.clipAction(clip)
    action.setLoop(three.LoopRepeat)
    action.play()

def get_group(self):
    return self.group

#-----------------------------------------

Functions

#-----------------------------------------

Scene setup methods

def setup_main_scene():
"""
Setup the first scene of the story board
"""
# Lighting and effects
scene.fog = three.FogExp2.new('#000000', 0.02)

sky_light = three.DirectionalLight.new(0xffffff, 0.5)
sky_light.castShadow = True
scene.add(sky_light)

ambient_light = three.AmbientLight.new(0x222222, 2)
scene.add(ambient_light)

# Objects
orange_row = NpcRow('#fc8c03', 6, -2, 50, -26)
scene.add(orange_row.get_group())

green_row = NpcRow('#00e658', 6, 50, 8, -53)
green_row.get_group().quaternion.setFromEuler(three.Euler.new(0, 0, math.pi / 2))
scene.add(green_row.get_group())

purple_row = NpcRow('#6600ff', 6, 22, -50, -15)
purple_row.get_group().quaternion.setFromEuler(three.Euler.new(0, -math.pi / 5, 0))
scene.add(purple_row.get_group())

blue_row = NpcRow('#0008ff', 6, 10, 0, -15)
blue_row.get_group().quaternion.setFromEuler(three.Euler.new(-math.pi/2, 0, 0))
scene.add(blue_row.get_group())

# plane = NpcRow('#ffffff', 0, 5, 6, 0)
plane = three.Mesh.new(
    three.BoxGeometry.new(1, 300, 1),
    three.MeshStandardMaterial.new({'color': '#fff'})
)
plane.name = 'plane'
plane.position.set(6, 5, 2)
plane.quaternion.setFromEuler(three.Euler.new(math.pi/2, 0, math.pi/2))
init_physics_box(plane, 0)
scene.add(plane)



# Additional setup
NpcRow.begin_animation()
controls.target = three.Vector3.new(6, 10, 2)
# init_physics_box(gus, 1)

def setup_scene_two():
"""
Setup the second scene of the story board
"""
pass

def get_gus_object():
"""
Create and return an object to represent the main character, Gus.

Gus is a red cube.

This will also add Gus to the physics world

Return: A three.js Mesh representing Gus
"""
gus = three.Mesh.new(three.BoxGeometry.new(1, 1, 1), three.MeshStandardMaterial.new({'color': '#f00'}))
gus.name = 'gus'
return gus

def get_evil_cube_object():
"""
Create and return an object to represent the evil cube

This will also add the evil cube to the physics world

Return: A three.js Mesh representing the evil cube.
"""
evil_cube = three.Mesh.new(
    three.BoxGeometry.new(2, 2, 2),
    three.MeshStandardMaterial.new({'color': '#444'})
)
evil_cube.name = 'evil_cube'
return evil_cube

def add_ufo(ufo):
"""
This is a callback to be passed to the OBJLoader for the UFO object.

This will run once the ufo is loaded and set it up in the scene
"""
ufo.position.set(9, 13, -30)
ufo.scale.set(0.02, 0.02, 0.02)
ufo.rotation.x -= math.pi / 3
ufo.name = 'ufo'
scene.add(ufo)

program_start = time.time()

def periodic():
"""
This is the periodic method. It gets called rapidly to update graphics,
animations, and physics.
"""
delta_time = clock.getDelta()
update_physics(delta_time)
if time.time() - program_start > LOAD_TIME:
npc_row_mixer.update(delta_time)
try:
scene.getObjectByName('ufo').rotation.z += 0.03
except:
pass
controls.update()
renderer.render(scene, camera)
timer.set_timeout(periodic, 1000 / FPS)

def on_load():
gus = get_gus_object()
gus.position.set(6, 15, 2)
gus_body = init_physics_box(gus, 10)
def on_contact(e):
print('CONTACT')
gus_body.addEventListener('collide', on_contact)

scene.add(gus)

periodic() # Start the periodic loop

#-----------------------------------------

Main code

#-----------------------------------------
setup_main_scene()

loader = three.OBJLoader.new()
loader.load('https://thomasp850.github.io/temporary_hosting/ufo.obj', add_ufo)
timer.set_timeout(on_load, LOAD_TIME * 1000)`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant