FBox EntitiesEditor::computeOvergroundBox(const FBox &bbox) const { int over_ground = 0; FBox test_box = bbox; test_box.max.y = test_box.min.y + 1.0f; while(over_ground < 16 && m_tile_map.findAny(test_box - float3(0, 1 + over_ground, 0)) == -1) over_ground++; if(over_ground) return FBox(asXZY(bbox.min.xz(), bbox.min.y - over_ground), asXZY(bbox.max.xz(), bbox.min.y)); return FBox::empty(); }
const IBox View::computeCursor(const int2 &start, const int2 &end, const int3 &bbox, int height, int offset) const { float2 height_off = worldToScreen(float3(0, height, 0)); int3 gbox(cellSize(), 1, cellSize()); int3 start_pos = asXZ((int2)( screenToWorld(float2(start + pos()) - height_off) + float2(0.5f, 0.5f))); int3 end_pos = asXZ((int2)( screenToWorld(float2(end + pos()) - height_off) + float2(0.5f, 0.5f))); start_pos.y = end_pos.y = height + offset; { int apos1 = start_pos.x % gbox.x; int apos2 = apos1 - gbox.x + bbox.x; start_pos.x -= apos1 < gbox.x - apos1 || bbox.x >= gbox.x? apos1 : apos2; } { int apos1 = start_pos.z % gbox.z; int apos2 = apos1 - gbox.z + bbox.z; start_pos.z -= apos1 < gbox.z - apos1 || bbox.z >= gbox.z? apos1 : apos2; } if(end == start) end_pos = start_pos; int3 dir(end_pos.x >= start_pos.x? 1 : -1, 1, end_pos.z >= start_pos.z? 1 : -1); int3 size(::abs(end_pos.x - start_pos.x), 1, ::abs(end_pos.z - start_pos.z)); size += bbox - int3(1, 1, 1); size.x -= size.x % bbox.x; size.z -= size.z % bbox.z; size = max(bbox, size); if(dir.x < 0) start_pos.x += bbox.x; if(dir.z < 0) start_pos.z += bbox.z; end_pos = start_pos + dir * size; if(start_pos.x > end_pos.x) swap(start_pos.x, end_pos.x); if(start_pos.z > end_pos.z) swap(start_pos.z, end_pos.z); int2 dims = m_tile_map.dimensions(); start_pos = asXZY(clamp(start_pos.xz(), int2(0, 0), dims), start_pos.y); end_pos = asXZY(clamp( end_pos.xz(), int2(0, 0), dims), end_pos.y); return IBox(start_pos, end_pos); }
void View::update(const InputState &state) { if(state.isKeyDown('g')) { if(m_is_visible) { if(m_cell_size == 3) m_cell_size = 6; else if(m_cell_size == 6) m_cell_size = 9; else { m_cell_size = 1; m_is_visible = false; } } else { m_cell_size = 3; m_is_visible = true; } } int height_change = state.mouseWheelMove() + (state.isKeyDownAuto(InputKey::pagedown)? -1 : 0) + (state.isKeyDownAuto(InputKey::pageup)? 1 : 0); if(height_change) m_height = clamp(m_height + height_change, 0, (int)Grid::max_height); { int actions[TileGroup::Group::side_count] = { InputKey::kp_1, InputKey::kp_2, InputKey::kp_3, InputKey::kp_6, InputKey::kp_9, InputKey::kp_8, InputKey::kp_7, InputKey::kp_4 }; for(int n = 0; n < arraySize(actions); n++) if(state.isKeyDownAuto(actions[n])) m_view_pos += worldToScreen(TileGroup::Group::s_side_offsets[n] * m_cell_size); } if((state.isKeyPressed(InputKey::lctrl) && state.isMouseButtonPressed(InputButton::left)) || state.isMouseButtonPressed(InputButton::middle)) m_view_pos -= state.mouseMove(); IRect rect = worldToScreen(IBox(int3(0, 0, 0), asXZY(m_tile_map.dimensions(), 256))); m_view_pos = clamp(m_view_pos, rect.min, rect.max - m_view_size); }
bool Turret::canSee(EntityRef target_ref, bool simple_test) { const Entity *target = refEntity(target_ref); if(!target || isDead()) return false; const FBox &box = boundingBox(); const FBox &target_box = target->boundingBox(); float dist = distance(box, target_box); float3 eye_pos = asXZY(box.center().xz(), box.min.y + box.height() * 0.75f); if(dist > max_distance) return false; if(simple_test) return true; //TODO: move to ThinkingEntity? return world()->isVisible(eye_pos, target_ref, ref(), target->typeId() == EntityId::actor? 4 : 3); }
void EntitiesEditor::drawBoxHelpers(Renderer2D &out, const IBox &box) const { int3 pos = box.min, bbox = box.max - box.min; int3 tsize = asXZY(m_tile_map.dimensions(), 32); drawLine(out, int3(0, pos.y, pos.z), int3(tsize.x, pos.y, pos.z), Color(0, 255, 0, 127)); drawLine(out, int3(0, pos.y, pos.z + bbox.z), int3(tsize.x, pos.y, pos.z + bbox.z), Color(0, 255, 0, 127)); drawLine(out, int3(pos.x, pos.y, 0), int3(pos.x, pos.y, tsize.z), Color(0, 255, 0, 127)); drawLine(out, int3(pos.x + bbox.x, pos.y, 0), int3(pos.x + bbox.x, pos.y, tsize.z), Color(0, 255, 0, 127)); int3 tpos(pos.x, 0, pos.z); drawBBox(out, IBox(tpos, tpos + int3(bbox.x, pos.y, bbox.z)), Color(0, 0, 255, 127)); drawLine(out, int3(0, 0, pos.z), int3(tsize.x, 0, pos.z), Color(0, 0, 255, 127)); drawLine(out, int3(0, 0, pos.z + bbox.z), int3(tsize.x, 0, pos.z + bbox.z), Color(0, 0, 255, 127)); drawLine(out, int3(pos.x, 0, 0), int3(pos.x, 0, tsize.z), Color(0, 0, 255, 127)); drawLine(out, int3(pos.x + bbox.x, 0, 0), int3(pos.x + bbox.x, 0, tsize.z), Color(0, 0, 255, 127)); }
void WorldViewer::update(double time_diff) { FWK_PROFILE("WorldViewer::update"); Actor *spectator = m_world->refEntity<Actor>(m_spectator); if((int)m_entities.size() != m_world->entityCount()) m_entities.resize(m_world->entityCount()); if(!m_spectator) { for(int n = 0; n < (int)m_entities.size(); n++) { Entity *entity = m_world->refEntity(n); VisEntity &vis_entity = m_entities[n]; vis_entity.ref = entity? entity->ref() : EntityRef(); vis_entity.mode = entity? VisEntity::visible : VisEntity::invisible; vis_entity.occluder_id = -1; } return; } if(!spectator) return; FBox bbox = spectator->boundingBox(); m_cur_pos = bbox.center(); m_eye_pos = asXZY(m_cur_pos.xz(), bbox.min.y + bbox.height() * 0.75f); for(int n = 0; n < (int)m_entities.size(); n++) { Entity *entity = m_world->refEntity(n); VisEntity &vis_entity = m_entities[n]; if(!entity) { vis_entity = VisEntity(); continue; } const auto *desc = m_world->refEntityDesc(n); DASSERT(desc); bool is_visible = m_see_all || spectator == entity || spectator->canSee(entity->ref(), !isMovable(*entity)); if(vis_entity.ref != entity->ref()) { vis_entity = VisEntity(); vis_entity.ref = entity->ref(); vis_entity.mode = is_visible? VisEntity::visible : VisEntity::invisible; } if(is_visible) { vis_entity.occluder_id = desc->occluder_id; if(vis_entity.mode == VisEntity::visible) continue; if(vis_entity.mode == VisEntity::shadowed || vis_entity.mode == VisEntity::pre_blending_out) { vis_entity.shadow.reset(); vis_entity.mode = VisEntity::visible; } else { float blend_value = 0.0; if(vis_entity.mode == VisEntity::blending_in) blend_value = vis_entity.blend_value; else if(vis_entity.mode == VisEntity::blending_out) blend_value = blend_time - vis_entity.blend_value; blend_value += time_diff; vis_entity.mode = blend_value > blend_time? VisEntity::visible : VisEntity::blending_in; vis_entity.blend_value = blend_value; } } else { if(vis_entity.mode == VisEntity::shadowed || vis_entity.mode == VisEntity::invisible) continue; if(vis_entity.mode == VisEntity::visible) { vis_entity.blend_value = 0.0f; vis_entity.mode = VisEntity::pre_blending_out; } if(vis_entity.mode == VisEntity::pre_blending_out) { vis_entity.blend_value += time_diff; if(vis_entity.blend_value > blend_time) { if(isMovable(*entity)) { vis_entity.mode = VisEntity::blending_out; vis_entity.blend_value = vis_entity.blend_value - blend_time; } else { vis_entity.mode = VisEntity::shadowed; vis_entity.occluder_id = desc->occluder_id; vis_entity.shadow.reset(entity->clone()); } } } else { float blend_value = 0.0f; if(vis_entity.mode == VisEntity::blending_in) blend_value = blend_time - vis_entity.blend_value; else if(vis_entity.mode == VisEntity::blending_out) blend_value = vis_entity.blend_value; vis_entity.blend_value = blend_value + time_diff; vis_entity.mode = VisEntity::blending_out; if(vis_entity.blend_value > blend_time) vis_entity.mode = VisEntity::invisible; } } } if(m_occluder_config.update(spectator->boundingBox())) m_world->tileMap().updateVisibility(m_occluder_config); }
bool OccluderConfig::update(const FBox &bbox) { //TODO: hiding when close to a door/window FBox test_box(bbox.min.x, bbox.min.y + 1.0f, bbox.min.z, bbox.max.x, 256, bbox.max.z); float3 mid_point = asXZY(test_box.center().xz(), bbox.min.y + 2.0f); bool vis_changed = update(); vector<int> temp; temp.reserve(256); IRect test_rect = (IRect)worldToScreen(bbox); const Grid &grid = m_map.m_grid; grid.findAll(temp, test_rect); vector<int> temp2; temp2.reserve(256); PodArray<int> overlaps(m_map.size()); memset(overlaps.data(), 0, m_map.size() * sizeof(int)); for(int i = 0; i < (int)temp.size(); i++) { const auto &object = grid[temp[i]]; if(object.occluder_id == -1) continue; const OccluderMap::Occluder &occluder = m_map[object.occluder_id]; int order = drawingOrder(object.bbox, bbox); if(order == 1) overlaps[object.occluder_id] = order; } for(int n = 0; n < (int)m_states.size(); n++) { bool is_overlapping = false; const OccluderMap::Occluder &occluder = m_map[n]; if(overlaps[n] == 1) { FBox bbox_around(bbox.min - float3(16, 0, 16), bbox.max + float3(16, 0, 16)); bbox_around.min.y = 0; bbox_around.max.y = Grid::max_height; temp2.clear(); grid.findAll(temp2, bbox_around); FBox local_box = FBox(); for(int i = 0; i < (int)temp2.size(); i++) { const auto &object = grid[temp2[i]]; if(object.occluder_id == n) local_box = local_box.empty()? object.bbox : sum(local_box, object.bbox); } is_overlapping = local_box.min.y > mid_point.y; } if(is_overlapping != m_states[n].is_overlapping) { m_states[n].is_overlapping = is_overlapping; vis_changed = true; } } if(!vis_changed) return false; for(int n= 0; n < (int)m_states.size(); n++) m_states[n].is_visible = !m_states[n].is_overlapping; //TODO: isUnder can be precomputed for(int n = 0; n < (int)m_states.size(); n++) { if(!m_states[n].is_visible) continue; for(int i = 0; i < (int)m_states.size(); i++) if(!m_states[i].is_visible && m_map.isUnder(i, n)) { m_states[n].is_visible = false; break; } } return true; }
bool EntitiesEditor::onMouseDrag(const InputState &state, int2 start, int2 current, int key, int is_final) { bool shift_pressed = state.isKeyPressed(InputKey::lshift); if(key == 0 && !state.isKeyPressed(InputKey::lctrl)) { computeCursor(start, current, shift_pressed); m_is_selecting = !is_final; if(m_mode == Mode::selecting && is_final && is_final != -1) { findVisible(m_selected_ids, m_selection); computeCursor(current, current, shift_pressed); } else if(m_mode == Mode::placing) { if(m_proto->typeId() == EntityId::trigger) { Trigger *trigger = static_cast<Trigger*>(m_proto.get()); if(m_trigger_mode == 0) { m_trigger_box = m_view.computeCursor(start, current, int3(1, 1, 1), m_cursor_pos.y, 0); if(state.isMouseButtonDown(InputButton::right)) { m_trigger_mode = 1; m_trigger_offset = current; } } IBox new_box = m_trigger_box; if(m_trigger_mode == 1) { int offset = screenToWorld(int2(0, m_trigger_offset.y - current.y)).y; if(offset < 0) new_box.min.y += offset; else new_box.max.y += offset; } trigger->setBox((FBox)new_box); m_cursor_pos = trigger->pos(); if(is_final > 0) { m_entity_map.add(PEntity(m_proto->clone())); } if(is_final) { m_trigger_mode = 0; trigger->setBox(FBox(0, 0, 0, 1, 1, 1) + trigger->pos()); } } else if(is_final > 0) m_entity_map.add(PEntity(m_proto->clone())); } return true; } else if(key == 1 && m_mode == Mode::selecting) { if(!m_is_moving) { m_is_moving_vertically = state.isKeyPressed(InputKey::lshift); m_is_moving = true; } if(m_is_moving_vertically) m_move_offset = int3(0, screenToWorld(int2(0, start.y - current.y)).y, 0); else m_move_offset = asXZY(screenToWorld(current - start), 0); if(is_final) m_is_moving = false; if(is_final > 0) { for(int n = 0; n < (int)m_selected_ids.size(); n++) { auto &object = m_entity_map[m_selected_ids[n]]; object.ptr->setPos(object.ptr->pos() + float3(m_move_offset)); m_entity_map.update(m_selected_ids[n]); } } return true; } return false; }