local WidgetContainer = require("ui/widget/container/widgetcontainer") local InputContainer = require("ui/widget/container/inputcontainer") local Screen = require("device").screen local UIManager = require("ui/uimanager") local BlitBuffer = require("ffi/blitbuffer") local CubePlugin = WidgetContainer:new{ name = "3dcube", } function CubePlugin:init() self:onDispatcherRegisterActions() self.ui.menu:registerTouchMenuEntry({ category = "tools", text = "Interactive 3D Cube", callback = function() self:showCube() end, }) end function CubePlugin:onDispatcherRegisterActions() self.dispatcher:registerAction("3d_cube_show", { category = "tools", event = "Show3DCube", title = "Interactive 3D Cube", general = true, }) end function CubePlugin:showCube() local CubeWidget = InputContainer:new{ angleX = 0.4, angleY = 0.6, -- 8 vertices of a unit cube nodes = { {-1,-1,-1}, {1,-1,-1}, {1,1,-1}, {-1,1,-1}, {-1,-1, 1}, {1,-1, 1}, {1,1, 1}, {-1,1, 1} }, -- 12 connecting edges edges = { {1,2},{2,3},{3,4},{4,1}, {5,6},{6,7},{7,8},{8,5}, {1,5},{2,6},{3,7},{4,8} }, } function CubeWidget:init() self.dimen = Screen:getBoundingRect() self.gesture_inst = { Pan = { gesture = "pan", touch_mode = "all", }, Tap = { gesture = "tap", touch_mode = "all", } } end -- Update rotation angles when dragging a finger function CubeWidget:onPan(arg, ges) if ges.relative then self.angleY = self.angleY + (ges.relative.x * 0.015) self.angleX = self.angleX - (ges.relative.y * 0.015) UIManager:setDirty(self, "partial") end return true end -- Tap near the top edge to exit back to KOReader function CubeWidget:onTap(arg, ges) if ges.pos.y < 120 then UIManager:close(self) end return true end function CubeWidget:paintTo(bb, x, y) -- Clear screen with white background bb:paintRect(0, 0, Screen:getWidth(), Screen:getHeight(), BlitBuffer.COLOR_WHITE) local fov, distance = 350, 3.5 local cx, cy = Screen:getWidth() / 2, Screen:getHeight() / 2 -- 3D to 2D Perspective Projection local projected = {} for i, p in ipairs(self.nodes) do -- Rotate around Y axis (yaw) local x1 = p[1] * math.cos(self.angleY) + p[3] * math.sin(self.angleY) local z1 = -p[1] * math.sin(self.angleY) + p[3] * math.cos(self.angleY) -- Rotate around X axis (pitch) local y2 = p[2] * math.cos(self.angleX) - z1 * math.sin(self.angleX) local z2 = p[2] * math.sin(self.angleX) + z1 * math.cos(self.angleX) -- Divide by Z depth local sz = z2 + distance projected[i] = { x = math.floor(cx + (x1 * fov) / sz), y = math.floor(cy + (y2 * fov) / sz) } end -- Draw wireframe lines for _, edge in ipairs(self.edges) do local p1, p2 = projected[edge[1]], projected[edge[2]] bb:drawLine(p1.x, p1.y, p2.x, p2.y, BlitBuffer.COLOR_BLACK, 4) end end UIManager:show(CubeWidget) end return CubePlugin