()
| 105 | // model has been instantiated into the world (runs from level.load's |
| 106 | // onLoaded, after the container reset + model creation) |
| 107 | const setupScene = () => { |
| 108 | app.world.addChild(new SkyBackdrop(), -10000); |
| 109 | |
| 110 | const scene = loader.getGLTF("character"); |
| 111 | // the animated asset loads as a single GLTFModel named after the asset |
| 112 | const model = app.world.getChildByName("character")[0] as GLTFModel; |
| 113 | if (!scene || !model) { |
| 114 | return; |
| 115 | } |
| 116 | |
| 117 | // frame a Camera3d on the model: center on its bounds, look down a touch |
| 118 | // at a 3/4 yaw, pulled back to fit the model height. |
| 119 | const { min, max } = scene.bounds; |
| 120 | const cx = ((min[0] + max[0]) / 2) * SCALE; |
| 121 | const cy = -((min[1] + max[1]) / 2) * SCALE; // render space: -Y is up |
| 122 | const cz = -((min[2] + max[2]) / 2) * SCALE; |
| 123 | const spanY = (max[1] - min[1]) * SCALE; |
| 124 | |
| 125 | const camera = app.viewport as InstanceType<typeof Camera3dClass>; |
| 126 | camera.setClipPlanes(SCALE * 0.1, 8000); |
| 127 | const clamp = (v: number, lo: number, hi: number) => |
| 128 | Math.max(lo, Math.min(hi, v)); |
| 129 | |
| 130 | // orbit state — drag to rotate around the character |
| 131 | let yaw = 0.5; |
| 132 | let pitch = -0.12; |
| 133 | let distance = spanY * 2.4 + 200; |
| 134 | const updateCam = () => { |
| 135 | pitch = clamp(pitch, -1.45, 1.45); |
| 136 | distance = clamp(distance, 120, 4000); |
| 137 | camera.pos.set( |
| 138 | cx + Math.sin(yaw) * Math.cos(pitch) * -distance, |
| 139 | cy + Math.sin(pitch) * distance, // up = -Y |
| 140 | cz - Math.cos(yaw) * Math.cos(pitch) * distance, |
| 141 | ); |
| 142 | camera.lookAt(cx, cy, cz); |
| 143 | }; |
| 144 | updateCam(); |
| 145 | |
| 146 | // drag to orbit — radians per pixel dragged. Use the camera-independent |
| 147 | // screen coords (gameScreenX/Y), NOT gameX/gameY: the latter are |
| 148 | // projected through the viewport, so since orbiting moves the camera |
| 149 | // every frame the same pixel would map to a different world point each |
| 150 | // move — a feedback loop that makes the drag jump. gameScreenX/Y come |
| 151 | // straight from the canvas/scale transform and stay stable. (Same |
| 152 | // approach as the glTF Scene example.) |
| 153 | const ORBIT_SENSITIVITY = 0.0022; |
| 154 | let dragging = false; |
| 155 | let lastX = 0; |
| 156 | let lastY = 0; |
| 157 | input.registerPointerEvent("pointerdown", camera, (ev: Pointer) => { |
| 158 | dragging = true; |
| 159 | lastX = ev.gameScreenX; |
| 160 | lastY = ev.gameScreenY; |
| 161 | }); |
| 162 | input.registerPointerEvent("pointerup", camera, () => { |
| 163 | dragging = false; |
| 164 | }); |
nothing calls this directly
no test coverage detected