Я бы сказал, просто проверьте, что вы не в углу - отбросьте случаи, когда percentOfWall
либо точно 0
, либо точно 1
РЕДАКТИРОВАТЬ : для решения углы, упомянутые в комментариях, я должен объяснить, почему ваша реализация застряла. Он рассчитал проникновение со всеми стенами и уменьшил изменение положения на эту величину проникновения. В углу объект столкнулся с обоими краями одновременно и, отталкиваясь от обоих, сразу перестал двигаться.
Как вы правильно заметили, в углах ваш квадрат попадает внутрь препятствия на один кадр, а затем отталкивается на следующем кадре.
Альтернативные решения, однако, более сложны и сложнее для отладки, но вот набросок для пары вариантов:
- Ограничение столкновений для отражения только с одной стены за раз и рано выходите из вашего
forEach
l oop. Это простое решение, которое будет работать в этом конкретном примере, но не будет работать в общем случае, например, в углу, когда вам нужно столкнуться с 2 стенами, мешающими вам go в обоих направлениях. - Добавьте крошечный круг в каждом из углов и столкнитесь с ним, чтобы не попасть внутрь. Нормаль находится вдоль линии между центром круга и точкой контакта. Это сглаживает нормальный разрыв, который вы имеете в углах, и всегда есть один нормаль, вдоль которого объект отталкивается, непрерывно переходя от одного сегмента к другому.
Направление "pu sh" чтобы за пределами каждого отрезка препятствия (что вы и просите) не помешало бы остановиться, так как на углу (это именно то место, которое вас интересует), обе стены столкнутся с вашим объектом, и «снаружи» будет в противоположные направления. Так что он застрянет так же, как и раньше, и по той же причине - нормали не будут непрерывными.
Надеюсь, это поможет
var aD =[]
var r
function start() {
r = new CanvasRenderer(can),
my = new scene();
window.my = my
eventHandler();
my.add(new mesh({
verts: [
0, 0,
100, 15,
115, 60,
50, 100,
20, 75,2,8
],
position: {
x: 100,
y:100
},
scale: {
x:4,y:5
},
color:"orange",
onupdate(me) {
// me.position.x++
}
}));
var g = false
my.add(new mesh({
primitive:"rect",
name: "player",
scale: {
x: 50,
y:50
},
position: {
x: 311,
y:75
},
origin: {
x:0.5,
y:0.5
},
onupdate(me) {
var upKey = keys[38],
downKey = keys[40],
rightKey = keys[39],
leftKey = keys[37],
drx = 0,
dx = 0,
speed = 5,
turningSpeed = 3
drx = leftKey ? -1 : rightKey ? 1 : 0
forward = upKey ? 1 : downKey ? -1 : 0
me.rotation.x += (
(drx * Math.PI / 180 * turningSpeed )
)
me.rotation.y = 1;
var xDir = Math.cos(me.rotation.x)
var yDir = Math.sin(me.rotation.x)
me.position.x += xDir * forward * speed
me.position.y += yDir * forward * speed
for(var i = 0; i < my.objects.length; i++) {
let cur = my.objects[i];
if(cur.name !== me.name) {
cur.lineSegments.forEach(l => {
var col = checkCollision(
me.position.x,
me.position.y,
me.scale.x/2,
l
)
if(col) {
me.position.y=col.y
me.position.x = col.x
}
});
}
}
}
}));
let i = setInterval(() => render(r, my), 16);
r.on("resize", () => render(r, my));
}
function checkCollision(x1, y1, rad,l) {
var dist = distance2(
l.start[0],
l.start[1],
l.end[0],
l.end[1]
),
vec1 = [
x1 - l.start[0],
y1 - l.start[1]
],
vec2 = [
l.end[0] - l.start[0],
l.end[1] - l.start[1]
],
percentOfWall = (
Math.max(
0,
Math.min(
1,
dot(
vec1[0],
vec1[1],
vec2[0],
vec2[1]
) / dist
)
)
),
projection = [
l.start[0] + percentOfWall * vec2[0],
l.start[1] + percentOfWall * vec2[1],
],
acDist = Math.sqrt(distance2(
x1, y1,
projection[0], projection[1]
))
aD.push( () => {
r.ctx.beginPath()
r.ctx.fillStyle="green"
r.ctx.arc(projection[0], projection[1], 5, 0, Math.PI*2);
r.ctx.fill()
r.ctx.closePath();
})
if(acDist < rad && percentOfWall > 0 && percentOfWall < 1) {
var mag = Math.sqrt(dist),
delt = [
l.end[0] - l.start[0],
l.end[1] - l.start[1]
],
normal = [
delt[0] / mag,
delt[1] / mag
]
return {
x: projection[0] +
rad * (normal[1] ),
y:projection[1] +
rad* (-normal[0] ),
projection,
normal
}
}
}
function dot(x1, y1, x2, y2) {
return (
x1 * x2 + y1 * y2
)
}
function distance2(x1, y1, x2, y2) {
let dx = (x1 - x2), dy = (y1 - y2);
return (
dx * dx + dy * dy
);
}
function render(r,s) {
//r.ctx.clearRect(0,0,r.ctx.canvas.width,r.ctx.canvas.height)
s.update();
r.render(s)
aD.forEach(x=>x());
aD = []
}
onload = start;
function eventHandler() {
window.keys = {};
addEventListener("keyup" , e=> {
keys[e.keyCode] = false;
});
addEventListener("keydown" , e=> {
keys[e.keyCode] = true;
});
}
function CanvasRenderer(dom) {
if(!dom) dom = document.createElement("canvas");
var events = {}, self = this;
function rsz() {
dom.width = dom.clientWidth;
dom.height = dom.clientHeight;
self.dispatchEvent("resize");
}
window.addEventListener("resize", rsz);
let ctx = dom.getContext("2d");
function render(scene) {
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
for(let i = 0; i < scene.objects.length; i++) {
let o = scene.objects[i],
verts = o.realVerts;
ctx.beginPath();
ctx.moveTo(
verts[0] ,
verts[1]
);
verts.forEach((v, i, ar) => {
let y = i;
ctx.lineTo(
v[0] ,
v[1]
);
});
ctx.lineTo(
verts[0],
verts[1]
);
ctx.fillStyle = o.color || "blue";
ctx.lineWidth = 1;
ctx.fill()
ctx.stroke();
ctx.closePath();
}
}
Object.defineProperties(this, {
domElement: {
get: () => dom
},
ctx: {
get: () => ctx
},
render: {
get: () => render
},
on: {
get: () => (nm, cb) => {
if(!events[nm]) {
events[nm] = [];
}
events[nm].push(data => {
if(typeof cb == "function") {
cb(data);
}
});
}
},
dispatchEvent: {
get: () => (name, data) => {
if(events[name]) {
events[name].forEach(x => {
x(data);
});
}
}
}
});
rsz();
}
function scene() {
let objects = [];
Object.defineProperties(this, {
add: {
get: () => obj => {
objects.push(obj);
}
},
objects: {
get: () => objects
},
update: {
get: () => () => {
objects.forEach(x => {
if(typeof x.update == "function") {
x.update();
}
});
}
}
});
}
function mesh(data={}) {
let verts = [],
self = this,
holder = {
position:{},
scale: {
},
rotation: {},
origin:{}
},
actual = {
},
position = {},
scale = {},
rotation = {},
origin = {},
color,
name,
primitive,
eventNames = "update",
events = {},
drawPrimitive = {
circle(ctx) {
ctx.beginPath();
ctx.arc(
self.position.x,
self.position.y,
5,
0,
360 * Math.PI / 180
);
ctx.closePath();
},
rect(ctx) {
ctx.strokeRect(
self.position.x,
self.position.y,
30, 30
);
}
},
width = 1,
height = 1,
primitiveToVerts = {
rect: () => [
0, 0,
width , 0,
width, height,
0, height
]
},
realVerts = verts,
lineSegments = [],
o = this;
function updateRealVerts() {
let actualVerts = [],
originedVerts = [],
adjustedVerts = [],
rotatedVerts = [],
stepSize = o.step || 2,
curVerts = [];
o.verts.forEach((v, i) => {
curVerts.push(v);
if(
(i - 1) % stepSize === 0 &&
i !== 0
) {
actualVerts.push(curVerts);
curVerts = [];
}
});
actualVerts = actualVerts.filter(x => x.length == stepSize);
originedVerts = actualVerts.map(v => [
v[0] - o.origin.x,
v[1] - o.origin.y,
v[2] - o.origin.z
]);
rotatedVerts = originedVerts.map(v =>
[
v[0] * Math.cos(o.rotation.x) -
v[1] * Math.sin(o.rotation.x),
v[0] * Math.sin(o.rotation.x) +
v[1] *Math.cos(o.rotation.x),
v[2]
]
);
adjustedVerts = rotatedVerts.map(v =>
[
v[0] *
o.scale.x +
o.position.x,
v[1] *
o.scale.y +
o.position.y,
v[2] *
o.scale.z +
o.position.z,
]
);
realVerts = adjustedVerts;
updateLineSegments();
}
function updateLineSegments() {
let lines = [];
for(let i = 0, a = realVerts; i < a.length;i++) {
let start = [], end = []
if(i < a.length - 1) {
start = a[i];
end = a[i + 1];
} else {
start = a[i];
end = a[0];
}
lines.push({
start, end
})
}
lineSegments = lines;
}
Object.defineProperties(position, {
x: {
get: () => holder.position.x || 0,
set: v => holder.position.x = v
},
y: {
get: () => holder.position.y || 0,
set: v => holder.position.y = v
},
z: {
get: () => holder.position.z || 0,
set: v => holder.position.z = v
}
});
Object.defineProperties(scale, {
x: {
get: () => holder.scale.x || 1,
set: v => holder.scale.x = v
},
y: {
get: () => holder.scale.y || 1,
set: v => holder.scale.y = v
},
z: {
get: () => holder.scale.z || 1,
set: v => holder.scale.z = v
}
});
Object.defineProperties(rotation, {
x: {
get: () => holder.rotation.x || 0,
set: v => holder.rotation.x = v
},
y: {
get: () => holder.rotation.y || 0,
set: v => holder.rotation.y = v
},
z: {
get: () => holder.rotation.z || 0,
set: v => holder.rotation.z = v
}
});
Object.defineProperties(origin, {
x: {
get: () => holder.origin.x || 0,
set: v => holder.origin.x = v
},
y: {
get: () => holder.origin.y || 0,
set: v => holder.origin.y = v
},
z: {
get: () => holder.origin.z || 0,
set: v => holder.origin.z = v
}
});
Object.defineProperties(this, {
verts: {
get: ()=>verts,
set(v) {
verts = v
}
},
name: {
get: ()=>name,
set(v) {
name = v
}
},
primitive: {
get: ()=>primitive,
set(v) {
primitive = v;
let newVerts = primitiveToVerts[v];
if(newVerts) {
this.verts = newVerts();
}
}
},
width: {
get: ()=>width,
set(v) {
width = v
}
},
height: {
get: ()=>height,
set(v) {
height = v
}
},
position: {
get: () => position,
set: v => {
position.x = v.x || 0;
position.y = v.y || 0;
position.z = v.z || 0;
}
},
scale: {
get: () => scale,
set: v => {
scale.x = v.x || v.x === 0 ? v.x : 1;
scale.y = v.y || v.y === 0 ? v.y : 1;
scale.z = v.z || v.z === 0 ? v.z : 1;
}
},
rotation: {
get: () => rotation,
set: v => {
rotation.x = v.x || 0;
rotation.y = v.y || 0;
rotation.z = v.z || 0;
}
},
origin: {
get: () => origin,
set: v => {
origin.x = v.x || 0;
origin.y = v.y || 0;
origin.z = v.z || 0;
}
},
color: {
get: () => color,
set: v => {
color = v;
}
},
realVerts: {
get: () => realVerts
},
lineSegments: {
get: () => lineSegments
},
update: {
get: () => () => {
if(events["update"]) {
events.update.forEach(x => {
updateRealVerts();
x(this);
});
}
}
},
on: {
get: () => (nm, fnc) => {
if(!events[nm]) events[nm] = [];
events[nm].push(stuff => {
if(typeof fnc == "function") {
fnc(stuff);
}
});
}
}
});
eventNames.split(" ").forEach(x => {
var name = "on" + x;
if(!this.hasOwnProperty(name)) {
Object.defineProperty(this, name, {
get: () => events[name],
set(v) {
events[x] = [
data => {
typeof v == "function" && v(data)
}
];
}
});
}
});
for(let k in data) {
this[k] = data[k]
}
updateRealVerts();
}
canvas{
width:100%;
height:100%;
position:absolute;
top:0;
left:0px
}
.wow{
float:right;
z-index:1298737198
}
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>
</canvas>