Initial commit
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import * as THREE from 'three';
|
||||
import { NET, goalLineX } from '../../shared/net.js';
|
||||
import { CAT, KIND, makeTag, xyz } from './bridge.js';
|
||||
|
||||
/**
|
||||
* The goal frame: posts, crossbar, and a mesh back that stops the puck.
|
||||
*
|
||||
* Static bodies, because a net that moves is a rule (it comes off its moorings)
|
||||
* rather than a feature, and not one worth having before there is a game.
|
||||
*
|
||||
* The back and sides are solid boxes rather than a real mesh. A puck that goes
|
||||
* in should stay in and settle, and modelling twine is a lot of work to make a
|
||||
* puck stop moving.
|
||||
*/
|
||||
export function createNet(physics, end) {
|
||||
const { api, world } = physics;
|
||||
const line = goalLineX(end);
|
||||
const halfW = NET.width / 2;
|
||||
const r = NET.postRadius;
|
||||
|
||||
const sd = api.b3DefaultShapeDef();
|
||||
sd.baseMaterial.friction = 0.4;
|
||||
// Posts ring; the back eats everything so the puck settles in the net.
|
||||
sd.baseMaterial.restitution = 0.35;
|
||||
sd.baseMaterial.userMaterialId = makeTag(KIND.RINK, 0xff, end > 0 ? 10 : 11);
|
||||
sd.filter.categoryBits = CAT.RINK;
|
||||
sd.filter.maskBits = 0xffffffffffffffffn;
|
||||
|
||||
const bodies = [];
|
||||
const box = (x, y, z, hx, hy, hz, restitution = null) => {
|
||||
const bd = api.b3DefaultBodyDef();
|
||||
bd.position = xyz(x, y, z);
|
||||
const b = api.b3CreateBody(world, bd);
|
||||
if (restitution !== null) sd.baseMaterial.restitution = restitution;
|
||||
api.b3CreateBoxShape(b, sd, hx, hy, hz);
|
||||
sd.baseMaterial.restitution = 0.35;
|
||||
bodies.push(b);
|
||||
return b;
|
||||
};
|
||||
|
||||
// Posts, on the line.
|
||||
box(line, NET.height / 2, halfW, r, NET.height / 2, r);
|
||||
box(line, NET.height / 2, -halfW, r, NET.height / 2, r);
|
||||
// Crossbar.
|
||||
box(line, NET.height, 0, r, r, halfW);
|
||||
// Back and sides, deadened so the puck does not fire back out.
|
||||
//
|
||||
// The net extends *away* from centre ice, `line + end * depth`. Getting this
|
||||
// sign backwards put the back panel a metre in front of the goal line — a
|
||||
// solid wall across the mouth — and every shot in the game bounced off it
|
||||
// before it could cross. Nothing ever scored, and the symptom looked like a
|
||||
// goalie problem.
|
||||
box(line + end * NET.depth, NET.height / 2, 0, 0.04, NET.height / 2, halfW, 0.02);
|
||||
box(line + end * NET.depth * 0.5, NET.height / 2, halfW, NET.depth / 2, NET.height / 2, 0.03, 0.05);
|
||||
box(line + end * NET.depth * 0.5, NET.height / 2, -halfW, NET.depth / 2, NET.height / 2, 0.03, 0.05);
|
||||
|
||||
return {
|
||||
end,
|
||||
bodies,
|
||||
destroy() {
|
||||
for (const b of bodies) api.b3DestroyBody(b);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** The rendered net: frame tubes plus a translucent mesh bag. */
|
||||
export function buildNetMesh(scene, end) {
|
||||
const line = goalLineX(end);
|
||||
const halfW = NET.width / 2;
|
||||
const group = new THREE.Group();
|
||||
group.name = 'net:' + end;
|
||||
|
||||
const frame = new THREE.MeshStandardMaterial({ color: 0xc0332c, roughness: 0.45, metalness: 0.25 });
|
||||
const mesh = new THREE.MeshStandardMaterial({
|
||||
color: 0xf2f4f8,
|
||||
roughness: 0.9,
|
||||
transparent: true,
|
||||
opacity: 0.28,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
});
|
||||
|
||||
const tube = (len, x, y, z, axis) => {
|
||||
const g = new THREE.CylinderGeometry(NET.postRadius, NET.postRadius, len, 10);
|
||||
const m = new THREE.Mesh(g, frame);
|
||||
if (axis === 'z') m.rotation.x = Math.PI / 2;
|
||||
if (axis === 'x') m.rotation.z = Math.PI / 2;
|
||||
m.position.set(x, y, z);
|
||||
m.castShadow = true;
|
||||
group.add(m);
|
||||
};
|
||||
tube(NET.height, line, NET.height / 2, halfW, 'y');
|
||||
tube(NET.height, line, NET.height / 2, -halfW, 'y');
|
||||
tube(NET.width, line, NET.height, 0, 'z');
|
||||
// Back frame, so the net reads as a box rather than as a doorway.
|
||||
tube(NET.depth, line + end * NET.depth / 2, 0.06, halfW, 'x');
|
||||
tube(NET.depth, line + end * NET.depth / 2, 0.06, -halfW, 'x');
|
||||
|
||||
const back = new THREE.Mesh(new THREE.PlaneGeometry(NET.width, NET.height), mesh);
|
||||
back.position.set(line + end * NET.depth, NET.height / 2, 0);
|
||||
back.rotation.y = Math.PI / 2;
|
||||
group.add(back);
|
||||
for (const s of [1, -1]) {
|
||||
const side = new THREE.Mesh(new THREE.PlaneGeometry(NET.depth, NET.height), mesh);
|
||||
side.position.set(line + end * NET.depth / 2, NET.height / 2, s * halfW);
|
||||
group.add(side);
|
||||
}
|
||||
const top = new THREE.Mesh(new THREE.PlaneGeometry(NET.depth, NET.width), mesh);
|
||||
top.rotation.x = -Math.PI / 2;
|
||||
top.position.set(line + end * NET.depth / 2, NET.height, 0);
|
||||
group.add(top);
|
||||
|
||||
// Crease paint.
|
||||
const crease = new THREE.Mesh(
|
||||
new THREE.CircleGeometry(NET.creaseRadius, 24, end > 0 ? -Math.PI / 2 : Math.PI / 2, Math.PI),
|
||||
new THREE.MeshBasicMaterial({ color: 0x77b3e0, transparent: true, opacity: 0.45, depthWrite: false }),
|
||||
);
|
||||
crease.rotation.x = -Math.PI / 2;
|
||||
crease.position.set(line, 0.004, 0);
|
||||
group.add(crease);
|
||||
|
||||
scene.add(group);
|
||||
return group;
|
||||
}
|
||||
Reference in New Issue
Block a user