Objects fall through each other right after page load, but are fine later.
ThomasP850 opened this issue · 0 comments
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/three@0.146.0/examples/js/controls/OrbitControls.js')
load('https://cdn.jsdelivr.net/npm/three@0.146.0/examples/js/lights/RectAreaLightUniformsLib.js')
load('https://cdn.jsdelivr.net/npm/three@0.146.0/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)`