schteppe / cannon.js

A lightweight 3D physics engine written in JavaScript.

Home Page:http://schteppe.github.com/cannon.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

ThomasP850 opened this issue · 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)`