require 'honey.std' local glfw = honey.glfw local gl = honey.gl local Vec3 = honey.Vec3 local Mat4 = honey.Mat4 local Quaternion = honey.Quaternion local ecs = honey.ecs local sys = honey.standardSystems local dispatch = sys.dispatch local ode = honey.ode local nvg = honey.nvg -- initialize honey local window = honey.init() window:setInputMode(glfw.CURSOR, glfw.CURSOR_DISABLED) -- setup vector graphics local vg = nvg.CreateContext() local november = nvg.CreateFont(vg, "November", "assets/november.regular.ttf") local vw, vh = 640, 480 -- setup physics local space = ode.HashSpaceCreate(ode.Space0) local world = ode.WorldCreate() ode.WorldSetGravity(world, 0, -10, 0) ode.WorldSetCFM(world, 1e-5) local physicsGc = honey.util.gc_canary(function() ode.SpaceDestroy(space) ode.WorldDestroy(world) end) -- setup ecs local entities = ecs.EntityDb() local systems = ecs.SystemDb(entities) systems:addSystem(sys.node) systems:addSystem(sys.renderCamera) systems:addSystem(sys.script) systems:addSystem(sys.collision, {space=space}) systems:addSystem(sys.physics, {space=space, world=world}) package.loaded['baseRotationScript'] = function(entities, id, dt) local node = entities:getComponent(id, "node") node.matrix:rotateZ(math.pi * dt) end package.loaded['cameraRotationScript'] = function(entities, id, dt) local node = entities:getComponent(id, "node") local z = entities:getComponent(id, "z") node.matrix :identity() :translate(Vec3{0, 0, z.value + math.sin(math.pi * glfw.GetTime())}) end package.loaded['cameraKeyHandler'] = function(entities, id, data) local z = entities:getComponent(id, "z") if data.key == glfw.KEY_W then z.value = z.value + 1 elseif data.key == glfw.KEY_S then z.value = z.value - 1 end end function setupEntities() local plane = entities:createEntity() entities:addComponents(plane, { node = { matrix = Mat4() :identity() :rotateZ(math.rad(5)) }, collision = { class = "plane", }, }) local planeMesh = entities:createEntityWithComponents{ node = { parent = plane, matrix = Mat4():identity():rotateX(0.5*math.pi):scale(Vec3{20,20,20}), }, renderMesh = { textures = { ourTexture={ filename="assets/green+grass-1024x1024.jpg" } }, shader = { vertex="vertex.glsl", fragment="fragment.glsl" }, mesh = { filename="builtin.quad", index=1 }, }, } local id = entities:createEntity() entities:addComponents(id, { renderMesh = { textures = { ourTexture={ filename="77155.png" } }, shader = { vertex="vertex.glsl", fragment="fragment.glsl" }, mesh = { filename="assets/icosahedron.obj", index=1 }, }, node = { matrix = Mat4() :identity() :translate(Vec3{0,1,0}) :rotateZ(math.rad(45)), }, collision = { class = "sphere", radius = 1, }, physics = { mass = { class = "sphere", density = 1, radius = 1, }, velocity = Vec3{ 0, 0, 0 }, angularVelocity = Vec3{ 0, 0, 0 }, }, }) local id2 = entities:createEntity() entities:addComponents(id2, { renderMesh = { shader = { vertex="vertex.glsl", fragment="fragment.glsl" }, --mesh = { filename="assets/tetrahedron.obj", index=1 }, mesh = { filename="builtin.quad", index=1 }, }, node = { parent=id, matrix=Mat4():identity():translate(Vec3{0, 2, 0}), }, }) local quad = entities:createEntity() entities:addComponents(quad, { renderQuad = { shader = { vertex="vertex.glsl", fragment="fragment.glsl" }, textures = { ourTexture = { filename = "44d9a0ec1c18e6126a5e9d9d9317f5ac.png" } }, }, }) local capsule = entities:createEntityWithComponents{ node = { matrix = Mat4():identity():translate(Vec3{0,10,0}):rotateX(0.5*math.pi) }, collision = { class = "capsule", radius = 1, length = 2, }, physics = { mass = { class = "capsule", density = 1, direction = 3, radius = 1, length = 2, }, maxAngularSpeed = 0, }, renderMesh = { mesh = { filename="assets/capsule.obj", index=1 }, shader = { vertex="vertex.glsl", fragment="fragment.glsl" }, }, } local pivotPivot = entities:createEntityWithComponents{ node = { parent = capsule, matrix = Mat4():identity():rotateX(-0.5*math.pi), }, } local capcamPivot = entities:createEntityWithComponents{ node = { parent = pivotPivot, matrix = Mat4():identity(), }, pitchyaw = { pitch = 0, yaw = 0, }, onCursorPos = { script = "cameraCursorPos" }, script = { script = "capsuleMove" }, } package.loaded["cameraCursorPos"] = (function() local prevx, prevy return function(entities, id, data) if not prevx then prevx, prevy = data.xpos, data.ypos end local dx = data.xpos - prevx local dy = data.ypos - prevy print(dx,dy) prevx, prevy = data.xpos, data.ypos local node = entities:getComponent(id, "node") local py = entities:getComponent(id, "pitchyaw") py.pitch = py.pitch - dy py.yaw = py.yaw - dx if py.pitch > 89.9 then py.pitch = 89.9 end if py.pitch < -89.9 then py.pitch = -89.9 end node.matrix :identity() :rotateY(math.rad(py.yaw)) :rotateX(math.rad(py.pitch)) end end)() package.loaded["capsuleMove"] = function(entities, id, dt) local pressed = function(key) return glfw.GetKey(window.win, key) == glfw.PRESS end local py = entities:getComponent(id, "pitchyaw") local yaw = math.rad(py.yaw) local forward = Vec3{-math.sin(yaw), 0, -math.cos(yaw)} local left = Vec3{-math.cos(yaw), 0, math.sin(yaw)} print(forward, left) local capsule = entities:getComponent(capsule, "physics") local vel = Vec3{0,0,0} if pressed(glfw.KEY_W) then vel = vel + forward end if pressed(glfw.KEY_A) then vel = vel + left end if pressed(glfw.KEY_S) then vel = vel - forward end if pressed(glfw.KEY_D) then vel = vel - left end vel = 10000 * vel:normalize() x, y, z = ode.BodyGetLinearVel(capsule._body) if Vec3{x,y,z}:norm2() < 500 then ode.BodyAddForce(capsule._body, vel[1], vel[2], vel[3]) end end local capcam = entities:createEntityWithComponents{ camera = { projection = Mat4():perspective(math.rad(45), 640/480, 0.1, 1000), render="screen", }, node = { parent = capcamPivot, matrix = Mat4():identity():translate(Vec3{0,0,10}), }, onWindowResize = { script = "cameraHandleResize" }, } local skybox = entities:createEntityWithComponents{ node = { parent = capsule, matrix = Mat4():identity():scale(Vec3{2,2,2}), }, renderMesh = { mesh = { filename="assets/skybox.obj", index=1 }, shader = { vertex="vertex.glsl", fragment="fragment.glsl" }, textures = { ourTexture = { filename = "assets/skyboxsun5deg2_tn.jpg" } }, }, } local camera = entities:createEntity() entities:addComponents(camera, { ccamera={ projection=Mat4():perspective(math.rad(45), 640/480, 0.1, 1000), render="screen", }, node={ matrix=Mat4():identity():rotateX(math.rad(-20)):translate(Vec3{0, 5, 30}), }, z = {value=-60}, onKey = { script="cameraKeyHandler", }, onWindowResize = { script = "cameraHandleResize" }, }) end setupEntities() -- close window on ESCAPE key window:setKeyCallback(function(_, key, scancode, action) dispatch(entities, "onKey", {key=key, scancode=scancode, action=action}) if action == glfw.PRESS then if key == glfw.KEY_ESCAPE then window:setShouldClose(true) elseif key == glfw.KEY_SPACE then entities:save("save") elseif key == glfw.KEY_L then entities:load("save") end end end) window:setCursorPosCallback(function(_, xpos, ypos) dispatch(entities, "onCursorPos", {xpos=xpos, ypos=ypos}) end) -- resize window correctly window:setFramebufferSizeCallback(function(_, width, height) gl.Viewport(0, 0, width, height) vw, vh = width, height dispatch(entities, "onWindowResize", { width=width, height=height }) end) package.loaded["cameraHandleResize"] = function(entities, id, data) local camera = entities:getComponent(id, "camera") if not camera then return end camera.projection:perspectiveResize(data.width/data.height) end --entities:load("save") -- main loop local time = 0 honey.loop(window, function(dt) time = time + dt if time > 1 then time = time-1 print(collectgarbage("count")) end gl.ClearColor(0.2, 0.4, 1.0, 1.0) gl.Clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT + gl.STENCIL_BUFFER_BIT) gl.Enable(gl.DEPTH_TEST) gl.Disable(gl.CULL_FACE) systems:update(dt) nvg.BeginFrame(vg, vw, vh, 1.0) nvg.StrokeColor(vg, nvg.RGBf(1, 1, 1)) nvg.FontFace(vg, "November") nvg.Text(vg, 50, 50, "fps: "..tostring(math.floor(1/dt))) nvg.EndFrame(vg) end) -- clean up honey.terminate()