mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-10-31 13:34:12 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			157 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
	
		
			4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /*
 | |
|  * SPDX-FileCopyrightText: syuilo and misskey-project
 | |
|  * SPDX-License-Identifier: AGPL-3.0-only
 | |
|  */
 | |
| 
 | |
| import * as Matter from 'matter-js';
 | |
| 
 | |
| export function physics(container: HTMLElement) {
 | |
| 	const containerWidth = container.offsetWidth;
 | |
| 	const containerHeight = container.offsetHeight;
 | |
| 	const containerCenterX = containerWidth / 2;
 | |
| 
 | |
| 	// サイズ固定化(要らないかも?)
 | |
| 	container.style.position = 'relative';
 | |
| 	container.style.boxSizing = 'border-box';
 | |
| 	container.style.width = `${containerWidth}px`;
 | |
| 	container.style.height = `${containerHeight}px`;
 | |
| 
 | |
| 	// create engine
 | |
| 	const engine = Matter.Engine.create({
 | |
| 		constraintIterations: 4,
 | |
| 		positionIterations: 8,
 | |
| 		velocityIterations: 8,
 | |
| 	});
 | |
| 
 | |
| 	const world = engine.world;
 | |
| 
 | |
| 	// create renderer
 | |
| 	const render = Matter.Render.create({
 | |
| 		engine: engine,
 | |
| 		//element: document.getElementById('debug'),
 | |
| 		options: {
 | |
| 			width: containerWidth,
 | |
| 			height: containerHeight,
 | |
| 			background: 'transparent', // transparent to hide
 | |
| 			wireframeBackground: 'transparent', // transparent to hide
 | |
| 		},
 | |
| 	});
 | |
| 
 | |
| 	// Disable to hide debug
 | |
| 	Matter.Render.run(render);
 | |
| 
 | |
| 	// create runner
 | |
| 	const runner = Matter.Runner.create();
 | |
| 	Matter.Runner.run(runner, engine);
 | |
| 
 | |
| 	const groundThickness = 1024;
 | |
| 	const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, {
 | |
| 		isStatic: true,
 | |
| 		restitution: 0.1,
 | |
| 		friction: 2,
 | |
| 	});
 | |
| 
 | |
| 	//const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts);
 | |
| 	//const wallLeft = Matter.Bodies.rectangle(-50, window.innerHeight/2, 100, window.innerHeight, wallopts);
 | |
| 
 | |
| 	Matter.World.add(world, [
 | |
| 		ground,
 | |
| 		//wallRight,
 | |
| 		//wallLeft,
 | |
| 	]);
 | |
| 
 | |
| 	const objEls = Array.from(container.children) as HTMLElement[];
 | |
| 	const objs: Matter.Body[] = [];
 | |
| 	for (const objEl of objEls) {
 | |
| 		const left = objEl.dataset.physicsX ? parseInt(objEl.dataset.physicsX) : objEl.offsetLeft;
 | |
| 		const top = objEl.dataset.physicsY ? parseInt(objEl.dataset.physicsY) : objEl.offsetTop;
 | |
| 
 | |
| 		let obj: Matter.Body;
 | |
| 		if (objEl.classList.contains('_physics_circle_')) {
 | |
| 			obj = Matter.Bodies.circle(
 | |
| 				left + (objEl.offsetWidth / 2),
 | |
| 				top + (objEl.offsetHeight / 2),
 | |
| 				Math.max(objEl.offsetWidth, objEl.offsetHeight) / 2,
 | |
| 				{
 | |
| 					restitution: 0.5,
 | |
| 				},
 | |
| 			);
 | |
| 		} else {
 | |
| 			const style = window.getComputedStyle(objEl);
 | |
| 			obj = Matter.Bodies.rectangle(
 | |
| 				left + (objEl.offsetWidth / 2),
 | |
| 				top + (objEl.offsetHeight / 2),
 | |
| 				objEl.offsetWidth,
 | |
| 				objEl.offsetHeight,
 | |
| 				{
 | |
| 					chamfer: { radius: parseInt(style.borderRadius || '0', 10) },
 | |
| 					restitution: 0.5,
 | |
| 				},
 | |
| 			);
 | |
| 		}
 | |
| 		objEl.id = obj.id.toString();
 | |
| 		objs.push(obj);
 | |
| 	}
 | |
| 
 | |
| 	Matter.World.add(engine.world, objs);
 | |
| 
 | |
| 	// Add mouse control
 | |
| 
 | |
| 	const mouse = Matter.Mouse.create(container);
 | |
| 	const mouseConstraint = Matter.MouseConstraint.create(engine, {
 | |
| 		mouse: mouse,
 | |
| 		constraint: {
 | |
| 			stiffness: 0.1,
 | |
| 			render: {
 | |
| 				visible: false,
 | |
| 			},
 | |
| 		},
 | |
| 	});
 | |
| 
 | |
| 	Matter.World.add(engine.world, mouseConstraint);
 | |
| 
 | |
| 	// keep the mouse in sync with rendering
 | |
| 	render.mouse = mouse;
 | |
| 
 | |
| 	for (const objEl of objEls) {
 | |
| 		objEl.style.position = 'absolute';
 | |
| 		objEl.style.top = '0';
 | |
| 		objEl.style.left = '0';
 | |
| 		objEl.style.margin = '0';
 | |
| 	}
 | |
| 
 | |
| 	window.requestAnimationFrame(update);
 | |
| 
 | |
| 	let stop = false;
 | |
| 
 | |
| 	function update() {
 | |
| 		for (const objEl of objEls) {
 | |
| 			const obj = objs.find(obj => obj.id.toString() === objEl.id.toString());
 | |
| 			if (obj == null) continue;
 | |
| 
 | |
| 			const x = (obj.position.x - objEl.offsetWidth / 2);
 | |
| 			const y = (obj.position.y - objEl.offsetHeight / 2);
 | |
| 			const angle = obj.angle;
 | |
| 			objEl.style.transform = `translate(${x}px, ${y}px) rotate(${angle}rad)`;
 | |
| 		}
 | |
| 
 | |
| 		if (!stop) {
 | |
| 			window.requestAnimationFrame(update);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 奈落に落ちたオブジェクトは消す
 | |
| 	const intervalId = window.setInterval(() => {
 | |
| 		for (const obj of objs) {
 | |
| 			if (obj.position.y > (containerHeight + 1024)) Matter.World.remove(world, obj);
 | |
| 		}
 | |
| 	}, 1000 * 10);
 | |
| 
 | |
| 	return {
 | |
| 		stop: () => {
 | |
| 			stop = true;
 | |
| 			Matter.Runner.stop(runner);
 | |
| 			window.clearInterval(intervalId);
 | |
| 		},
 | |
| 	};
 | |
| }
 |