@aresrpg/aresrpg-sdk
Version:
General SDK to interract with AresRPG
194 lines (186 loc) • 4.92 kB
JavaScript
/**
* @param {Vec2} v1
* @param {Vec2} v2
* @returns {number}
*/
function distance_vec2(v1, v2) {
const delta = substract_vec2(v1, v2)
return Math.sqrt(delta.x * delta.x + delta.z * delta.z)
}
/**
* @param {Vec2} v1
* @param {Vec2} v2
* @returns {Vec2}
*/
function substract_vec2(v1, v2) {
return { x: v1.x - v2.x, z: v1.z - v2.z }
}
/**
* @param {Vec2} p1
* @param {Vec2} p2
* @param {Vec2} p3
* @returns {number}
*/
function sign(p1, p2, p3) {
return (p1.x - p3.x) * (p2.z - p3.z) - (p2.x - p3.x) * (p1.z - p3.z)
}
/**
* @param {Vec2} pt
* @param {Vec2} v1
* @param {Vec2} v2
* @param {Vec2} v3
* @returns {boolean}
*/
function is_point_in_triangle(pt, v1, v2, v3) {
const d1 = sign(pt, v1, v2)
const d2 = sign(pt, v2, v3)
const d3 = sign(pt, v3, v1)
const has_neg = d1 < 0 || d2 < 0 || d3 < 0
const has_pos = d1 > 0 || d2 > 0 || d3 > 0
return !(has_neg && has_pos)
}
export class LineOfSight {
/**
* @private
* @readonly
*/
grid
/**
* @public
* @param {Parameters} params
*/
constructor(params) {
this.grid = {
size: params.grid.size,
cells: params.grid.cells.map((view_blocking, index) => ({
viewBlocking: view_blocking,
x: index % params.grid.size.x,
z: Math.floor(index / params.grid.size.x),
})),
}
}
/**
* @public
* @param {GridCoord} origin
* @param {number} max_distance
* @returns {GridVisibility}
*/
compute_cells_visibility(origin, max_distance) {
const result = {
size: { ...this.grid.size },
cells: this.grid.cells.map(cell => ({
x: cell.x,
z: cell.z,
visibility:
distance_vec2(origin, cell) > max_distance ? 'neutral' : 'visible',
})),
}
const set_cell_visibility = (coords, visibility) => {
if (
coords.x < 0 ||
coords.z < 0 ||
coords.x >= this.grid.size.x ||
coords.z >= this.grid.size.z
) {
return
}
const index = coords.x + coords.z * this.grid.size.x
result.cells[index].visibility = visibility
}
const viewer_position = { x: origin.x + 0.5, z: origin.z + 0.5 }
const apply_cast_shadow = (s1, s2, min_distance) => {
const p1 = viewer_position
const p2 = {
x: viewer_position.x + 10000 * (s1.x - viewer_position.x),
z: viewer_position.z + 10000 * (s1.z - viewer_position.z),
}
const p3 = {
x: viewer_position.x + 10000 * (s2.x - viewer_position.x),
z: viewer_position.z + 10000 * (s2.z - viewer_position.z),
}
for (let i_z = 0; i_z < this.grid.size.z; i_z++) {
for (let i_x = 0; i_x < this.grid.size.x; i_x++) {
const cell_center = { x: i_x + 0.5, z: i_z + 0.5 }
const distance_from_viewer = distance_vec2(
cell_center,
viewer_position,
)
if (
distance_from_viewer >= min_distance &&
distance_from_viewer <= max_distance
) {
if (is_point_in_triangle(cell_center, p1, p2, p3)) {
set_cell_visibility({ x: i_x, z: i_z }, 'hidden')
}
}
}
}
}
for (const cell of this.grid.cells.filter(cell => cell.viewBlocking)) {
const cell_center = { x: cell.x + 0.5, z: cell.z + 0.5 }
const cell_distance_from_viewer = distance_vec2(
viewer_position,
cell_center,
)
if (cell_distance_from_viewer > max_distance) {
continue
}
const e = 0.01
const c1 = { x: cell.x + 0.0 + e, z: cell.z + 0.0 + e }
const c2 = { x: cell.x + 0.0 + e, z: cell.z + 1.0 - e }
const c3 = { x: cell.x + 1.0 - e, z: cell.z + 1.0 - e }
const c4 = { x: cell.x + 1.0 - e, z: cell.z + 0.0 + e }
const segments = [
[c1, c2],
[c2, c3],
[c3, c4],
[c4, c1],
]
for (const segment of segments) {
apply_cast_shadow(
segment[0],
segment[1],
cell_distance_from_viewer + 0.001,
)
}
}
// @ts-ignore
return result
}
}
/**
* @typedef {Object} GridCoord
* @property {number} x
* @property {number} z
*/
/**
* @typedef {Object} InputGrid
* @property {GridCoord} size
* @property {ReadonlyArray<boolean>} cells
*/
/**
* @typedef {Object} Parameters
* @property {InputGrid} grid
*/
/**
* @typedef {Object} GridCell
* @property {boolean} viewBlocking
* @property {number} x
* @property {number} z
*/
/**
* @typedef {Object} Grid
* @property {GridCoord} size
* @property {ReadonlyArray<GridCell>} cells
*/
/** @typedef {'visible' | 'hidden' | 'neutral'} CellVisibility */
/**
* @typedef {Object} GridVisibility
* @property {GridCoord} size
* @property {ReadonlyArray<{ x: number; z: number; visibility: CellVisibility }>} cells
*/
/**
* @typedef {Object} Vec2
* @property {number} x
* @property {number} z
*/